PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
2.14.0 2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 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 All 55 releases
← All changes | includes/api/class-ai-insights-endpoint.php +13 -232 1.28.0 → 2.14.0 View file →
@@ -1,17 +1,17 @@
1 1 <?php
2 2 /**
3 3 * AI Insights API endpoints.
4 4 *
5 - * Admin REST surface for the AI Insights screen (#248 P1 trio):
5 + * Admin REST surface for the AI Insights screen (#248 P1):
6 6 *
7 7 * GET /ai-insights/traffic — AI referral/crawler dashboard summary
8 - * GET /ai-insights/brand — saved queries + check history
9 - * POST /ai-insights/brand — save the query list
10 - * POST /ai-insights/brand/run — run checks now (paid AI calls, capped)
11 - * GET /ai-insights/auto-ai — auto-optimization settings + last run
12 - * POST /ai-insights/auto-ai — save auto-optimization settings
13 8 *
9 + * Automatic AI metadata on publish, the other tab on this screen, is a
10 + * ThinkRank Pro feature and registers its own routes there (#673). Brand
11 + * Visibility used to live in this section too; its v1 routes here were removed
12 + * in 1.30.0 (#301), and the feature itself was removed later.
13 + *
14 14 * @package ThinkRank
15 15 * @subpackage API
16 16 * @since 1.27.0
17 17 */
@@ -19,12 +19,10 @@
19 19 declare(strict_types=1);
20 20
21 21 namespace ThinkRank\API;
22 22
23 -use ThinkRank\AI\Brand_Visibility_Checker;
24 -use ThinkRank\Core\Settings;
23 +use ThinkRank\Core\Capability_Manager;
25 24 use ThinkRank\SEO\Ai_Traffic_Tracker;
26 -use ThinkRank\SEO\Auto_Ai_Optimizer;
27 25 use WP_REST_Controller;
28 26 use WP_REST_Request;
29 27 use WP_REST_Response;
30 28
@@ -33,9 +31,9 @@
33 31 exit;
34 32 }
35 33
36 34 /**
37 - * REST controller for AI traffic, brand visibility, and auto AI settings.
35 + * REST controller for AI traffic.
38 36 */
39 37 class Ai_Insights_Endpoint extends WP_REST_Controller {
40 38
41 39 /**
@@ -71,93 +69,8 @@
71 69 'maximum' => 180,
72 70 ],
73 71 ],
74 72 ]);
75 -
76 - register_rest_route($this->namespace, '/' . $this->rest_base . '/brand', [
77 - [
78 - 'methods' => 'GET',
79 - 'callback' => [$this, 'get_brand'],
80 - 'permission_callback' => [$this, 'check_admin_permissions'],
81 - ],
82 - [
83 - 'methods' => 'POST',
84 - 'callback' => [$this, 'save_brand_queries'],
85 - 'permission_callback' => [$this, 'check_admin_permissions'],
86 - 'args' => [
87 - 'queries' => [
88 - 'required' => true,
89 - 'type' => 'array',
90 - 'items' => ['type' => 'string'],
91 - 'sanitize_callback' => static function ($value) {
92 - if (!is_array($value)) {
93 - return [];
94 - }
95 - $clean = array_values(array_filter(array_map(
96 - static fn($q) => sanitize_text_field((string) $q),
97 - $value
98 - )));
99 - // Clamp to the plan's cap, not the hard ceiling, so
100 - // free can't persist queries it may not run.
101 - return array_slice($clean, 0, Brand_Visibility_Checker::query_limit());
102 - },
103 - ],
104 - ],
105 - ],
106 - ]);
107 -
108 - register_rest_route($this->namespace, '/' . $this->rest_base . '/brand/run', [
109 - 'methods' => 'POST',
110 - 'callback' => [$this, 'run_brand_checks'],
111 - 'permission_callback' => [$this, 'check_admin_permissions'],
112 - ]);
113 -
114 - register_rest_route($this->namespace, '/' . $this->rest_base . '/brand/history/(?P<id>\d+)', [
115 - 'methods' => 'DELETE',
116 - 'callback' => [$this, 'delete_brand_history'],
117 - 'permission_callback' => [$this, 'check_admin_permissions'],
118 - 'args' => [
119 - 'id' => [
120 - 'required' => true,
121 - 'type' => 'integer',
122 - 'minimum' => 1,
123 - ],
124 - ],
125 - ]);
126 -
127 - register_rest_route($this->namespace, '/' . $this->rest_base . '/auto-ai', [
128 - [
129 - 'methods' => 'GET',
130 - 'callback' => [$this, 'get_auto_ai'],
131 - 'permission_callback' => [$this, 'check_admin_permissions'],
132 - ],
133 - [
134 - 'methods' => 'POST',
135 - 'callback' => [$this, 'save_auto_ai'],
136 - 'permission_callback' => [$this, 'check_admin_permissions'],
137 - 'args' => [
138 - 'enabled' => [
139 - 'required' => false,
140 - 'type' => 'boolean',
141 - ],
142 - 'post_types' => [
143 - 'required' => false,
144 - 'type' => 'array',
145 - 'items' => ['type' => 'string'],
146 - 'sanitize_callback' => static function ($value) {
147 - if (!is_array($value)) {
148 - return [];
149 - }
150 - // Only real, public post types survive.
151 - return array_values(array_filter(
152 - array_map('sanitize_key', $value),
153 - static fn($t) => post_type_exists($t)
154 - ));
155 - },
156 - ],
157 - ],
158 - ],
159 - ]);
160 73 }
161 74
162 75 /**
163 76 * Admin permission gate (matches the other admin-only endpoints).
@@ -164,9 +77,13 @@
164 77 *
165 78 * @return bool
166 79 */
167 80 public function check_admin_permissions(): bool {
168 - return current_user_can('manage_options');
81 + // The capability the Role Manager actually writes against, not
82 + // manage_options: `route_map()` maps this prefix to
83 + // thinkrank_ai_insights and the matrix offers an "AI Insights" row, so
84 + // demanding manage_options here made that grant do nothing (#844).
85 + return Capability_Manager::current_user_can('thinkrank_ai_insights');
169 86 }
170 87
171 88 /**
172 89 * AI traffic dashboard summary.
@@ -180,142 +97,6 @@
180 97 return new WP_REST_Response([
181 98 'success' => true,
182 99 'data' => $tracker->summary((int) $request->get_param('days')),
183 100 ], 200);
184 - }
185 -
186 - /**
187 - * Saved brand queries + history.
188 - *
189 - * @return WP_REST_Response
190 - */
191 - public function get_brand(): WP_REST_Response {
192 - $checker = new Brand_Visibility_Checker();
193 -
194 - return new WP_REST_Response([
195 - 'success' => true,
196 - 'data' => [
197 - 'queries' => $checker->queries(),
198 - 'history' => $checker->history(),
199 - // The plan's cap, not the hard ceiling — the UI renders the
200 - // limit the user can actually use.
201 - 'max_queries' => Brand_Visibility_Checker::query_limit(),
202 - 'plan' => \ThinkRank\Core\Plan_Config::ai_visibility(),
203 - 'is_pro' => \ThinkRank\Core\Plan_Config::is_pro(),
204 - 'brand' => (string) get_bloginfo('name'),
205 - 'host' => (string) wp_parse_url(home_url(), PHP_URL_HOST),
206 - ],
207 - ], 200);
208 - }
209 -
210 - /**
211 - * Save the brand query list.
212 - *
213 - * @param WP_REST_Request $request Request.
214 - * @return WP_REST_Response
215 - */
216 - public function save_brand_queries(WP_REST_Request $request): WP_REST_Response {
217 - $queries = (array) $request->get_param('queries');
218 - Settings::instance()->set('brand_visibility_queries', $queries);
219 -
220 - return new WP_REST_Response([
221 - 'success' => true,
222 - 'data' => ['queries' => $queries],
223 - ], 200);
224 - }
225 -
226 - /**
227 - * Run brand checks now.
228 - *
229 - * @return WP_REST_Response
230 - */
231 - public function run_brand_checks(): WP_REST_Response {
232 - try {
233 - $checker = new Brand_Visibility_Checker();
234 - $results = $checker->run();
235 - } catch (\Exception $e) {
236 - return new WP_REST_Response([
237 - 'success' => false,
238 - 'message' => $e->getMessage(),
239 - ], 400);
240 - }
241 -
242 - return new WP_REST_Response([
243 - 'success' => true,
244 - 'data' => [
245 - 'results' => $results,
246 - 'history' => (new Brand_Visibility_Checker())->history(),
247 - ],
248 - ], 200);
249 - }
250 -
251 - /**
252 - * Delete a single brand check-history row.
253 - *
254 - * @param WP_REST_Request $request Request.
255 - * @return WP_REST_Response
256 - */
257 - public function delete_brand_history(WP_REST_Request $request): WP_REST_Response {
258 - $id = (int) $request->get_param('id');
259 - $checker = new Brand_Visibility_Checker();
260 - $deleted = $checker->delete_history($id);
261 -
262 - return new WP_REST_Response([
263 - 'success' => $deleted,
264 - 'data' => [
265 - 'id' => $id,
266 - 'history' => $checker->history(),
267 - ],
268 - ], $deleted ? 200 : 404);
269 - }
270 -
271 - /**
272 - * Auto AI settings + last run outcome.
273 - *
274 - * @return WP_REST_Response
275 - */
276 - public function get_auto_ai(): WP_REST_Response {
277 - $settings = Settings::instance();
278 -
279 - return new WP_REST_Response([
280 - 'success' => true,
281 - 'data' => [
282 - 'enabled' => (bool) $settings->get('auto_ai_meta_enabled', false),
283 - 'post_types' => (array) $settings->get('auto_ai_meta_post_types', ['post']),
284 - 'last_run' => get_option(Auto_Ai_Optimizer::LAST_RUN_OPTION, null),
285 - // Pro-gated: the UI locks the toggle and shows the upsell.
286 - 'available' => \ThinkRank\Core\Plan_Config::can('auto_ai_meta', 'ai_visibility'),
287 - ],
288 - ], 200);
289 - }
290 -
291 - /**
292 - * Save auto AI settings.
293 - *
294 - * @param WP_REST_Request $request Request.
295 - * @return WP_REST_Response
296 - */
297 - public function save_auto_ai(WP_REST_Request $request): WP_REST_Response {
298 - $settings = Settings::instance();
299 -
300 - if (null !== $request->get_param('enabled')) {
301 - $enabled = rest_sanitize_boolean($request->get_param('enabled'));
302 -
303 - // Pro capability. Turning it OFF always works — a lapsed licence
304 - // must never trap the setting in the on position.
305 - if ($enabled && !\ThinkRank\Core\Plan_Config::can('auto_ai_meta', 'ai_visibility')) {
306 - return new WP_REST_Response([
307 - 'success' => false,
308 - 'message' => __('Automatic AI metadata is a ThinkRank Pro feature.', 'thinkrank'),
309 - 'data' => ['requires_pro' => true],
310 - ], 403);
311 - }
312 -
313 - $settings->set('auto_ai_meta_enabled', $enabled);
314 - }
315 - if (null !== $request->get_param('post_types')) {
316 - $settings->set('auto_ai_meta_post_types', (array) $request->get_param('post_types'));
317 - }
318 -
319 - return $this->get_auto_ai();
320 101 }
321 102 }