PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.2
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.2
2.14.2 2.14.1 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 All 57 releases
← All changes | includes/api/class-seo-analytics-endpoint.php +46 -207 2.1.1 → 2.14.2 View file →
@@ -145,21 +145,8 @@
145 145 ]
146 146 ]
147 147 );
148 148
149 - // Get indexing status
150 - register_rest_route(
151 - $this->namespace,
152 - '/' . $this->rest_base . '/indexing-status',
153 - [
154 - [
155 - 'methods' => 'GET',
156 - 'callback' => [$this, 'get_indexing_status'],
157 - 'permission_callback' => [$this, 'check_permissions'],
158 - ]
159 - ]
160 - );
161 -
162 149 // Refresh cached data
163 150 register_rest_route(
164 151 $this->namespace,
165 152 '/' . $this->rest_base . '/refresh',
@@ -184,54 +171,8 @@
184 171 ]
185 172 ]
186 173 );
187 174
188 - // ========================================
189 - // SEO Intelligence Enhancement Endpoints
190 - // ========================================
191 -
192 - // Get intelligent dashboard data with trends and insights
193 - register_rest_route(
194 - $this->namespace,
195 - '/' . $this->rest_base . '/intelligent-dashboard',
196 - [
197 - [
198 - 'methods' => 'GET',
199 - 'callback' => [$this, 'get_intelligent_dashboard'],
200 - 'permission_callback' => [$this, 'check_data_permissions'],
201 - 'args' => $this->get_dashboard_args()
202 - ]
203 - ]
204 - );
205 -
206 - // Get intelligent SEO opportunities with prioritization
207 - register_rest_route(
208 - $this->namespace,
209 - '/' . $this->rest_base . '/intelligent-opportunities',
210 - [
211 - [
212 - 'methods' => 'GET',
213 - 'callback' => [$this, 'get_intelligent_opportunities'],
214 - 'permission_callback' => [$this, 'check_data_permissions'],
215 - 'args' => $this->get_opportunities_args()
216 - ]
217 - ]
218 - );
219 -
220 - // Get SEO insights
221 - register_rest_route(
222 - $this->namespace,
223 - '/' . $this->rest_base . '/insights',
224 - [
225 - [
226 - 'methods' => 'GET',
227 - 'callback' => [$this, 'get_seo_insights'],
228 - 'permission_callback' => [$this, 'check_data_permissions'],
229 - 'args' => $this->get_dashboard_args()
230 - ]
231 - ]
232 - );
233 -
234 175 // Get Search Console totals for custom date range
235 176 register_rest_route(
236 177 $this->namespace,
237 178 '/' . $this->rest_base . '/search-totals',
@@ -430,32 +371,8 @@
430 371 }
431 372 }
432 373
433 374 /**
434 - * Get indexing status
435 - *
436 - * @param WP_REST_Request $request Request object
437 - * @return WP_REST_Response|WP_Error Response object
438 - */
439 - public function get_indexing_status(WP_REST_Request $request) {
440 - try {
441 - $indexing_status = $this->analytics_manager->get_indexing_status();
442 -
443 - return new WP_REST_Response([
444 - 'success' => true,
445 - 'data' => $indexing_status,
446 - 'message' => 'Indexing status retrieved successfully'
447 - ], 200);
448 - } catch (\Exception $e) {
449 - return new WP_Error(
450 - 'indexing_status_failed',
451 - 'Failed to retrieve indexing status: ' . $e->getMessage(),
452 - ['status' => 500]
453 - );
454 - }
455 - }
456 -
457 - /**
458 375 * Refresh cached analytics data
459 376 *
460 377 * @param WP_REST_Request $request Request object
461 378 * @return WP_REST_Response|WP_Error Response object
@@ -550,11 +467,11 @@
550 467 return [
551 468 'site_url' => [
552 469 'required' => true,
553 470 'type' => 'string',
554 - 'sanitize_callback' => 'esc_url_raw',
471 + 'sanitize_callback' => [$this, 'sanitize_site_url'],
555 472 'validate_callback' => [$this, 'validate_site_url'],
556 - 'description' => 'Site URL to verify in Search Console'
473 + 'description' => 'Search Console property: a URL-prefix property (https://example.com/) or a domain property (sc-domain:example.com)'
557 474 ]
558 475 ];
559 476 }
560 477
@@ -585,9 +502,26 @@
585 502 ['status' => 400]
586 503 );
587 504 }
588 505
589 - if (!filter_var($site_url, FILTER_VALIDATE_URL)) {
506 + // A domain property (sc-domain:example.com) is not a URL, and
507 + // is_valid() refused every one, though the rest of the Search Console
508 + // code reads them. Its host must be a real hostname; an IDN is fine.
509 + if (0 === stripos($site_url, 'sc-domain:')) {
510 + if (null === \ThinkRank\Core\Url_Validator::search_console_domain_property($site_url)) {
511 + return new WP_Error(
512 + 'invalid_site_url',
513 + 'A domain property must be sc-domain: followed by a domain name, such as sc-domain:example.com',
514 + ['status' => 400]
515 + );
516 + }
517 +
518 + return true;
519 + }
520 +
521 + // Url_Validator accepts an internationalised domain, which is a valid
522 + // site URL; the raw PHP filter refuses every non-ASCII byte.
523 + if (!\ThinkRank\Core\Url_Validator::is_valid($site_url)) {
590 524 return new WP_Error(
591 525 'invalid_site_url',
592 526 'Site URL must be a valid URL',
593 527 ['status' => 400]
@@ -597,8 +531,34 @@
597 531 return true;
598 532 }
599 533
600 534 /**
535 + * Sanitize the Search Console property sent to setup.
536 + *
537 + * esc_url_raw() for a URL-prefix property, as before. A domain property
538 + * is not a URL: esc_url_raw() drops "sc-domain:" as an unknown protocol
539 + * and returns "", so it is normalised to its punycode form instead, which
540 + * is what verify_site() matches against the account's property list.
541 + *
542 + * @since 2.14.2
543 + *
544 + * @param mixed $site_url Raw parameter, already through validate_site_url().
545 + * @return string
546 + */
547 + public function sanitize_site_url($site_url): string {
548 + if (!is_string($site_url)) {
549 + return '';
550 + }
551 +
552 + $domain = \ThinkRank\Core\Url_Validator::search_console_domain_property($site_url);
553 + if (null !== $domain) {
554 + return $domain;
555 + }
556 +
557 + return esc_url_raw($site_url);
558 + }
559 +
560 + /**
601 561 * Get Search Console totals for a custom date range
602 562 *
603 563 * @param WP_REST_Request $request Request object
604 564 * @return WP_REST_Response|WP_Error Response object
@@ -951,127 +911,6 @@
951 911 );
952 912 }
953 913
954 914 return true;
955 - }
956 -
957 - // ========================================
958 - // SEO Intelligence Enhancement Endpoints
959 - // ========================================
960 -
961 - /**
962 - * Get intelligent dashboard data with trends and insights
963 - *
964 - * @param WP_REST_Request $request Request object
965 - * @return WP_REST_Response|WP_Error Response object
966 - */
967 - public function get_intelligent_dashboard(WP_REST_Request $request) {
968 - try {
969 - $date_range = $request->get_param('date_range');
970 -
971 - // Cache the intelligence computation (trend analysis + generators are
972 - // expensive) so the AI-insights panel doesn't recompute every load.
973 - $response = $this->cached_response(
974 - 'intelligent_dashboard',
975 - function () use ($date_range) {
976 - $intelligent_data = $this->analytics_manager->get_intelligent_dashboard_data($date_range);
977 -
978 - return [
979 - 'success' => isset($intelligent_data['success']) ? $intelligent_data['success'] : false,
980 - 'data' => $intelligent_data['data'] ?? null,
981 - 'message' => $intelligent_data['message'] ?? 'Intelligent dashboard data retrieved',
982 - 'timestamp' => current_time('mysql'),
983 - ];
984 - },
985 - ['date_range' => $date_range]
986 - );
987 -
988 - // Always return 200 for successful API calls, even when no data available.
989 - return new WP_REST_Response($response, 200);
990 -
991 - } catch (\Exception $e) {
992 - return new WP_Error(
993 - 'intelligent_dashboard_error',
994 - 'Failed to retrieve intelligent dashboard data: ' . $e->getMessage(),
995 - ['status' => 500]
996 - );
997 - }
998 - }
999 -
1000 - /**
1001 - * Get intelligent SEO opportunities with prioritization
1002 - *
1003 - * @param WP_REST_Request $request Request object
1004 - * @return WP_REST_Response|WP_Error Response object
1005 - */
1006 - public function get_intelligent_opportunities(WP_REST_Request $request) {
1007 - try {
1008 - $date_range = $request->get_param('date_range');
1009 -
1010 - // Cache the opportunity detection so it doesn't recompute every load.
1011 - $response = $this->cached_response(
1012 - 'intelligent_opportunities',
1013 - function () use ($date_range) {
1014 - $intelligent_opportunities = $this->analytics_manager->get_intelligent_seo_opportunities($date_range);
1015 -
1016 - return [
1017 - 'success' => isset($intelligent_opportunities['success']) ? $intelligent_opportunities['success'] : false,
1018 - 'data' => $intelligent_opportunities['data'] ?? null,
1019 - 'message' => $intelligent_opportunities['message'] ?? 'Intelligent opportunities retrieved',
1020 - 'timestamp' => current_time('mysql'),
1021 - ];
1022 - },
1023 - ['date_range' => $date_range]
1024 - );
1025 -
1026 - // Always return 200 for successful API calls, even when no data available.
1027 - return new WP_REST_Response($response, 200);
1028 -
1029 - } catch (\Exception $e) {
1030 - return new WP_Error(
1031 - 'intelligent_opportunities_error',
1032 - 'Failed to retrieve intelligent opportunities: ' . $e->getMessage(),
1033 - ['status' => 500]
1034 - );
1035 - }
1036 - }
1037 -
1038 - /**
1039 - * Get SEO insights
1040 - *
1041 - * @param WP_REST_Request $request Request object
1042 - * @return WP_REST_Response|WP_Error Response object
1043 - */
1044 - public function get_seo_insights(WP_REST_Request $request) {
1045 - try {
1046 - $date_range = $request->get_param('date_range');
1047 -
1048 - // Endpoint-level cache for consistency with the other two intelligence
1049 - // calls (the manager also caches insights internally).
1050 - $response = $this->cached_response(
1051 - 'seo_insights',
1052 - function () use ($date_range) {
1053 - $insights = $this->analytics_manager->get_seo_insights($date_range);
1054 -
1055 - return [
1056 - 'success' => isset($insights['success']) ? $insights['success'] : false,
1057 - 'data' => $insights['data'] ?? null,
1058 - 'cached' => $insights['cached'] ?? false,
1059 - 'message' => $insights['message'] ?? 'SEO insights retrieved',
1060 - 'timestamp' => current_time('mysql'),
1061 - ];
1062 - },
1063 - ['date_range' => $date_range]
1064 - );
1065 -
1066 - // Always return 200 for successful API calls, even when no data available.
1067 - return new WP_REST_Response($response, 200);
1068 -
1069 - } catch (\Exception $e) {
1070 - return new WP_Error(
1071 - 'seo_insights_error',
1072 - 'Failed to retrieve SEO insights: ' . $e->getMessage(),
1073 - ['status' => 500]
1074 - );
1075 - }
1076 915 }
1077 916 }