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 / 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.10.0, at includes/core/class-settings-manager.php

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