PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.27.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.27.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 / core / class-settings-manager.php

class-settings-manager.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.27.0, at includes/core/class-settings-manager.php

812 lines 25.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Centralized Settings Manager Class
5 *
6 * Coordinates settings management across all ThinkRank components including
7 * core plugin settings, SEO-specific settings, and cross-manager coordination.
8 * Provides unified interface for the Settings Management API Endpoints with
9 * proper validation, import/export, and backup/restore functionality.
10 *
11 * @package ThinkRank
12 * @subpackage Core
13 * @since 1.0.0
14 */
15
16 declare(strict_types=1);
17
18 namespace ThinkRank\Core;
19
20 use ThinkRank\Core\Settings;
21 use ThinkRank\SEO\SEO_Settings_Manager;
22
23 // Prevent direct access
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Centralized Settings Manager Class
30 *
31 * Provides unified settings management interface that coordinates between
32 * core plugin settings and SEO-specific settings. Handles cross-manager
33 * validation, import/export, backup/restore, and conflict resolution.
34 *
35 * @since 1.0.0
36 */
37 class Settings_Manager {
38
39 /**
40 * Core Settings instance
41 *
42 * @since 1.0.0
43 * @var Settings
44 */
45 private Settings $core_settings;
46
47 /**
48 * SEO Settings Manager instance
49 *
50 * @since 1.0.0
51 * @var SEO_Settings_Manager
52 */
53 private SEO_Settings_Manager $seo_settings;
54
55 /**
56 * Settings categories mapping
57 *
58 * @since 1.0.0
59 * @var array
60 */
61 private array $settings_categories = [
62 'core' => [
63 'name' => 'Core Plugin Settings',
64 'manager' => 'core',
65 'keys' => [
66 'ai_provider',
67 'openai_api_key',
68 'openai_model',
69 'claude_api_key',
70 'claude_model',
71 'gemini_api_key',
72 'gemini_model',
73 'openrouter_api_key',
74 'openrouter_model',
75 'max_tokens',
76 'temperature',
77 'cache_duration',
78 'max_requests_per_minute',
79 'enable_logging',
80 'debug_mode',
81 'api_timeout',
82 'retry_attempts',
83 'rate_limit_enabled',
84 'data_retention_days',
85 'anonymize_logs',
86 'share_usage_data',
87 'keep_data_on_uninstall'
88 ]
89 ],
90 'seo' => [
91 'name' => 'SEO Settings',
92 'manager' => 'seo',
93 'keys' => [
94 'auto_optimize',
95 'seo_score_threshold',
96 'enable_meta_generation',
97 'enable_schema_markup'
98 ]
99 ],
100 'ui' => [
101 'name' => 'User Interface Settings',
102 'manager' => 'core',
103 'keys' => [
104 'show_welcome_message',
105 'dashboard_widgets',
106 'editor_panel_position'
107 ]
108 ],
109 'basic_integrations' => [
110 'name' => 'Basic Integration Settings',
111 'manager' => 'core',
112 'keys' => [
113 'google_analytics_id',
114 'search_console_property'
115 ]
116 ],
117 'social_media' => [
118 'name' => 'Social Media & Open Graph',
119 'manager' => 'seo',
120 'keys' => [
121 'enabled',
122 'enable_open_graph',
123 'og_site_name',
124 'og_description',
125 'og_type',
126 'og_locale',
127 'default_og_image',
128 'og_image_width',
129 'og_image_height',
130 'enable_twitter_cards',
131 'twitter_username',
132 'twitter_card_type',
133 'default_twitter_image',
134 'facebook_app_id',
135 'facebook_admins',
136 'enable_linkedin',
137 'enable_pinterest',
138 'pinterest_site_verification',
139 'enable_instagram',
140 'instagram_verification',
141 'enable_tiktok',
142 'tiktok_verification',
143 'enable_youtube',
144 'youtube_channel_id',
145 'enable_whatsapp',
146 'whatsapp_business_id',
147 'auto_generate_descriptions',
148 'fallback_to_excerpt',
149 'strip_html_tags',
150 'max_description_length'
151 ]
152 ],
153 'sitemap' => [
154 'name' => 'XML Sitemap Management',
155 'manager' => 'seo',
156 'keys' => [
157 'enabled',
158 'include_posts',
159 'include_pages',
160 'include_categories',
161 'include_tags',
162 'auto_generate',
163 'ping_search_engines',
164 'last_generated',
165 'exclude_posts',
166 'exclude_terms',
167 'exclude_password_protected',
168 'exclude_private_posts',
169 'enable_styling',
170 'custom_url_pattern',
171 'links_per_sitemap',
172 'include_images',
173 'include_featured_images',
174 'use_sitemap_index'
175 ]
176 ],
177 'site_identity' => [
178 'name' => 'Site Identity & Global SEO',
179 'manager' => 'seo',
180 'keys' => []
181 ],
182 'content_analysis' => [
183 'name' => 'AI Content Analysis',
184 'manager' => 'seo',
185 'keys' => []
186 ],
187 'content_optimization' => [
188 'name' => 'Content Optimization',
189 'manager' => 'seo',
190 'keys' => []
191 ],
192 'performance_monitoring' => [
193 'name' => 'Performance Monitoring',
194 'manager' => 'seo',
195 'keys' => []
196 ],
197 'schema_management' => [
198 'name' => 'Schema Management',
199 'manager' => 'seo',
200 'keys' => []
201 ],
202 'integrations' => [
203 'name' => 'Integrations',
204 'manager' => 'core',
205 'keys' => [
206 // Google API Keys
207 'google_analytics_api_key',
208 'google_search_console_api_key',
209 'google_pagespeed_api_key',
210 // Google OAuth Tokens
211 'google_access_token',
212 'google_refresh_token',
213 'google_token_expires_in',
214 'google_token_created',
215 'google_account_connected',
216 // API Configuration
217 'api_timeout',
218 'enable_rate_limiting',
219 'cache_duration',
220 // Connection settings
221 'auto_test_connections',
222 'retry_failed_requests'
223 ]
224 ],
225 'seo_analytics' => [
226 'name' => 'SEO Analytics & Intelligence',
227 'manager' => 'core',
228 'keys' => [
229 // Core settings
230 'seo_analytics_enabled',
231 'seo_analytics_setup_completed',
232
233 // Google Analytics configuration
234 'seo_analytics_google_analytics_property_id',
235 'ga_analytics_account_id',
236 'ga_analytics_data_stream_id',
237
238 // GA4 Tracking Code Injection (Pro)
239 'ga4_auto_inject',
240 'ga4_measurement_id',
241
242 // Search Console configuration
243 'search_console_property',
244
245 // AI features
246 'seo_analytics_enable_ai_insights',
247 'seo_analytics_enable_automated_alerts',
248 'seo_analytics_enable_predictive_analysis',
249
250 // Monitoring settings
251 'seo_analytics_monitoring_frequency',
252 'seo_analytics_alert_thresholds',
253 'seo_analytics_report_schedule',
254
255 // Data retention
256 'seo_analytics_data_retention_days',
257 'seo_analytics_cache_analytics_data'
258 ]
259 ],
260
261 ];
262
263 /**
264 * Constructor
265 *
266 * @since 1.0.0
267 */
268 public function __construct() {
269 $this->core_settings = Settings::instance();
270 $this->seo_settings = new SEO_Settings_Manager();
271 }
272
273 /**
274 * Get settings for a specific category
275 *
276 * @since 1.0.0
277 *
278 * @param string $category Settings category
279 * @param string $context_type Optional. Context type for SEO settings
280 * @param int|null $context_id Optional. Context ID for SEO settings
281 * @return array Settings array
282 */
283 public function get_settings(string $category, string $context_type = 'site', ?int $context_id = null): array {
284 if (!isset($this->settings_categories[$category])) {
285 return [];
286 }
287
288 $category_config = $this->settings_categories[$category];
289
290 if ($category_config['manager'] === 'core') {
291 return $this->get_core_settings_by_category($category);
292 } else {
293 return $this->get_seo_settings_by_category($category, $context_type, $context_id);
294 }
295 }
296
297 /**
298 * Update settings for a specific category
299 *
300 * @since 1.0.0
301 *
302 * @param array $settings Settings to update
303 * @param string $category Settings category
304 * @param string $context_type Optional. Context type for SEO settings
305 * @param int|null $context_id Optional. Context ID for SEO settings
306 * @return bool Success status
307 */
308 public function update_settings(array $settings, string $category, string $context_type = 'site', ?int $context_id = null): bool {
309 if (!isset($this->settings_categories[$category])) {
310 return false;
311 }
312
313 $category_config = $this->settings_categories[$category];
314
315 if ($category_config['manager'] === 'core') {
316 return $this->update_core_settings_by_category($settings, $category);
317 } else {
318 return $this->update_seo_settings_by_category($settings, $category, $context_type, $context_id);
319 }
320 }
321
322 /**
323 * Get all settings across categories
324 *
325 * @since 1.0.0
326 *
327 * @param array $categories Optional. Specific categories to retrieve
328 * @return array All settings organized by category
329 */
330 public function get_all_settings(array $categories = []): array {
331 $all_settings = [];
332 $target_categories = empty($categories) ? array_keys($this->settings_categories) : $categories;
333
334 foreach ($target_categories as $category) {
335 if (isset($this->settings_categories[$category])) {
336 $all_settings[$category] = $this->get_settings($category);
337 }
338 }
339
340 return $all_settings;
341 }
342
343 /**
344 * Update multiple categories of settings
345 *
346 * @since 1.0.0
347 *
348 * @param array $settings_by_category Settings organized by category
349 * @return array Update results for each category
350 */
351 public function update_multiple_settings(array $settings_by_category): array {
352 $results = [];
353
354 foreach ($settings_by_category as $category => $settings) {
355 try {
356 $success = $this->update_settings($settings, $category);
357 $results[$category] = [
358 'success' => $success,
359 'settings_count' => count($settings)
360 ];
361 } catch (\Exception $e) {
362 $results[$category] = [
363 'success' => false,
364 'error' => $e->getMessage()
365 ];
366 }
367 }
368
369 return $results;
370 }
371
372 /**
373 * Validate settings across categories
374 *
375 * @since 1.0.0
376 *
377 * @param array $settings_by_category Settings organized by category
378 * @return array Validation results for each category
379 */
380 public function validate_settings(array $settings_by_category): array {
381 $validation_results = [];
382
383 foreach ($settings_by_category as $category => $settings) {
384 if (!isset($this->settings_categories[$category])) {
385 $validation_results[$category] = [
386 'valid' => false,
387 'errors' => ['Invalid category'],
388 'warnings' => []
389 ];
390 continue;
391 }
392
393 $category_config = $this->settings_categories[$category];
394
395 if ($category_config['manager'] === 'core') {
396 $validation_results[$category] = $this->validate_core_settings($settings, $category);
397 } else {
398 $validation_results[$category] = $this->validate_seo_settings($settings, $category);
399 }
400 }
401
402 return $validation_results;
403 }
404
405 /**
406 * Get settings categories information
407 *
408 * @since 1.0.0
409 *
410 * @return array Categories information
411 */
412 public function get_categories(): array {
413 $categories = [];
414
415 foreach ($this->settings_categories as $key => $config) {
416 $categories[$key] = [
417 'name' => $config['name'],
418 'manager' => $config['manager'],
419 'key_count' => count($config['keys'])
420 ];
421 }
422
423 return $categories;
424 }
425
426 /**
427 * Export settings
428 *
429 * @since 1.0.0
430 *
431 * @param array $categories Optional. Categories to export
432 * @return array Export data with metadata
433 */
434 public function export_settings(array $categories = []): array {
435 $export_data = [
436 'metadata' => [
437 'export_timestamp' => current_time('mysql'),
438 'plugin_version' => defined('THINKRANK_VERSION') ? THINKRANK_VERSION : '1.0.0',
439 'wordpress_version' => get_bloginfo('version'),
440 'site_url' => home_url(),
441 'exported_categories' => empty($categories) ? array_keys($this->settings_categories) : $categories
442 ],
443 'settings' => $this->get_all_settings($categories)
444 ];
445
446 return $export_data;
447 }
448
449 /**
450 * Import settings
451 *
452 * @since 1.0.0
453 *
454 * @param array $import_data Import data with settings
455 * @param bool $validate_before_import Whether to validate before importing
456 * @return array Import results
457 */
458 public function import_settings(array $import_data, bool $validate_before_import = true): array {
459 $settings = $import_data['settings'] ?? $import_data;
460 $import_results = [];
461
462 // Validate if requested
463 if ($validate_before_import) {
464 $validation_results = $this->validate_settings($settings);
465
466 foreach ($validation_results as $category => $validation) {
467 if (!$validation['valid']) {
468 $import_results[$category] = [
469 'success' => false,
470 'error' => 'Validation failed',
471 'validation_errors' => $validation['errors']
472 ];
473 continue;
474 }
475 }
476 }
477
478 // Import settings for each category
479 foreach ($settings as $category => $category_settings) {
480 if (isset($import_results[$category])) {
481 continue; // Skip if validation failed
482 }
483
484 try {
485 $success = $this->update_settings($category_settings, $category);
486 $import_results[$category] = [
487 'success' => $success,
488 'settings_count' => count($category_settings)
489 ];
490 } catch (\Exception $e) {
491 $import_results[$category] = [
492 'success' => false,
493 'error' => $e->getMessage()
494 ];
495 }
496 }
497
498 return $import_results;
499 }
500
501 /**
502 * Reset settings to defaults
503 *
504 * @since 1.0.0
505 *
506 * @param array $categories Optional. Categories to reset
507 * @return array Reset results
508 */
509 public function reset_settings(array $categories = []): array {
510 $target_categories = empty($categories) ? array_keys($this->settings_categories) : $categories;
511 $reset_results = [];
512
513 foreach ($target_categories as $category) {
514 if (!isset($this->settings_categories[$category])) {
515 continue;
516 }
517
518 try {
519 $category_config = $this->settings_categories[$category];
520
521 if ($category_config['manager'] === 'core') {
522 $success = $this->reset_core_settings($category);
523 } else {
524 $success = $this->reset_seo_settings($category);
525 }
526
527 $reset_results[$category] = [
528 'success' => $success,
529 'reset_to_defaults' => true
530 ];
531 } catch (\Exception $e) {
532 $reset_results[$category] = [
533 'success' => false,
534 'error' => $e->getMessage()
535 ];
536 }
537 }
538
539 return $reset_results;
540 }
541
542 /**
543 * Get settings statistics
544 *
545 * @since 1.0.0
546 *
547 * @return array Settings statistics
548 */
549 public function get_settings_statistics(): array {
550 $stats = [
551 'total_categories' => count($this->settings_categories),
552 'core_categories' => 0,
553 'seo_categories' => 0,
554 'total_settings' => 0,
555 'last_updated' => get_option('thinkrank_settings_last_updated'),
556 'version' => get_option('thinkrank_settings_version', '1.0.0')
557 ];
558
559 foreach ($this->settings_categories as $category => $config) {
560 if ($config['manager'] === 'core') {
561 $stats['core_categories']++;
562 } else {
563 $stats['seo_categories']++;
564 }
565
566 $category_settings = $this->get_settings($category);
567 $stats['total_settings'] += count($category_settings);
568 }
569
570 return $stats;
571 }
572
573 /**
574 * Helper methods for core settings management
575 */
576
577 /**
578 * Get core settings by category
579 *
580 * @since 1.0.0
581 *
582 * @param string $category Category name
583 * @return array Core settings for category
584 */
585 private function get_core_settings_by_category(string $category): array {
586 $category_config = $this->settings_categories[$category];
587 $settings = [];
588
589 foreach ($category_config['keys'] as $key) {
590 $settings[$key] = $this->core_settings->get($key);
591 }
592
593 return $settings;
594 }
595
596 /**
597 * Update core settings by category
598 *
599 * @since 1.0.0
600 *
601 * @param array $settings Settings to update
602 * @param string $category Category name
603 * @return bool Success status
604 */
605 private function update_core_settings_by_category(array $settings, string $category): bool {
606 $category_config = $this->settings_categories[$category];
607 $success_count = 0;
608 $total_count = 0;
609
610 foreach ($settings as $key => $value) {
611 if (in_array($key, $category_config['keys'], true)) {
612 $total_count++;
613
614 if ($this->core_settings->set($key, $value)) {
615 $success_count++;
616 }
617 }
618 }
619
620 // Consider successful if at least 70% of settings were saved
621 $success_rate = $total_count > 0 ? ($success_count / $total_count) : 0;
622 $success = $success_rate >= 0.7;
623
624 if ($success) {
625 update_option('thinkrank_settings_last_updated', current_time('mysql'));
626 }
627
628 return $success;
629 }
630
631 /**
632 * Get SEO settings by category
633 *
634 * @since 1.0.0
635 *
636 * @param string $category Category name
637 * @param string $context_type Context type
638 * @param int|null $context_id Context ID
639 * @return array SEO settings for category
640 */
641 private function get_seo_settings_by_category(string $category, string $context_type, ?int $context_id): array {
642 // For SEO categories, delegate to SEO Settings Manager
643 return $this->seo_settings->get_settings_by_category($context_type, $context_id, $category);
644 }
645
646 /**
647 * Update SEO settings by category
648 *
649 * @since 1.0.0
650 *
651 * @param array $settings Settings to update
652 * @param string $category Category name
653 * @param string $context_type Context type
654 * @param int|null $context_id Context ID
655 * @return bool Success status
656 */
657 private function update_seo_settings_by_category(array $settings, string $category, string $context_type, ?int $context_id): bool {
658 return $this->seo_settings->save_settings_by_category($context_type, $context_id, $settings, $category);
659 }
660
661 /**
662 * Validate core settings
663 *
664 * @since 1.0.0
665 *
666 * @param array $settings Settings to validate
667 * @param string $category Category name
668 * @return array Validation results
669 */
670 private function validate_core_settings(array $settings, string $category): array {
671 $validation = [
672 'valid' => true,
673 'errors' => [],
674 'warnings' => []
675 ];
676
677 $category_config = $this->settings_categories[$category];
678
679 foreach ($settings as $key => $value) {
680 if (!in_array($key, $category_config['keys'], true)) {
681 $validation['warnings'][] = "Unknown setting key: {$key}";
682 continue;
683 }
684
685 // Validate specific core settings
686 $field_validation = $this->validate_core_setting($key, $value);
687 if (!$field_validation['valid']) {
688 $validation['valid'] = false;
689 $validation['errors'] = array_merge($validation['errors'], $field_validation['errors']);
690 }
691 }
692
693 return $validation;
694 }
695
696 /**
697 * Validate SEO settings
698 *
699 * @since 1.0.0
700 *
701 * @param array $settings Settings to validate
702 * @param string $category Category name
703 * @return array Validation results
704 */
705 private function validate_seo_settings(array $settings, string $category): array {
706 // Delegate to SEO Settings Manager validation
707 return $this->seo_settings->validate_settings($settings);
708 }
709
710 /**
711 * Validate individual core setting
712 *
713 * @since 1.0.0
714 *
715 * @param string $key Setting key
716 * @param mixed $value Setting value
717 * @return array Validation result
718 */
719 private function validate_core_setting(string $key, $value): array {
720 $validation = ['valid' => true, 'errors' => []];
721
722 switch ($key) {
723 case 'openai_api_key':
724 case 'claude_api_key':
725 case 'openrouter_api_key':
726 if (!empty($value) && !is_string($value)) {
727 $validation['valid'] = false;
728 $validation['errors'][] = "{$key} must be a string";
729 }
730 break;
731
732 case 'max_tokens':
733 case 'cache_duration':
734 case 'max_requests_per_minute':
735 case 'seo_score_threshold':
736 case 'api_timeout':
737 case 'retry_attempts':
738 case 'data_retention_days':
739 if (!is_numeric($value) || $value < 0) {
740 $validation['valid'] = false;
741 $validation['errors'][] = "{$key} must be a positive number";
742 }
743 break;
744
745 case 'temperature':
746 if (!is_numeric($value) || $value < 0 || $value > 2) {
747 $validation['valid'] = false;
748 $validation['errors'][] = "temperature must be between 0 and 2";
749 }
750 break;
751
752 case 'ai_provider':
753 if (!in_array($value, ['openai', 'claude', 'gemini', 'openrouter'], true)) {
754 $validation['valid'] = false;
755 $validation['errors'][] = "ai_provider must be 'openai', 'claude', 'gemini', or 'openrouter'";
756 }
757 break;
758
759 case 'dashboard_widgets':
760 if (!is_array($value)) {
761 $validation['valid'] = false;
762 $validation['errors'][] = "dashboard_widgets must be an array";
763 }
764 break;
765 }
766
767 return $validation;
768 }
769
770 /**
771 * Reset core settings for category
772 *
773 * @since 1.0.0
774 *
775 * @param string $category Category name
776 * @return bool Success status
777 */
778 private function reset_core_settings(string $category): bool {
779 $category_config = $this->settings_categories[$category];
780 $success = true;
781
782 foreach ($category_config['keys'] as $key) {
783 if (!$this->core_settings->delete($key)) {
784 $success = false;
785 }
786 }
787
788 return $success;
789 }
790
791 /**
792 * Reset SEO settings for category
793 *
794 * @since 1.0.0
795 *
796 * @param string $category Category name
797 * @return bool Success status
798 */
799 private function reset_seo_settings(string $category): bool {
800 try {
801 // Map the settings category to the SEO context type
802 return $this->seo_settings->reset_to_defaults('site');
803 } catch (\Exception $e) {
804 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
805 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging only when WP_DEBUG is enabled.
806 error_log('ThinkRank: Failed to reset SEO settings for category "' . $category . '": ' . $e->getMessage());
807 }
808 return false;
809 }
810 }
811 }
812