PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.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.0.0, at includes/api/class-integrations-endpoint.php

553 lines 19.0 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 * @since 1.0.0
394 * @param string $api_key API key to test
395 * @return array Test result
396 */
397 private function test_google_analytics(string $api_key): array {
398 // Simple validation test - in production this would make actual API call
399 if (strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
400 return ['status' => 'error', 'message' => 'Invalid Google Analytics API key format'];
401 }
402
403 return ['status' => 'configured', 'message' => 'Google Analytics API key is configured and format is valid'];
404 }
405
406 /**
407 * Test Google Search Console API connection
408 *
409 * @since 1.0.0
410 * @param string $api_key API key to test
411 * @return array Test result
412 */
413 private function test_search_console(string $api_key): array {
414 // Simple validation test - in production this would make actual API call
415 if (strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
416 return ['status' => 'error', 'message' => 'Invalid Search Console API key format'];
417 }
418
419 return ['status' => 'configured', 'message' => 'Search Console API key is configured and format is valid'];
420 }
421
422 /**
423 * Test Google PageSpeed API connection
424 *
425 * @since 1.0.0
426 * @param string $api_key API key to test
427 * @return array Test result
428 */
429 private function test_pagespeed(string $api_key): array {
430 // Simple validation test - in production this would make actual API call
431 if (strlen($api_key) < 30 || !str_starts_with($api_key, 'AIza')) {
432 return ['status' => 'error', 'message' => 'Invalid PageSpeed Insights API key format'];
433 }
434
435 return ['status' => 'configured', 'message' => 'PageSpeed Insights API key is configured and format is valid'];
436 }
437
438 /**
439 * Get settings arguments for REST API
440 *
441 * @since 1.0.0
442 * @return array Settings arguments
443 */
444 private function get_settings_args(): array {
445 return [
446 'settings' => [
447 'required' => true,
448 'type' => 'object',
449 'description' => 'Integrations settings object'
450 ]
451 ];
452 }
453
454 /**
455 * Check read permissions
456 *
457 * @since 1.0.0
458 * @return bool Permission status
459 */
460 public function check_read_permissions(): bool {
461 return current_user_can('manage_options');
462 }
463
464 /**
465 * Check manage permissions
466 *
467 * @since 1.0.0
468 * @return bool Permission status
469 */
470 public function check_manage_permissions(): bool {
471 return current_user_can('manage_options');
472 }
473
474 /**
475 * Verify GA4 tracking
476 * Following ThinkRank API response patterns
477 *
478 * @since 1.0.0
479 * @param WP_REST_Request $request Request object
480 * @return WP_REST_Response|WP_Error Response object
481 */
482 public function verify_ga4_tracking(WP_REST_Request $request): WP_REST_Response|WP_Error {
483 try {
484 $measurement_id = $request->get_param('measurement_id');
485
486 if (empty($measurement_id)) {
487 return new WP_Error(
488 'missing_measurement_id',
489 'Measurement ID is required',
490 ['status' => 400]
491 );
492 }
493
494 // Load tracking manager
495 if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
496 require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
497 }
498
499 $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
500 $verification_result = $tracking_manager->verify_tracking($measurement_id);
501
502 return new WP_REST_Response([
503 'success' => true,
504 'data' => $verification_result,
505 'message' => 'Tracking verification completed'
506 ], 200);
507
508 } catch (\Exception $e) {
509 return new WP_Error(
510 'verification_failed',
511 'Tracking verification failed: ' . $e->getMessage(),
512 ['status' => 500]
513 );
514 }
515 }
516
517 /**
518 * Detect GA4 conflicts
519 * Following ThinkRank API response patterns
520 *
521 * @since 1.0.0
522 * @param WP_REST_Request $request Request object
523 * @return WP_REST_Response|WP_Error Response object
524 */
525 public function detect_ga4_conflicts(WP_REST_Request $request): WP_REST_Response|WP_Error {
526 try {
527 // Load tracking manager
528 if (!class_exists('ThinkRank\\Frontend\\Google_Analytics_Tracking_Manager')) {
529 require_once THINKRANK_PLUGIN_DIR . 'includes/frontend/class-google-analytics-tracking-manager.php';
530 }
531
532 $tracking_manager = new \ThinkRank\Frontend\Google_Analytics_Tracking_Manager();
533 $conflicts = $tracking_manager->detect_existing_tracking();
534
535 return new WP_REST_Response([
536 'success' => true,
537 'data' => [
538 'conflicts' => $conflicts,
539 'has_conflicts' => !empty($conflicts)
540 ],
541 'message' => 'Conflict detection completed'
542 ], 200);
543
544 } catch (\Exception $e) {
545 return new WP_Error(
546 'conflict_detection_failed',
547 'Conflict detection failed: ' . $e->getMessage(),
548 ['status' => 500]
549 );
550 }
551 }
552 }
553