| @@ -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', |
| @@ -336,9 +277,9 @@ | ||
| 336 | 277 | * |
| 337 | 278 | * @param WP_REST_Request $request Request object |
| 338 | 279 | * @return WP_REST_Response|WP_Error Response object |
| 339 | 280 | */ |
| 340 | - public function test_connections(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 281 | + public function test_connections(WP_REST_Request $request) { | |
| 341 | 282 | try { |
| 342 | 283 | $connection_results = $this->analytics_manager->test_connections(); |
| 343 | 284 | |
| 344 | 285 | return new WP_REST_Response([ |
| @@ -360,9 +301,9 @@ | ||
| 360 | 301 | * |
| 361 | 302 | * @param WP_REST_Request $request Request object |
| 362 | 303 | * @return WP_REST_Response|WP_Error Response object |
| 363 | 304 | */ |
| 364 | - public function get_dashboard_data(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 305 | + public function get_dashboard_data(WP_REST_Request $request) { | |
| 365 | 306 | try { |
| 366 | 307 | $date_range = $request->get_param('date_range'); |
| 367 | 308 | $dashboard_data = $this->analytics_manager->get_dashboard_data($date_range); |
| 368 | 309 | |
| @@ -385,9 +326,9 @@ | ||
| 385 | 326 | * |
| 386 | 327 | * @param WP_REST_Request $request Request object |
| 387 | 328 | * @return WP_REST_Response|WP_Error Response object |
| 388 | 329 | */ |
| 389 | - public function get_seo_opportunities(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 330 | + public function get_seo_opportunities(WP_REST_Request $request) { | |
| 390 | 331 | try { |
| 391 | 332 | $date_range = $request->get_param('date_range'); |
| 392 | 333 | $opportunities = $this->analytics_manager->get_seo_opportunities($date_range); |
| 393 | 334 | |
| @@ -410,9 +351,9 @@ | ||
| 410 | 351 | * |
| 411 | 352 | * @param WP_REST_Request $request Request object |
| 412 | 353 | * @return WP_REST_Response|WP_Error Response object |
| 413 | 354 | */ |
| 414 | - public function setup_search_console(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 355 | + public function setup_search_console(WP_REST_Request $request) { | |
| 415 | 356 | try { |
| 416 | 357 | $site_url = $request->get_param('site_url'); |
| 417 | 358 | $setup_result = $this->analytics_manager->setup_search_console_verification($site_url); |
| 418 | 359 | |
| @@ -430,38 +371,14 @@ | ||
| 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): WP_REST_Response|WP_Error { | |
| 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 |
| 462 | 379 | */ |
| 463 | - public function refresh_data(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 380 | + public function refresh_data(WP_REST_Request $request) { | |
| 464 | 381 | try { |
| 465 | 382 | $refresh_result = $this->analytics_manager->refresh_data(); |
| 466 | 383 | |
| 467 | 384 | // Bust the cached Search Console passthrough responses (search-totals, |
| @@ -487,9 +404,9 @@ | ||
| 487 | 404 | * |
| 488 | 405 | * @param WP_REST_Request $request Request object |
| 489 | 406 | * @return WP_REST_Response|WP_Error Response object |
| 490 | 407 | */ |
| 491 | - public function get_client_status(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 408 | + public function get_client_status(WP_REST_Request $request) { | |
| 492 | 409 | try { |
| 493 | 410 | $client_status = $this->analytics_manager->get_client_status(); |
| 494 | 411 | |
| 495 | 412 | return new WP_REST_Response([ |
| @@ -564,9 +481,21 @@ | ||
| 564 | 481 | * |
| 565 | 482 | * @param string $site_url Site URL to validate |
| 566 | 483 | * @return bool|WP_Error Validation result |
| 567 | 484 | */ |
| 568 | - public function validate_site_url(string $site_url): bool|WP_Error { | |
| 485 | + public function validate_site_url($site_url) { | |
| 486 | + // Not a `string` type hint: this is a validate_callback, so it runs on | |
| 487 | + // the raw pre-sanitize parameter. `?site_url[]=x` handed it an array | |
| 488 | + // and PHP raised an uncaught TypeError — a 500 where the API owes the | |
| 489 | + // caller a 400 (#394). | |
| 490 | + if (!is_string($site_url)) { | |
| 491 | + return new WP_Error( | |
| 492 | + 'invalid_site_url', | |
| 493 | + 'Site URL must be a string', | |
| 494 | + ['status' => 400] | |
| 495 | + ); | |
| 496 | + } | |
| 497 | + | |
| 569 | 498 | if (empty($site_url)) { |
| 570 | 499 | return new WP_Error( |
| 571 | 500 | 'invalid_site_url', |
| 572 | 501 | 'Site URL is required', |
| @@ -590,9 +519,9 @@ | ||
| 590 | 519 | * |
| 591 | 520 | * @param WP_REST_Request $request Request object |
| 592 | 521 | * @return WP_REST_Response|WP_Error Response object |
| 593 | 522 | */ |
| 594 | - public function get_search_totals(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 523 | + public function get_search_totals(WP_REST_Request $request) { | |
| 595 | 524 | try { |
| 596 | 525 | $start_date = $request->get_param('start_date'); |
| 597 | 526 | $end_date = $request->get_param('end_date'); |
| 598 | 527 | |
| @@ -643,9 +572,9 @@ | ||
| 643 | 572 | * |
| 644 | 573 | * @param WP_REST_Request $request Request object |
| 645 | 574 | * @return WP_REST_Response|WP_Error Response object |
| 646 | 575 | */ |
| 647 | - public function get_search_daily(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 576 | + public function get_search_daily(WP_REST_Request $request) { | |
| 648 | 577 | try { |
| 649 | 578 | $date_range = $request->get_param('date_range') ?: '30d'; |
| 650 | 579 | $days = (int) preg_replace('/[^0-9]/', '', $date_range); |
| 651 | 580 | if ($days <= 0 || $days > 90) { |
| @@ -737,9 +666,9 @@ | ||
| 737 | 666 | * |
| 738 | 667 | * @param WP_REST_Request $request Request object |
| 739 | 668 | * @return WP_REST_Response|WP_Error |
| 740 | 669 | */ |
| 741 | - public function get_branded(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 670 | + public function get_branded(WP_REST_Request $request) { | |
| 742 | 671 | try { |
| 743 | 672 | $date_range = $request->get_param('date_range') ?: '30d'; |
| 744 | 673 | $brand_name = $request->get_param('brand_name') ?: ''; |
| 745 | 674 | |
| @@ -777,9 +706,9 @@ | ||
| 777 | 706 | * |
| 778 | 707 | * @param WP_REST_Request $request Request object |
| 779 | 708 | * @return WP_REST_Response|WP_Error Response object |
| 780 | 709 | */ |
| 781 | - public function get_countries(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 710 | + public function get_countries(WP_REST_Request $request) { | |
| 782 | 711 | try { |
| 783 | 712 | $date_range = $request->get_param('date_range') ?: '30d'; |
| 784 | 713 | |
| 785 | 714 | $context = $this->resolve_search_console(); |
| @@ -818,9 +747,9 @@ | ||
| 818 | 747 | * |
| 819 | 748 | * @since 1.0.0 |
| 820 | 749 | * @return array{0: \ThinkRank\Integrations\Google_Search_Console_Client, 1: string}|WP_Error |
| 821 | 750 | */ |
| 822 | - private function resolve_search_console(): array|WP_Error { | |
| 751 | + private function resolve_search_console() { | |
| 823 | 752 | $client = $this->analytics_manager->get_search_console_client(); |
| 824 | 753 | |
| 825 | 754 | if (!$client) { |
| 826 | 755 | return new WP_Error( |
| @@ -925,9 +854,9 @@ | ||
| 925 | 854 | * gated so the feature can always be (re-)enabled. |
| 926 | 855 | * |
| 927 | 856 | * @return bool|WP_Error True when allowed, false or WP_Error otherwise |
| 928 | 857 | */ |
| 929 | - public function check_data_permissions(): bool|WP_Error { | |
| 858 | + public function check_data_permissions() { | |
| 930 | 859 | if (!$this->check_permissions()) { |
| 931 | 860 | return false; |
| 932 | 861 | } |
| 933 | 862 | |
| @@ -939,127 +868,6 @@ | ||
| 939 | 868 | ); |
| 940 | 869 | } |
| 941 | 870 | |
| 942 | 871 | return true; |
| 943 | - } | |
| 944 | - | |
| 945 | - // ======================================== | |
| 946 | - // SEO Intelligence Enhancement Endpoints | |
| 947 | - // ======================================== | |
| 948 | - | |
| 949 | - /** | |
| 950 | - * Get intelligent dashboard data with trends and insights | |
| 951 | - * | |
| 952 | - * @param WP_REST_Request $request Request object | |
| 953 | - * @return WP_REST_Response|WP_Error Response object | |
| 954 | - */ | |
| 955 | - public function get_intelligent_dashboard(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 956 | - try { | |
| 957 | - $date_range = $request->get_param('date_range'); | |
| 958 | - | |
| 959 | - // Cache the intelligence computation (trend analysis + generators are | |
| 960 | - // expensive) so the AI-insights panel doesn't recompute every load. | |
| 961 | - $response = $this->cached_response( | |
| 962 | - 'intelligent_dashboard', | |
| 963 | - function () use ($date_range) { | |
| 964 | - $intelligent_data = $this->analytics_manager->get_intelligent_dashboard_data($date_range); | |
| 965 | - | |
| 966 | - return [ | |
| 967 | - 'success' => isset($intelligent_data['success']) ? $intelligent_data['success'] : false, | |
| 968 | - 'data' => $intelligent_data['data'] ?? null, | |
| 969 | - 'message' => $intelligent_data['message'] ?? 'Intelligent dashboard data retrieved', | |
| 970 | - 'timestamp' => current_time('mysql'), | |
| 971 | - ]; | |
| 972 | - }, | |
| 973 | - ['date_range' => $date_range] | |
| 974 | - ); | |
| 975 | - | |
| 976 | - // Always return 200 for successful API calls, even when no data available. | |
| 977 | - return new WP_REST_Response($response, 200); | |
| 978 | - | |
| 979 | - } catch (\Exception $e) { | |
| 980 | - return new WP_Error( | |
| 981 | - 'intelligent_dashboard_error', | |
| 982 | - 'Failed to retrieve intelligent dashboard data: ' . $e->getMessage(), | |
| 983 | - ['status' => 500] | |
| 984 | - ); | |
| 985 | - } | |
| 986 | - } | |
| 987 | - | |
| 988 | - /** | |
| 989 | - * Get intelligent SEO opportunities with prioritization | |
| 990 | - * | |
| 991 | - * @param WP_REST_Request $request Request object | |
| 992 | - * @return WP_REST_Response|WP_Error Response object | |
| 993 | - */ | |
| 994 | - public function get_intelligent_opportunities(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 995 | - try { | |
| 996 | - $date_range = $request->get_param('date_range'); | |
| 997 | - | |
| 998 | - // Cache the opportunity detection so it doesn't recompute every load. | |
| 999 | - $response = $this->cached_response( | |
| 1000 | - 'intelligent_opportunities', | |
| 1001 | - function () use ($date_range) { | |
| 1002 | - $intelligent_opportunities = $this->analytics_manager->get_intelligent_seo_opportunities($date_range); | |
| 1003 | - | |
| 1004 | - return [ | |
| 1005 | - 'success' => isset($intelligent_opportunities['success']) ? $intelligent_opportunities['success'] : false, | |
| 1006 | - 'data' => $intelligent_opportunities['data'] ?? null, | |
| 1007 | - 'message' => $intelligent_opportunities['message'] ?? 'Intelligent opportunities retrieved', | |
| 1008 | - 'timestamp' => current_time('mysql'), | |
| 1009 | - ]; | |
| 1010 | - }, | |
| 1011 | - ['date_range' => $date_range] | |
| 1012 | - ); | |
| 1013 | - | |
| 1014 | - // Always return 200 for successful API calls, even when no data available. | |
| 1015 | - return new WP_REST_Response($response, 200); | |
| 1016 | - | |
| 1017 | - } catch (\Exception $e) { | |
| 1018 | - return new WP_Error( | |
| 1019 | - 'intelligent_opportunities_error', | |
| 1020 | - 'Failed to retrieve intelligent opportunities: ' . $e->getMessage(), | |
| 1021 | - ['status' => 500] | |
| 1022 | - ); | |
| 1023 | - } | |
| 1024 | - } | |
| 1025 | - | |
| 1026 | - /** | |
| 1027 | - * Get SEO insights | |
| 1028 | - * | |
| 1029 | - * @param WP_REST_Request $request Request object | |
| 1030 | - * @return WP_REST_Response|WP_Error Response object | |
| 1031 | - */ | |
| 1032 | - public function get_seo_insights(WP_REST_Request $request): WP_REST_Response|WP_Error { | |
| 1033 | - try { | |
| 1034 | - $date_range = $request->get_param('date_range'); | |
| 1035 | - | |
| 1036 | - // Endpoint-level cache for consistency with the other two intelligence | |
| 1037 | - // calls (the manager also caches insights internally). | |
| 1038 | - $response = $this->cached_response( | |
| 1039 | - 'seo_insights', | |
| 1040 | - function () use ($date_range) { | |
| 1041 | - $insights = $this->analytics_manager->get_seo_insights($date_range); | |
| 1042 | - | |
| 1043 | - return [ | |
| 1044 | - 'success' => isset($insights['success']) ? $insights['success'] : false, | |
| 1045 | - 'data' => $insights['data'] ?? null, | |
| 1046 | - 'cached' => $insights['cached'] ?? false, | |
| 1047 | - 'message' => $insights['message'] ?? 'SEO insights retrieved', | |
| 1048 | - 'timestamp' => current_time('mysql'), | |
| 1049 | - ]; | |
| 1050 | - }, | |
| 1051 | - ['date_range' => $date_range] | |
| 1052 | - ); | |
| 1053 | - | |
| 1054 | - // Always return 200 for successful API calls, even when no data available. | |
| 1055 | - return new WP_REST_Response($response, 200); | |
| 1056 | - | |
| 1057 | - } catch (\Exception $e) { | |
| 1058 | - return new WP_Error( | |
| 1059 | - 'seo_insights_error', | |
| 1060 | - 'Failed to retrieve SEO insights: ' . $e->getMessage(), | |
| 1061 | - ['status' => 500] | |
| 1062 | - ); | |
| 1063 | - } | |
| 1064 | 872 | } |
| 1065 | 873 | } |