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

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