PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.10.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 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / api / class-seo-analytics-endpoint.php

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

886 lines 30.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * SEO Analytics API Endpoints Class
5 *
6 * REST API endpoints for SEO analytics data collection, Google API integration,
7 * and AI-powered insights generation. Provides comprehensive API access to
8 * Analytics Manager functionality with proper authentication, validation,
9 * and error handling.
10 *
11 * @package ThinkRank
12 * @subpackage API
13 * @since 1.0.0
14 */
15
16 declare(strict_types=1);
17
18 namespace ThinkRank\API;
19
20 use ThinkRank\SEO\Analytics_Manager;
21 use WP_REST_Controller;
22 use WP_REST_Request;
23 use WP_REST_Response;
24 use WP_Error;
25
26 // Prevent direct access
27 if (!defined('ABSPATH')) {
28 exit;
29 }
30
31 /**
32 * SEO Analytics API Endpoints Class
33 *
34 * Provides REST API endpoints for SEO analytics operations including
35 * Google API integration, dashboard data retrieval, SEO opportunities
36 * analysis, and connection management with proper authentication and validation.
37 *
38 * @since 1.0.0
39 */
40 class SEO_Analytics_Endpoint extends WP_REST_Controller {
41
42 /**
43 * Analytics Manager instance
44 *
45 * @since 1.0.0
46 * @var Analytics_Manager
47 */
48 private Analytics_Manager $analytics_manager;
49
50 /**
51 * API namespace
52 *
53 * @since 1.0.0
54 * @var string
55 */
56 protected $namespace = 'thinkrank/v1';
57
58 /**
59 * API resource base
60 *
61 * @since 1.0.0
62 * @var string
63 */
64 protected $rest_base = 'seo-analytics';
65
66 /**
67 * Constructor
68 *
69 * @since 1.0.0
70 * @param Analytics_Manager|null $analytics_manager Analytics manager instance
71 */
72 public function __construct(?Analytics_Manager $analytics_manager = null) {
73 $this->analytics_manager = $analytics_manager ?? new Analytics_Manager();
74 }
75
76 /**
77 * Register API routes
78 * Following ThinkRank endpoint registration patterns
79 *
80 * @since 1.0.0
81 */
82 public function register_routes(): void {
83 // Test Google API connections
84 register_rest_route(
85 $this->namespace,
86 '/' . $this->rest_base . '/test-connections',
87 [
88 [
89 'methods' => 'GET',
90 'callback' => [$this, 'test_connections'],
91 'permission_callback' => [$this, 'check_permissions'],
92 ]
93 ]
94 );
95
96 // Get dashboard data
97 register_rest_route(
98 $this->namespace,
99 '/' . $this->rest_base . '/dashboard',
100 [
101 [
102 'methods' => 'GET',
103 'callback' => [$this, 'get_dashboard_data'],
104 'permission_callback' => [$this, 'check_permissions'],
105 'args' => $this->get_dashboard_args()
106 ]
107 ]
108 );
109
110 // Get SEO opportunities
111 register_rest_route(
112 $this->namespace,
113 '/' . $this->rest_base . '/opportunities',
114 [
115 [
116 'methods' => 'GET',
117 'callback' => [$this, 'get_seo_opportunities'],
118 'permission_callback' => [$this, 'check_permissions'],
119 'args' => $this->get_opportunities_args()
120 ]
121 ]
122 );
123
124 // Setup Search Console verification
125 register_rest_route(
126 $this->namespace,
127 '/' . $this->rest_base . '/setup/search-console',
128 [
129 [
130 'methods' => 'POST',
131 'callback' => [$this, 'setup_search_console'],
132 'permission_callback' => [$this, 'check_permissions'],
133 'args' => $this->get_setup_args()
134 ]
135 ]
136 );
137
138 // Get indexing status
139 register_rest_route(
140 $this->namespace,
141 '/' . $this->rest_base . '/indexing-status',
142 [
143 [
144 'methods' => 'GET',
145 'callback' => [$this, 'get_indexing_status'],
146 'permission_callback' => [$this, 'check_permissions'],
147 ]
148 ]
149 );
150
151 // Refresh cached data
152 register_rest_route(
153 $this->namespace,
154 '/' . $this->rest_base . '/refresh',
155 [
156 [
157 'methods' => 'POST',
158 'callback' => [$this, 'refresh_data'],
159 'permission_callback' => [$this, 'check_permissions'],
160 ]
161 ]
162 );
163
164 // Get client status (for debugging)
165 register_rest_route(
166 $this->namespace,
167 '/' . $this->rest_base . '/status',
168 [
169 [
170 'methods' => 'GET',
171 'callback' => [$this, 'get_client_status'],
172 'permission_callback' => [$this, 'check_permissions'],
173 ]
174 ]
175 );
176
177 // ========================================
178 // SEO Intelligence Enhancement Endpoints
179 // ========================================
180
181 // Get intelligent dashboard data with trends and insights
182 register_rest_route(
183 $this->namespace,
184 '/' . $this->rest_base . '/intelligent-dashboard',
185 [
186 [
187 'methods' => 'GET',
188 'callback' => [$this, 'get_intelligent_dashboard'],
189 'permission_callback' => [$this, 'check_permissions'],
190 'args' => $this->get_dashboard_args()
191 ]
192 ]
193 );
194
195 // Get intelligent SEO opportunities with prioritization
196 register_rest_route(
197 $this->namespace,
198 '/' . $this->rest_base . '/intelligent-opportunities',
199 [
200 [
201 'methods' => 'GET',
202 'callback' => [$this, 'get_intelligent_opportunities'],
203 'permission_callback' => [$this, 'check_permissions'],
204 'args' => $this->get_opportunities_args()
205 ]
206 ]
207 );
208
209 // Get SEO insights
210 register_rest_route(
211 $this->namespace,
212 '/' . $this->rest_base . '/insights',
213 [
214 [
215 'methods' => 'GET',
216 'callback' => [$this, 'get_seo_insights'],
217 'permission_callback' => [$this, 'check_permissions'],
218 'args' => $this->get_dashboard_args()
219 ]
220 ]
221 );
222
223 // Get Search Console totals for custom date range
224 register_rest_route(
225 $this->namespace,
226 '/' . $this->rest_base . '/search-totals',
227 [
228 [
229 'methods' => 'GET',
230 'callback' => [$this, 'get_search_totals'],
231 'permission_callback' => [$this, 'check_permissions'],
232 'args' => [
233 'start_date' => [
234 'required' => true,
235 'type' => 'string',
236 'sanitize_callback' => 'sanitize_text_field',
237 'description' => 'Start date (Y-m-d)',
238 ],
239 'end_date' => [
240 'required' => true,
241 'type' => 'string',
242 'sanitize_callback' => 'sanitize_text_field',
243 'description' => 'End date (Y-m-d)',
244 ],
245 ],
246 ]
247 ]
248 );
249
250 // Get daily Search Console data (by date dimension) for chart rendering
251 register_rest_route(
252 $this->namespace,
253 '/' . $this->rest_base . '/search-daily',
254 [
255 [
256 'methods' => 'GET',
257 'callback' => [$this, 'get_search_daily'],
258 'permission_callback' => [$this, 'check_permissions'],
259 'args' => [
260 'date_range' => [
261 'required' => false,
262 'type' => 'string',
263 'default' => '30d',
264 'sanitize_callback' => 'sanitize_text_field',
265 'description' => 'Date range: 7d, 30d, or 90d',
266 ],
267 ],
268 ]
269 ]
270 );
271
272 // Get branded vs non-branded breakdown from Search Console
273 register_rest_route(
274 $this->namespace,
275 '/' . $this->rest_base . '/branded',
276 [
277 [
278 'methods' => 'GET',
279 'callback' => [$this, 'get_branded'],
280 'permission_callback' => [$this, 'check_permissions'],
281 'args' => [
282 'date_range' => [
283 'required' => false,
284 'type' => 'string',
285 'default' => '30d',
286 'sanitize_callback' => 'sanitize_text_field',
287 ],
288 'brand_name' => [
289 'required' => false,
290 'type' => 'string',
291 'default' => '',
292 'sanitize_callback' => 'sanitize_text_field',
293 ],
294 ],
295 ]
296 ]
297 );
298
299 // Get top countries from Search Console
300 register_rest_route(
301 $this->namespace,
302 '/' . $this->rest_base . '/countries',
303 [
304 [
305 'methods' => 'GET',
306 'callback' => [$this, 'get_countries'],
307 'permission_callback' => [$this, 'check_permissions'],
308 'args' => [
309 'date_range' => [
310 'required' => false,
311 'type' => 'string',
312 'default' => '30d',
313 'sanitize_callback' => 'sanitize_text_field',
314 'description' => 'Date range: 7d, 30d, or 90d',
315 ],
316 ],
317 ]
318 ]
319 );
320 }
321
322 /**
323 * Test Google API connections
324 * Following ThinkRank response patterns
325 *
326 * @param WP_REST_Request $request Request object
327 * @return WP_REST_Response|WP_Error Response object
328 */
329 public function test_connections(WP_REST_Request $request): WP_REST_Response|WP_Error {
330 try {
331 $connection_results = $this->analytics_manager->test_connections();
332
333 return new WP_REST_Response([
334 'success' => true,
335 'data' => $connection_results,
336 'message' => 'Connection tests completed'
337 ], 200);
338 } catch (\Exception $e) {
339 return new WP_Error(
340 'connection_test_failed',
341 'Connection test failed: ' . $e->getMessage(),
342 ['status' => 500]
343 );
344 }
345 }
346
347 /**
348 * Get analytics dashboard data
349 *
350 * @param WP_REST_Request $request Request object
351 * @return WP_REST_Response|WP_Error Response object
352 */
353 public function get_dashboard_data(WP_REST_Request $request): WP_REST_Response|WP_Error {
354 try {
355 $date_range = $request->get_param('date_range');
356 $dashboard_data = $this->analytics_manager->get_dashboard_data($date_range);
357
358 return new WP_REST_Response([
359 'success' => true,
360 'data' => $dashboard_data,
361 'message' => 'Dashboard data retrieved successfully'
362 ], 200);
363 } catch (\Exception $e) {
364 return new WP_Error(
365 'dashboard_data_failed',
366 'Failed to retrieve dashboard data: ' . $e->getMessage(),
367 ['status' => 500]
368 );
369 }
370 }
371
372 /**
373 * Get SEO opportunities
374 *
375 * @param WP_REST_Request $request Request object
376 * @return WP_REST_Response|WP_Error Response object
377 */
378 public function get_seo_opportunities(WP_REST_Request $request): WP_REST_Response|WP_Error {
379 try {
380 $date_range = $request->get_param('date_range');
381 $opportunities = $this->analytics_manager->get_seo_opportunities($date_range);
382
383 return new WP_REST_Response([
384 'success' => true,
385 'data' => $opportunities,
386 'message' => 'SEO opportunities retrieved successfully'
387 ], 200);
388 } catch (\Exception $e) {
389 return new WP_Error(
390 'opportunities_failed',
391 'Failed to retrieve SEO opportunities: ' . $e->getMessage(),
392 ['status' => 500]
393 );
394 }
395 }
396
397 /**
398 * Setup Search Console verification
399 *
400 * @param WP_REST_Request $request Request object
401 * @return WP_REST_Response|WP_Error Response object
402 */
403 public function setup_search_console(WP_REST_Request $request): WP_REST_Response|WP_Error {
404 try {
405 $site_url = $request->get_param('site_url');
406 $setup_result = $this->analytics_manager->setup_search_console_verification($site_url);
407
408 return new WP_REST_Response([
409 'success' => $setup_result['success'],
410 'data' => $setup_result,
411 'message' => $setup_result['message']
412 ], $setup_result['success'] ? 200 : 400);
413 } catch (\Exception $e) {
414 return new WP_Error(
415 'setup_failed',
416 'Search Console setup failed: ' . $e->getMessage(),
417 ['status' => 500]
418 );
419 }
420 }
421
422 /**
423 * Get indexing status
424 *
425 * @param WP_REST_Request $request Request object
426 * @return WP_REST_Response|WP_Error Response object
427 */
428 public function get_indexing_status(WP_REST_Request $request): WP_REST_Response|WP_Error {
429 try {
430 $indexing_status = $this->analytics_manager->get_indexing_status();
431
432 return new WP_REST_Response([
433 'success' => true,
434 'data' => $indexing_status,
435 'message' => 'Indexing status retrieved successfully'
436 ], 200);
437 } catch (\Exception $e) {
438 return new WP_Error(
439 'indexing_status_failed',
440 'Failed to retrieve indexing status: ' . $e->getMessage(),
441 ['status' => 500]
442 );
443 }
444 }
445
446 /**
447 * Refresh cached analytics data
448 *
449 * @param WP_REST_Request $request Request object
450 * @return WP_REST_Response|WP_Error Response object
451 */
452 public function refresh_data(WP_REST_Request $request): WP_REST_Response|WP_Error {
453 try {
454 $refresh_result = $this->analytics_manager->refresh_data();
455
456 return new WP_REST_Response([
457 'success' => $refresh_result['success'],
458 'data' => $refresh_result,
459 'message' => $refresh_result['message']
460 ], 200);
461 } catch (\Exception $e) {
462 return new WP_Error(
463 'refresh_failed',
464 'Failed to refresh data: ' . $e->getMessage(),
465 ['status' => 500]
466 );
467 }
468 }
469
470 /**
471 * Get client status for debugging
472 *
473 * @param WP_REST_Request $request Request object
474 * @return WP_REST_Response|WP_Error Response object
475 */
476 public function get_client_status(WP_REST_Request $request): WP_REST_Response|WP_Error {
477 try {
478 $client_status = $this->analytics_manager->get_client_status();
479
480 return new WP_REST_Response([
481 'success' => true,
482 'data' => $client_status,
483 'message' => 'Client status retrieved successfully'
484 ], 200);
485 } catch (\Exception $e) {
486 return new WP_Error(
487 'status_failed',
488 'Failed to retrieve client status: ' . $e->getMessage(),
489 ['status' => 500]
490 );
491 }
492 }
493
494 /**
495 * Get dashboard endpoint arguments
496 * Following ThinkRank argument validation patterns
497 *
498 * @return array Endpoint arguments
499 */
500 private function get_dashboard_args(): array {
501 return [
502 'date_range' => [
503 'type' => 'string',
504 'default' => '30d',
505 'enum' => ['7d', '30d', '90d'],
506 'sanitize_callback' => 'sanitize_key',
507 'description' => 'Date range for analytics data'
508 ]
509 ];
510 }
511
512 /**
513 * Get opportunities endpoint arguments
514 *
515 * @return array Endpoint arguments
516 */
517 private function get_opportunities_args(): array {
518 return [
519 'date_range' => [
520 'type' => 'string',
521 'default' => '30d',
522 'enum' => ['7d', '30d', '90d'],
523 'sanitize_callback' => 'sanitize_key',
524 'description' => 'Date range for opportunities analysis'
525 ]
526 ];
527 }
528
529 /**
530 * Get setup endpoint arguments
531 *
532 * @return array Endpoint arguments
533 */
534 private function get_setup_args(): array {
535 return [
536 'site_url' => [
537 'required' => true,
538 'type' => 'string',
539 'sanitize_callback' => 'esc_url_raw',
540 'validate_callback' => [$this, 'validate_site_url'],
541 'description' => 'Site URL to verify in Search Console'
542 ]
543 ];
544 }
545
546 /**
547 * Validate site URL parameter
548 * Following ThinkRank validation patterns
549 *
550 * @param string $site_url Site URL to validate
551 * @return bool|WP_Error Validation result
552 */
553 public function validate_site_url(string $site_url): bool|WP_Error {
554 if (empty($site_url)) {
555 return new WP_Error(
556 'invalid_site_url',
557 'Site URL is required',
558 ['status' => 400]
559 );
560 }
561
562 if (!filter_var($site_url, FILTER_VALIDATE_URL)) {
563 return new WP_Error(
564 'invalid_site_url',
565 'Site URL must be a valid URL',
566 ['status' => 400]
567 );
568 }
569
570 return true;
571 }
572
573 /**
574 * Get Search Console totals for a custom date range
575 *
576 * @param WP_REST_Request $request Request object
577 * @return WP_REST_Response|WP_Error Response object
578 */
579 public function get_search_totals(WP_REST_Request $request): WP_REST_Response|WP_Error {
580 try {
581 $start_date = $request->get_param('start_date');
582 $end_date = $request->get_param('end_date');
583
584 // Validate date format and actual calendar validity
585 $start_dt = \DateTime::createFromFormat('Y-m-d', $start_date);
586 $end_dt = \DateTime::createFromFormat('Y-m-d', $end_date);
587 if (
588 !$start_dt || $start_dt->format('Y-m-d') !== $start_date ||
589 !$end_dt || $end_dt->format('Y-m-d') !== $end_date
590 ) {
591 return new WP_Error('invalid_dates', 'Dates must be valid calendar dates in Y-m-d format', ['status' => 400]);
592 }
593 if ($start_dt > $end_dt) {
594 return new WP_Error('invalid_dates', 'start_date must not be after end_date', ['status' => 400]);
595 }
596
597 // Use Analytics Manager to access the initialized client with decrypted credentials
598 $search_console = $this->analytics_manager->get_search_console_client();
599 $site_url = $this->analytics_manager->get_property_url();
600
601 if (!$search_console) {
602 return new WP_Error('no_client', 'Search Console client not available', ['status' => 500]);
603 }
604
605 $totals = $search_console->get_search_totals_by_dates($site_url, $start_date, $end_date);
606
607 return new WP_REST_Response([
608 'success' => true,
609 'data' => $totals,
610 'message' => 'Search totals retrieved',
611 ], 200);
612 } catch (\Exception $e) {
613 return new WP_Error(
614 'search_totals_failed',
615 'Failed to retrieve search totals: ' . $e->getMessage(),
616 ['status' => 500]
617 );
618 }
619 }
620
621 /**
622 * Get daily Search Console data grouped by date for chart rendering.
623 *
624 * Returns rows sorted ascending by date, each containing:
625 * clicks, impressions, ctr (as %), position.
626 *
627 * @param WP_REST_Request $request Request object
628 * @return WP_REST_Response|WP_Error Response object
629 */
630 public function get_search_daily(WP_REST_Request $request): WP_REST_Response|WP_Error {
631 try {
632 $date_range = $request->get_param('date_range') ?: '30d';
633 $days = (int) preg_replace('/[^0-9]/', '', $date_range);
634 if ($days <= 0 || $days > 90) {
635 $days = 30;
636 }
637
638 // Window = exactly $days back from today (inclusive of today).
639 // 7d → today-6 ... today
640 // 30d → today-29 ... today
641 // 90d → today-89 ... today
642 $end_date = gmdate('Y-m-d');
643 $start_date = gmdate('Y-m-d', strtotime('-' . ($days - 1) . ' days'));
644
645 $search_console = $this->analytics_manager->get_search_console_client();
646 $site_url = $this->analytics_manager->get_property_url();
647
648 if (!$search_console) {
649 return new WP_Error('no_client', 'Search Console client not available', ['status' => 500]);
650 }
651
652 $raw_rows = $search_console->get_search_performance_by_dates(
653 $site_url,
654 $start_date,
655 $end_date,
656 $days + 5,
657 ['date']
658 );
659
660 // Index GSC rows by date so we can pad missing days (GSC's lag means
661 // the most recent few days often have no data yet).
662 $by_date = [];
663 foreach ($raw_rows as $row) {
664 $date = $row['keys'][0] ?? '';
665 if (!$date) {
666 continue;
667 }
668 $by_date[$date] = [
669 'clicks' => (int) ($row['clicks'] ?? 0),
670 'impressions' => (int) ($row['impressions'] ?? 0),
671 'ctr' => round(($row['ctr'] ?? 0) * 100, 2),
672 'position' => round($row['position'] ?? 0, 1),
673 ];
674 }
675
676 // Build a contiguous N-day series from $start_date → $end_date.
677 // Days GSC has no data for (today minus 2-4 days, typically) come
678 // through as zeros so the chart x-axis always spans the full window.
679 $rows = [];
680 $cursor = strtotime($start_date);
681 $end_ts = strtotime($end_date);
682 while ($cursor <= $end_ts) {
683 $date = gmdate('Y-m-d', $cursor);
684 $rows[] = array_merge(
685 ['date' => $date],
686 $by_date[$date] ?? ['clicks' => 0, 'impressions' => 0, 'ctr' => 0, 'position' => 0]
687 );
688 $cursor = strtotime('+1 day', $cursor);
689 }
690
691 return new WP_REST_Response([
692 'success' => true,
693 'data' => [
694 'rows' => $rows,
695 'start_date' => $start_date,
696 'end_date' => $end_date,
697 ],
698 'message' => 'Daily search data retrieved',
699 ], 200);
700 } catch (\Exception $e) {
701 return new WP_Error(
702 'search_daily_failed',
703 'Failed to retrieve daily search data: ' . $e->getMessage(),
704 ['status' => 500]
705 );
706 }
707 }
708
709 /**
710 * Get branded vs non-branded query breakdown from Search Console.
711 *
712 * Accepts optional `brand_name` param (comma-separated keywords).
713 * When omitted the brand is auto-derived from the registered domain.
714 * Also returns the equivalent previous-period data so the frontend can
715 * compute trend arrows without a second round-trip.
716 *
717 * @param WP_REST_Request $request Request object
718 * @return WP_REST_Response|WP_Error
719 */
720 public function get_branded(WP_REST_Request $request): WP_REST_Response|WP_Error {
721 try {
722 $date_range = $request->get_param('date_range') ?: '30d';
723 $brand_name = $request->get_param('brand_name') ?: '';
724
725 $search_console = $this->analytics_manager->get_search_console_client();
726 $site_url = $this->analytics_manager->get_property_url();
727
728 if (!$search_console) {
729 return new WP_Error('no_client', 'Search Console client not available', ['status' => 500]);
730 }
731
732 $data = $search_console->get_branded_performance($site_url, $date_range, $brand_name);
733
734 return new WP_REST_Response([
735 'success' => true,
736 'data' => $data,
737 'message' => 'Branded data retrieved',
738 ], 200);
739 } catch (\Exception $e) {
740 return new WP_Error(
741 'branded_failed',
742 'Failed to retrieve branded data: ' . $e->getMessage(),
743 ['status' => 500]
744 );
745 }
746 }
747
748 /**
749 * Get top countries from Search Console (country dimension).
750 *
751 * Returns up to 10 countries sorted by clicks descending, each with
752 * clicks, impressions, ctr, position, and a percentage share of total clicks.
753 *
754 * @param WP_REST_Request $request Request object
755 * @return WP_REST_Response|WP_Error Response object
756 */
757 public function get_countries(WP_REST_Request $request): WP_REST_Response|WP_Error {
758 try {
759 $date_range = $request->get_param('date_range') ?: '30d';
760
761 $search_console = $this->analytics_manager->get_search_console_client();
762 $site_url = $this->analytics_manager->get_property_url();
763
764 if (!$search_console) {
765 return new WP_Error('no_client', 'Search Console client not available', ['status' => 500]);
766 }
767
768 $raw_rows = $search_console->get_country_performance($site_url, $date_range);
769
770 return new WP_REST_Response([
771 'success' => true,
772 'data' => $raw_rows,
773 'message' => 'Country data retrieved',
774 ], 200);
775 } catch (\Exception $e) {
776 return new WP_Error(
777 'countries_failed',
778 'Failed to retrieve country data: ' . $e->getMessage(),
779 ['status' => 500]
780 );
781 }
782 }
783
784 /**
785 * Check permissions for API access
786 * Following ThinkRank permission patterns
787 *
788 * @return bool Permission status
789 */
790 public function check_permissions(): bool {
791 return current_user_can('manage_options');
792 }
793
794 // ========================================
795 // SEO Intelligence Enhancement Endpoints
796 // ========================================
797
798 /**
799 * Get intelligent dashboard data with trends and insights
800 *
801 * @param WP_REST_Request $request Request object
802 * @return WP_REST_Response|WP_Error Response object
803 */
804 public function get_intelligent_dashboard(WP_REST_Request $request): WP_REST_Response|WP_Error {
805 try {
806 $date_range = $request->get_param('date_range');
807 $intelligent_data = $this->analytics_manager->get_intelligent_dashboard_data($date_range);
808
809 $success = isset($intelligent_data['success']) ? $intelligent_data['success'] : false;
810
811 return new WP_REST_Response([
812 'success' => $success,
813 'data' => $intelligent_data['data'] ?? null,
814 'message' => $intelligent_data['message'] ?? 'Intelligent dashboard data retrieved',
815 'timestamp' => current_time('mysql')
816 ], 200); // Always return 200 for successful API calls, even when no data available
817
818 } catch (Exception $e) {
819 return new WP_Error(
820 'intelligent_dashboard_error',
821 'Failed to retrieve intelligent dashboard data: ' . $e->getMessage(),
822 ['status' => 500]
823 );
824 }
825 }
826
827 /**
828 * Get intelligent SEO opportunities with prioritization
829 *
830 * @param WP_REST_Request $request Request object
831 * @return WP_REST_Response|WP_Error Response object
832 */
833 public function get_intelligent_opportunities(WP_REST_Request $request): WP_REST_Response|WP_Error {
834 try {
835 $date_range = $request->get_param('date_range');
836 $intelligent_opportunities = $this->analytics_manager->get_intelligent_seo_opportunities($date_range);
837
838 $success = isset($intelligent_opportunities['success']) ? $intelligent_opportunities['success'] : false;
839
840 return new WP_REST_Response([
841 'success' => $success,
842 'data' => $intelligent_opportunities['data'] ?? null,
843 'message' => $intelligent_opportunities['message'] ?? 'Intelligent opportunities retrieved',
844 'timestamp' => current_time('mysql')
845 ], 200); // Always return 200 for successful API calls, even when no data available
846
847 } catch (Exception $e) {
848 return new WP_Error(
849 'intelligent_opportunities_error',
850 'Failed to retrieve intelligent opportunities: ' . $e->getMessage(),
851 ['status' => 500]
852 );
853 }
854 }
855
856 /**
857 * Get SEO insights
858 *
859 * @param WP_REST_Request $request Request object
860 * @return WP_REST_Response|WP_Error Response object
861 */
862 public function get_seo_insights(WP_REST_Request $request): WP_REST_Response|WP_Error {
863 try {
864 $date_range = $request->get_param('date_range');
865 $insights = $this->analytics_manager->get_seo_insights($date_range);
866
867 $success = isset($insights['success']) ? $insights['success'] : false;
868
869 return new WP_REST_Response([
870 'success' => $success,
871 'data' => $insights['data'] ?? null,
872 'cached' => $insights['cached'] ?? false,
873 'message' => $insights['message'] ?? 'SEO insights retrieved',
874 'timestamp' => current_time('mysql')
875 ], 200); // Always return 200 for successful API calls, even when no data available
876
877 } catch (Exception $e) {
878 return new WP_Error(
879 'seo_insights_error',
880 'Failed to retrieve SEO insights: ' . $e->getMessage(),
881 ['status' => 500]
882 );
883 }
884 }
885 }
886