PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 1.11.0 All 47 releases
thinkrank / includes / api / class-pillar-content-endpoint.php

class-pillar-content-endpoint.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/api/class-pillar-content-endpoint.php

189 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Pillar Content API Endpoint Class
5 *
6 * REST API endpoints for fetching pillar content suggestions.
7 *
8 * @package ThinkRank
9 * @subpackage API
10 * @since 1.0.0
11 */
12
13 declare(strict_types=1);
14
15 namespace ThinkRank\API;
16
17 use WP_REST_Controller;
18 use WP_REST_Request;
19 use WP_REST_Response;
20 use WP_Query;
21 use WP_Error;
22
23 // Prevent direct access
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Pillar Content API Endpoint Class
30 *
31 * @since 1.0.0
32 */
33 class Pillar_Content_Endpoint extends WP_REST_Controller {
34
35 /**
36 * API namespace
37 *
38 * @var string
39 */
40 protected $namespace = 'thinkrank/v1';
41
42 /**
43 * API resource base
44 *
45 * @var string
46 */
47 protected $rest_base = 'pillar-content';
48
49 /**
50 * Register API routes
51 *
52 * @return void
53 */
54 public function register_routes(): void {
55 register_rest_route(
56 $this->namespace,
57 '/' . $this->rest_base . '/suggestions',
58 [
59 [
60 'methods' => 'GET',
61 'callback' => [$this, 'get_suggestions'],
62 'permission_callback' => [$this, 'check_read_permissions'],
63 'args' => [
64 'post_id' => [
65 'required' => true,
66 'type' => 'integer',
67 'validate_callback' => function ($param, $request, $key) {
68 return is_numeric($param);
69 },
70 'sanitize_callback' => 'absint',
71 ],
72 ],
73 ]
74 ]
75 );
76 }
77
78 /**
79 * Get pillar content suggestions
80 *
81 * @param WP_REST_Request $request Request object
82 * @return WP_REST_Response|WP_Error Response object
83 */
84 public function get_suggestions(WP_REST_Request $request) {
85 $post_id = $request->get_param('post_id');
86
87 if (!$post_id) {
88 return new WP_Error(
89 'missing_post_id',
90 __('Post ID is required', 'thinkrank'),
91 ['status' => 400]
92 );
93 }
94
95 $post_type = get_post_type($post_id);
96 if (!$post_type) {
97 return new WP_Error(
98 'invalid_post_id',
99 __('Invalid Post ID', 'thinkrank'),
100 ['status' => 400]
101 );
102 }
103
104 // Check global settings for this post type
105 $all_settings = get_option('thinkrank_global_seo_settings', []);
106 $link_suggestions_enabled = true; // Default to true
107
108 if (isset($all_settings[$post_type]['link_suggestions'])) {
109 $link_suggestions_enabled = (bool) $all_settings[$post_type]['link_suggestions'];
110 }
111
112 if (!$link_suggestions_enabled) {
113 return new WP_REST_Response([], 200);
114 }
115
116 $taxonomies = get_object_taxonomies($post_type);
117 $tax_query = [];
118 $has_terms = false;
119
120 if (!empty($taxonomies)) {
121 $tax_query['relation'] = 'OR';
122
123 foreach ($taxonomies as $taxonomy) {
124 $terms = get_the_terms($post_id, $taxonomy);
125 if ($terms && !is_wp_error($terms)) {
126 $term_ids = wp_list_pluck($terms, 'term_id');
127 if (!empty($term_ids)) {
128 $tax_query[] = [
129 'taxonomy' => $taxonomy,
130 'field' => 'term_id',
131 'terms' => $term_ids,
132 ];
133 $has_terms = true;
134 }
135 }
136 }
137 }
138
139 // Without this the query kept a tax_query holding nothing but
140 // 'relation' => 'OR' whenever the post had no terms — $has_terms was
141 // assigned and never read.
142 if (!$has_terms) {
143 $tax_query = [];
144 }
145
146 $args = [
147 'post_type' => $post_type,
148 'posts_per_page' => 5,
149 'post__not_in' => [$post_id],
150 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- admin-only endpoint listing pillar posts; there is no taxonomy to key this on.
151 'meta_query' => [
152 [
153 'key' => '_thinkrank_pillar_content',
154 'value' => '1',
155 'compare' => '=',
156 ]
157 ],
158 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- admin-only endpoint, optional filter.
159 'tax_query' => $tax_query,
160 'fields' => 'ids'
161 ];
162
163 $query = new WP_Query($args);
164 $suggestions = [];
165
166 if ($query->have_posts()) {
167 foreach ($query->posts as $p_id) {
168 $suggestions[] = [
169 'id' => $p_id,
170 'title' => get_the_title($p_id),
171 'permalink' => get_permalink($p_id),
172 'type' => get_post_type($p_id)
173 ];
174 }
175 }
176
177 return new WP_REST_Response($suggestions, 200);
178 }
179
180 /**
181 * Check if user can read posts
182 *
183 * @return bool
184 */
185 public function check_read_permissions() {
186 return current_user_can('edit_posts');
187 }
188 }
189