PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.1.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.1.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-integrations-endpoint.php

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

858 lines 29.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Integrations API Endpoints Class
4 *
5 * Provides REST API endpoints for Google API integrations management
6 * following the same pattern as Site Identity and Social Media endpoints.
7 *
8 * @package ThinkRank\API
9 * @since 1.0.0
10 */
11
12 declare(strict_types=1);
13
14 namespace ThinkRank\API;
15
16 use WP_REST_Controller;
17 use WP_REST_Request;
18 use WP_REST_Response;
19 use WP_Error;
20 use ThinkRank\Core\Settings;
21
22 // Prevent direct access
23 if (!defined('ABSPATH')) {
24 exit;
25 }
26
27 /**
28 * Integrations API Endpoints Class
29 *
30 * Handles Google API keys and integration settings management
31 * following ThinkRank patterns from working endpoints.
32 *
33 * @since 1.0.0
34 */
35 class Integrations_Endpoint extends WP_REST_Controller {
36
37 /**
38 * API namespace
39 *
40 * @since 1.0.0
41 * @var string
42 */
43 protected $namespace = 'thinkrank/v1';
44
45 /**
46 * REST base
47 *
48 * @since 1.0.0
49 * @var string
50 */
51 protected $rest_base = 'integrations';
52
53 /**
54 * Settings instance
55 *
56 * @since 1.0.0
57 * @var Settings
58 */
59 private Settings $settings;
60
61 /**
62 * Constructor
63 *
64 * @since 1.0.0
65 */
66 public function __construct() {
67 $this->settings = new Settings();
68 }
69
70 /**
71 * Register API routes
72 *
73 * @since 1.0.0
74 */
75 public function register_routes(): void {
76 // Integrations settings management
77 register_rest_route(
78 $this->namespace,
79 '/' . $this->rest_base . '/settings',
80 [
81 [
82 'methods' => 'GET',
83 'callback' => [$this, 'get_settings'],
84 'permission_callback' => [$this, 'check_read_permissions']
85 ],
86 [
87 'methods' => 'POST',
88 'callback' => [$this, 'update_settings'],
89 'permission_callback' => [$this, 'check_manage_permissions'],
90 'args' => $this->get_settings_args()
91 ]
92 ]
93 );
94
95 // Test Google API connections
96 register_rest_route(
97 $this->namespace,
98 '/' . $this->rest_base . '/test-connections',
99 [
100 [
101 'methods' => 'POST',
102 'callback' => [$this, 'test_connections'],
103 'permission_callback' => [$this, 'check_manage_permissions']
104 ]
105 ]
106 );
107
108 // Verify GA4 tracking
109 register_rest_route(
110 $this->namespace,
111 '/' . $this->rest_base . '/verify-ga4-tracking',
112 [
113 [
114 'methods' => 'POST',
115 'callback' => [$this, 'verify_ga4_tracking'],
116 'permission_callback' => [$this, 'check_manage_permissions'],
117 'args' => [
118 'measurement_id' => [
119 'required' => true,
120 'type' => 'string',
121 'pattern' => '/^G-[A-Z0-9]{10}$/',
122 'sanitize_callback' => 'sanitize_text_field',
123 'description' => 'GA4 Measurement ID in format G-XXXXXXXXXX'
124 ]
125 ]
126 ]
127 ]
128 );
129
130 // Detect GA4 conflicts
131 register_rest_route(
132 $this->namespace,
133 '/' . $this->rest_base . '/detect-ga4-conflicts',
134 [
135 [
136 'methods' => 'GET',
137 'callback' => [$this, 'detect_ga4_conflicts'],
138 'permission_callback' => [$this, 'check_read_permissions']
139 ]
140 ]
141 );
142 }
143
144 /**
145 * Get integrations settings
146 *
147 * @since 1.0.0
148 *
149 * @param WP_REST_Request $request Request object
150 * @return WP_REST_Response Response object
151 */
152 public function get_settings(WP_REST_Request $request): WP_REST_Response {
153 try {
154 $settings = $this->get_integrations_settings();
155
156 return new WP_REST_Response([
157 'success' => true,
158 'data' => [
159 'settings' => $settings
160 ],
161 'message' => 'Integrations settings retrieved successfully'
162 ], 200);
163
164 } catch (\Exception $e) {
165 return new WP_REST_Response([
166 'success' => false,
167 'message' => 'Failed to retrieve integrations settings: ' . $e->getMessage()
168 ], 500);
169 }
170 }
171
172 /**
173 * Update integrations settings
174 *
175 * @since 1.0.0
176 *
177 * @param WP_REST_Request $request Request object
178 * @return WP_REST_Response Response object
179 */
180 public function update_settings(WP_REST_Request $request): WP_REST_Response {
181 try {
182 $settings = $request->get_param('settings');
183
184 if (empty($settings) || !is_array($settings)) {
185 return new WP_REST_Response([
186 'success' => false,
187 'message' => 'Invalid settings data provided'
188 ], 400);
189 }
190
191 // Sanitize and save settings
192 $sanitized_settings = $this->sanitize_settings($settings);
193 $success = $this->save_integrations_settings($sanitized_settings);
194
195 if ($success) {
196 return new WP_REST_Response([
197 'success' => true,
198 'data' => [
199 'settings' => $this->get_integrations_settings()
200 ],
201 'message' => 'Integrations settings saved successfully'
202 ], 200);
203 } else {
204 return new WP_REST_Response([
205 'success' => false,
206 'message' => 'Failed to save integrations settings'
207 ], 500);
208 }
209
210 } catch (\Exception $e) {
211 return new WP_REST_Response([
212 'success' => false,
213 'message' => 'Failed to update integrations settings: ' . $e->getMessage()
214 ], 500);
215 }
216 }
217
218 /**
219 * Test Google API connections
220 *
221 * @since 1.0.0
222 *
223 * @param WP_REST_Request $request Request object
224 * @return WP_REST_Response Response object
225 */
226 public function test_connections(WP_REST_Request $request): WP_REST_Response {
227 try {
228 // Get raw, unmasked API keys directly from Settings class for testing
229 $analytics_key = $this->settings->get('google_analytics_api_key');
230 $search_console_key = $this->settings->get('google_search_console_api_key');
231 $pagespeed_key = $this->settings->get('google_pagespeed_api_key');
232
233 $results = [];
234
235 // Test Google Analytics API
236 if (!empty($analytics_key)) {
237 $results['google_analytics'] = $this->test_google_analytics($analytics_key);
238 } else {
239 $results['google_analytics'] = ['status' => 'not_configured', 'message' => 'API key not configured'];
240 }
241
242 // Test Search Console API
243 if (!empty($search_console_key)) {
244 $results['search_console'] = $this->test_search_console($search_console_key);
245 } else {
246 $results['search_console'] = ['status' => 'not_configured', 'message' => 'API key not configured'];
247 }
248
249 // Test PageSpeed API
250 if (!empty($pagespeed_key)) {
251 $results['pagespeed'] = $this->test_pagespeed($pagespeed_key);
252 } else {
253 $results['pagespeed'] = ['status' => 'not_configured', 'message' => 'API key not configured'];
254 }
255
256 return new WP_REST_Response([
257 'success' => true,
258 'data' => $results,
259 'message' => 'Connection tests completed'
260 ], 200);
261
262 } catch (\Exception $e) {
263 return new WP_REST_Response([
264 'success' => false,
265 'message' => 'Connection test failed: ' . $e->getMessage()
266 ], 500);
267 }
268 }
269
270 /**
271 * Get integrations settings from Settings class (with encryption)
272 *
273 * @since 1.0.0
274 * @return array Settings array
275 */
276 private function get_integrations_settings(): array {
277 $settings = [];
278
279 // Get encrypted API keys
280 $settings['google_analytics_api_key'] = $this->settings->get('google_analytics_api_key');
281 $settings['google_search_console_api_key'] = $this->settings->get('google_search_console_api_key');
282 $settings['google_pagespeed_api_key'] = $this->settings->get('google_pagespeed_api_key');
283
284 // Get GA4 tracking settings (let Settings class handle defaults)
285 $settings['ga4_measurement_id'] = $this->settings->get('ga4_measurement_id');
286 $settings['ga4_auto_inject'] = $this->settings->get('ga4_auto_inject');
287 $settings['ga4_anonymize_ip'] = $this->settings->get('ga4_anonymize_ip');
288 $settings['ga4_exclude_admin'] = $this->settings->get('ga4_exclude_admin');
289 $settings['ga4_tracking_verified'] = $this->settings->get('ga4_tracking_verified');
290 $settings['ga4_last_verification'] = $this->settings->get('ga4_last_verification');
291
292 // Get other integration settings (let Settings class handle defaults)
293 $settings['api_timeout'] = $this->settings->get('api_timeout');
294 $settings['enable_rate_limiting'] = $this->settings->get('enable_rate_limiting');
295 $settings['cache_duration'] = $this->settings->get('cache_duration');
296 $settings['auto_test_connections'] = $this->settings->get('auto_test_connections');
297 $settings['retry_failed_requests'] = $this->settings->get('retry_failed_requests');
298
299 // Mask API keys for security (like OpenAI/Claude keys)
300 $settings['google_analytics_api_key'] = $this->mask_api_key($settings['google_analytics_api_key']);
301 $settings['google_search_console_api_key'] = $this->mask_api_key($settings['google_search_console_api_key']);
302 $settings['google_pagespeed_api_key'] = $this->mask_api_key($settings['google_pagespeed_api_key']);
303
304 return $settings;
305 }
306
307 /**
308 * Save integrations settings using Settings class (with encryption)
309 *
310 * @since 1.0.0
311 * @param array $settings Settings to save
312 * @return bool Success status
313 */
314 private function save_integrations_settings(array $settings): bool {
315 $success = true;
316
317 // Save each setting individually using the Settings class
318 // This ensures proper encryption for API keys
319 foreach ($settings as $key => $value) {
320 if (!$this->settings->set($key, $value)) {
321 $success = false;
322 // Setting save failed - error details available through settings manager
323 }
324 }
325
326 return $success;
327 }
328
329 /**
330 * Sanitize settings data
331 *
332 * @since 1.0.0
333 * @param array $settings Raw settings
334 * @return array Sanitized settings
335 */
336 private function sanitize_settings(array $settings): array {
337 $sanitized = [];
338
339 // Sanitize API keys (only if not empty - don't overwrite with empty values)
340 if (!empty($settings['google_analytics_api_key'])) {
341 $sanitized['google_analytics_api_key'] = sanitize_text_field($settings['google_analytics_api_key']);
342 }
343 if (!empty($settings['google_search_console_api_key'])) {
344 $sanitized['google_search_console_api_key'] = sanitize_text_field($settings['google_search_console_api_key']);
345 }
346 if (!empty($settings['google_pagespeed_api_key'])) {
347 $sanitized['google_pagespeed_api_key'] = sanitize_text_field($settings['google_pagespeed_api_key']);
348 }
349
350 // Sanitize numeric settings
351 $sanitized['api_timeout'] = absint($settings['api_timeout'] ?? 30);
352 $sanitized['cache_duration'] = absint($settings['cache_duration'] ?? 3600);
353
354 // Sanitize GA4 tracking settings
355 $sanitized['ga4_measurement_id'] = sanitize_text_field($settings['ga4_measurement_id'] ?? '');
356 $sanitized['ga4_auto_inject'] = isset($settings['ga4_auto_inject']) ? (bool) $settings['ga4_auto_inject'] : false;
357 $sanitized['ga4_anonymize_ip'] = isset($settings['ga4_anonymize_ip']) ? (bool) $settings['ga4_anonymize_ip'] : false;
358 $sanitized['ga4_exclude_admin'] = isset($settings['ga4_exclude_admin']) ? (bool) $settings['ga4_exclude_admin'] : false;
359 $sanitized['ga4_tracking_verified'] = isset($settings['ga4_tracking_verified']) ? (bool) $settings['ga4_tracking_verified'] : false;
360 $sanitized['ga4_last_verification'] = sanitize_text_field($settings['ga4_last_verification'] ?? '');
361
362 // Sanitize boolean settings
363 $sanitized['enable_rate_limiting'] = isset($settings['enable_rate_limiting']) ? (bool) $settings['enable_rate_limiting'] : true;
364 $sanitized['auto_test_connections'] = isset($settings['auto_test_connections']) ? (bool) $settings['auto_test_connections'] : true;
365 $sanitized['retry_failed_requests'] = isset($settings['retry_failed_requests']) ? (bool) $settings['retry_failed_requests'] : true;
366
367 return $sanitized;
368 }
369
370 /**
371 * Mask API key for security display (XXX pattern)
372 *
373 * @since 1.0.0
374 * @param string $api_key API key to mask
375 * @return string Masked API key or empty string
376 */
377 private function mask_api_key(string $api_key): string {
378 if (empty($api_key)) {
379 return '';
380 }
381
382 // Show first 6 characters + XXXX suffix (consistent with placeholders)
383 if (strlen($api_key) > 10) {
384 return substr($api_key, 0, 6) . 'XXXX';
385 }
386
387 return 'XXXX';
388 }
389
390 /**
391 * Test Google Analytics API connection
392 *
393 * Makes a real API call to Google's PageSpeed Insights API to verify
394 * that the API key is valid and has proper permissions.
395 *
396 * @since 1.0.0
397 * @param string $api_key API key to test
398 * @return array Test result with status and message
399 */
400 private function test_google_analytics(string $api_key): array {
401 // Basic format validation
402 if (empty($api_key) || strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
403 return [
404 'status' => 'error',
405 'message' => 'Invalid Google API key format. Key should start with "AIza" and be at least 30 characters.'
406 ];
407 }
408
409 // Make a real API call to test the key
410 // Using PageSpeed Insights API as it uses the same API key and has a simple test endpoint
411 $test_url = add_query_arg([
412 'url' => 'https://example.com/',
413 'key' => $api_key,
414 'category' => 'performance',
415 'strategy' => 'mobile'
416 ], 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed');
417
418 // Get timeout setting from settings or use default
419 $timeout = absint($this->settings->get('api_timeout') ?? 30);
420
421 $response = wp_remote_get($test_url, [
422 'timeout' => $timeout,
423 'headers' => [
424 'Accept' => 'application/json'
425 ],
426 'sslverify' => true
427 ]);
428
429 // Handle network/connection errors
430 if (is_wp_error($response)) {
431 return [
432 'status' => 'error',
433 'message' => 'Connection failed: ' . $response->get_error_message()
434 ];
435 }
436
437 $response_code = wp_remote_retrieve_response_code($response);
438 $response_body = wp_remote_retrieve_body($response);
439
440 // Handle HTTP errors
441 if ($response_code === 400) {
442 $error_data = json_decode($response_body, true);
443 $error_message = $error_data['error']['message'] ?? 'Bad request';
444
445 return [
446 'status' => 'error',
447 'message' => 'API key validation failed: ' . $error_message
448 ];
449 }
450
451 if ($response_code === 403) {
452 $error_data = json_decode($response_body, true);
453 $error_message = $error_data['error']['message'] ?? 'Access forbidden';
454
455 // Check if it's an API key issue
456 if (stripos($error_message, 'API key') !== false || stripos($error_message, 'invalid') !== false) {
457 return [
458 'status' => 'error',
459 'message' => 'Invalid API key or insufficient permissions. Please verify your Google API key.'
460 ];
461 }
462
463 return [
464 'status' => 'error',
465 'message' => 'Access denied: ' . $error_message
466 ];
467 }
468
469 if ($response_code === 429) {
470 return [
471 'status' => 'configured',
472 'message' => 'API rate limit exceeded. The key is valid but you\'ve reached the quota limit.'
473 ];
474 }
475
476 if ($response_code !== 200) {
477 return [
478 'status' => 'error',
479 'message' => 'API request failed with status code: ' . $response_code
480 ];
481 }
482
483 // Validate response body
484 $data = json_decode($response_body, true);
485
486 if (json_last_error() !== JSON_ERROR_NONE) {
487 return [
488 'status' => 'error',
489 'message' => 'Invalid API response format'
490 ];
491 }
492
493 // Check if response has expected structure
494 if (!isset($data['lighthouseResult']) && !isset($data['loadingExperience'])) {
495 return [
496 'status' => 'error',
497 'message' => 'Unexpected API response structure'
498 ];
499 }
500
501 // Success - API key is valid and working
502 return [
503 'status' => 'configured',
504 'message' => 'Google API key is valid and working correctly'
505 ];
506 }
507
508 /**
509 * Test Google Search Console API connection
510 *
511 * Makes a real API call to Google's PageSpeed Insights API to verify
512 * that the API key is valid and has proper permissions.
513 *
514 * @since 1.0.0
515 * @param string $api_key API key to test
516 * @return array Test result with status and message
517 */
518 private function test_search_console(string $api_key): array {
519 // Basic format validation
520 if (empty($api_key) || strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
521 return [
522 'status' => 'error',
523 'message' => 'Invalid Google API key format. Key should start with "AIza" and be at least 30 characters.'
524 ];
525 }
526
527 // Make a real API call to test the key
528 // Using PageSpeed Insights API as it uses the same API key format
529 $test_url = add_query_arg([
530 'url' => 'https://example.com/',
531 'key' => $api_key,
532 'category' => 'seo',
533 'strategy' => 'desktop'
534 ], 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed');
535
536 // Get timeout setting from settings or use default
537 $timeout = absint($this->settings->get('api_timeout') ?? 30);
538
539 $response = wp_remote_get($test_url, [
540 'timeout' => $timeout,
541 'headers' => [
542 'Accept' => 'application/json'
543 ],
544 'sslverify' => true
545 ]);
546
547 // Handle network/connection errors
548 if (is_wp_error($response)) {
549 return [
550 'status' => 'error',
551 'message' => 'Connection failed: ' . $response->get_error_message()
552 ];
553 }
554
555 $response_code = wp_remote_retrieve_response_code($response);
556 $response_body = wp_remote_retrieve_body($response);
557
558 // Handle HTTP errors
559 if ($response_code === 400) {
560 $error_data = json_decode($response_body, true);
561 $error_message = $error_data['error']['message'] ?? 'Bad request';
562
563 return [
564 'status' => 'error',
565 'message' => 'API key validation failed: ' . $error_message
566 ];
567 }
568
569 if ($response_code === 403) {
570 $error_data = json_decode($response_body, true);
571 $error_message = $error_data['error']['message'] ?? 'Access forbidden';
572
573 // Check if it's an API key issue
574 if (stripos($error_message, 'API key') !== false || stripos($error_message, 'invalid') !== false) {
575 return [
576 'status' => 'error',
577 'message' => 'Invalid API key or insufficient permissions. Please verify your Google API key.'
578 ];
579 }
580
581 return [
582 'status' => 'error',
583 'message' => 'Access denied: ' . $error_message
584 ];
585 }
586
587 if ($response_code === 429) {
588 return [
589 'status' => 'configured',
590 'message' => 'API rate limit exceeded. The key is valid but you\'ve reached the quota limit.'
591 ];
592 }
593
594 if ($response_code !== 200) {
595 return [
596 'status' => 'error',
597 'message' => 'API request failed with status code: ' . $response_code
598 ];
599 }
600
601 // Validate response body
602 $data = json_decode($response_body, true);
603
604 if (json_last_error() !== JSON_ERROR_NONE) {
605 return [
606 'status' => 'error',
607 'message' => 'Invalid API response format'
608 ];
609 }
610
611 // Check if response has expected structure
612 if (!isset($data['lighthouseResult']) && !isset($data['loadingExperience'])) {
613 return [
614 'status' => 'error',
615 'message' => 'Unexpected API response structure'
616 ];
617 }
618
619 // Success - API key is valid and working
620 return [
621 'status' => 'configured',
622 'message' => 'Google API key is valid and working correctly'
623 ];
624 }
625
626 /**
627 * Test Google PageSpeed API connection
628 *
629 * Makes a real API call to Google's PageSpeed Insights API to verify
630 * that the API key is valid and has proper permissions.
631 *
632 * @since 1.0.0
633 * @param string $api_key API key to test
634 * @return array Test result with status and message
635 */
636 private function test_pagespeed(string $api_key): array {
637 // Basic format validation
638 if (empty($api_key) || strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
639 return [
640 'status' => 'error',
641 'message' => 'Invalid Google API key format. Key should start with "AIza" and be at least 30 characters.'
642 ];
643 }
644
645 // Make a real API call to test the key
646 $test_url = add_query_arg([
647 'url' => 'https://example.com/',
648 'key' => $api_key,
649 'category' => 'performance',
650 'strategy' => 'mobile'
651 ], 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed');
652
653 // Get timeout setting from settings or use default
654 $timeout = absint($this->settings->get('api_timeout') ?? 30);
655
656 $response = wp_remote_get($test_url, [
657 'timeout' => $timeout,
658 'headers' => [
659 'Accept' => 'application/json'
660 ],
661 'sslverify' => true
662 ]);
663
664 // Handle network/connection errors
665 if (is_wp_error($response)) {
666 return [
667 'status' => 'error',
668 'message' => 'Connection failed: ' . $response->get_error_message()
669 ];
670 }
671
672 $response_code = wp_remote_retrieve_response_code($response);
673 $response_body = wp_remote_retrieve_body($response);
674
675 // Handle HTTP errors
676 if ($response_code === 400) {
677 $error_data = json_decode($response_body, true);
678 $error_message = $error_data['error']['message'] ?? 'Bad request';
679
680 return [
681 'status' => 'error',
682 'message' => 'API key validation failed: ' . $error_message
683 ];
684 }
685
686 if ($response_code === 403) {
687 $error_data = json_decode($response_body, true);
688 $error_message = $error_data['error']['message'] ?? 'Access forbidden';
689
690 // Check if it's an API key issue
691 if (stripos($error_message, 'API key') !== false || stripos($error_message, 'invalid') !== false) {
692 return [
693 'status' => 'error',
694 'message' => 'Invalid API key or insufficient permissions. Please verify your Google API key.'
695 ];
696 }
697
698 return [
699 'status' => 'error',
700 'message' => 'Access denied: ' . $error_message
701 ];
702 }
703
704 if ($response_code === 429) {
705 return [
706 'status' => 'configured',
707 'message' => 'API rate limit exceeded. The key is valid but you\'ve reached the quota limit.'
708 ];
709 }
710
711 if ($response_code !== 200) {
712 return [
713 'status' => 'error',
714 'message' => 'API request failed with status code: ' . $response_code
715 ];
716 }
717
718 // Validate response body
719 $data = json_decode($response_body, true);
720
721 if (json_last_error() !== JSON_ERROR_NONE) {
722 return [
723 'status' => 'error',
724 'message' => 'Invalid API response format'
725 ];
726 }
727
728 // Check if response has expected structure
729 if (!isset($data['lighthouseResult']) && !isset($data['loadingExperience'])) {
730 return [
731 'status' => 'error',
732 'message' => 'Unexpected API response structure'
733 ];
734 }
735
736 // Success - API key is valid and working
737 return [
738 'status' => 'configured',
739 'message' => 'Google API key is valid and working correctly'
740 ];
741 }
742
743 /**
744 * Get settings arguments for REST API
745 *
746 * @since 1.0.0
747 * @return array Settings arguments
748 */
749 private function get_settings_args(): array {
750 return [
751 'settings' => [
752 'required' => true,
753 'type' => 'object',
754 'description' => 'Integrations settings object'
755 ]
756 ];
757 }
758
759 /**
760 * Check read permissions
761 *
762 * @since 1.0.0
763 * @return bool Permission status
764 */
765 public function check_read_permissions(): bool {
766 return current_user_can('manage_options');
767 }
768
769 /**
770 * Check manage permissions
771 *
772 * @since 1.0.0
773 * @return bool Permission status
774 */
775 public function check_manage_permissions(): bool {
776 return current_user_can('manage_options');
777 }
778
779 /**
780 * Verify GA4 tracking
781 * Following ThinkRank API response patterns
782 *
783 * @since 1.0.0
784 * @param WP_REST_Request $request Request object
785 * @return WP_REST_Response|WP_Error Response object
786 */
787 public function verify_ga4_tracking(WP_REST_Request $request): WP_REST_Response|WP_Error {
788 try {
789 $measurement_id = $request->get_param('measurement_id');
790
791 if (empty($measurement_id)) {
792 return new WP_Error(
793 'missing_measurement_id',
794 'Measurement ID is required',
795 ['status' => 400]
796 );
797 }
798
799 // Load tracking manager
800 if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
801 require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
802 }
803
804 $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
805 $verification_result = $tracking_manager->verify_tracking($measurement_id);
806
807 return new WP_REST_Response([
808 'success' => true,
809 'data' => $verification_result,
810 'message' => 'Tracking verification completed'
811 ], 200);
812
813 } catch (\Exception $e) {
814 return new WP_Error(
815 'verification_failed',
816 'Tracking verification failed: ' . $e->getMessage(),
817 ['status' => 500]
818 );
819 }
820 }
821
822 /**
823 * Detect GA4 conflicts
824 * Following ThinkRank API response patterns
825 *
826 * @since 1.0.0
827 * @param WP_REST_Request $request Request object
828 * @return WP_REST_Response|WP_Error Response object
829 */
830 public function detect_ga4_conflicts(WP_REST_Request $request): WP_REST_Response|WP_Error {
831 try {
832 // Load tracking manager
833 if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
834 require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
835 }
836
837 $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
838 $conflicts = $tracking_manager->detect_existing_tracking();
839
840 return new WP_REST_Response([
841 'success' => true,
842 'data' => [
843 'conflicts' => $conflicts,
844 'has_conflicts' => !empty($conflicts)
845 ],
846 'message' => 'Conflict detection completed'
847 ], 200);
848
849 } catch (\Exception $e) {
850 return new WP_Error(
851 'conflict_detection_failed',
852 'Conflict detection failed: ' . $e->getMessage(),
853 ['status' => 500]
854 );
855 }
856 }
857 }
858