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

871 lines 28.3 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 * Keys the most recent core-category save could not persist.
57 *
58 * A batch save is all-or-nothing in its reporting but not in its writes, so
59 * a caller that gets false needs to know *which* settings did not make it —
60 * a bare boolean leaves the UI unable to say anything useful (#300).
61 *
62 * @since 1.30.0
63 * @var string[]
64 */
65 private array $last_failed_keys = [];
66
67 /**
68 * Settings categories mapping
69 *
70 * @since 1.0.0
71 * @var array
72 */
73 private array $settings_categories = [
74 'core' => [
75 'name' => 'Core Plugin Settings',
76 'manager' => 'core',
77 'keys' => [
78 'ai_provider',
79 'openai_api_key',
80 'openai_model',
81 'claude_api_key',
82 'claude_model',
83 'gemini_api_key',
84 'gemini_model',
85 'openrouter_api_key',
86 'openrouter_model',
87 'max_tokens',
88 'temperature',
89 'cache_duration',
90 'max_requests_per_minute',
91 'enable_logging',
92 'debug_mode',
93 'api_timeout',
94 'retry_attempts',
95 // 'rate_limit_enabled' used to be listed here, but it has no
96 // entry in Settings::defaults, so Settings::set() rejected it on
97 // every save and no code ever read it. Under the old 70%
98 // threshold that silent rejection was reported as success;
99 // all-or-nothing reporting would now fail every core save that
100 // carried it, so the dead key goes rather than the save (#300).
101 'data_retention_days',
102 'anonymize_logs',
103 'share_usage_data',
104 'keep_data_on_uninstall'
105 ]
106 ],
107 'seo' => [
108 'name' => 'SEO Settings',
109 'manager' => 'seo',
110 'keys' => [
111 'auto_optimize',
112 'seo_score_threshold',
113 'enable_meta_generation',
114 'enable_schema_markup'
115 ]
116 ],
117 'ui' => [
118 'name' => 'User Interface Settings',
119 'manager' => 'core',
120 'keys' => [
121 'show_welcome_message',
122 'dashboard_widgets',
123 'editor_panel_position'
124 ]
125 ],
126 'basic_integrations' => [
127 'name' => 'Basic Integration Settings',
128 'manager' => 'core',
129 'keys' => [
130 'google_analytics_id',
131 'search_console_property'
132 ]
133 ],
134 'social_media' => [
135 'name' => 'Social Media & Open Graph',
136 'manager' => 'seo',
137 'keys' => [
138 'enabled',
139 'enable_open_graph',
140 'og_site_name',
141 'og_description',
142 'og_type',
143 'og_locale',
144 'default_og_image',
145 'og_image_width',
146 'og_image_height',
147 'enable_twitter_cards',
148 'twitter_username',
149 'twitter_card_type',
150 'default_twitter_image',
151 'facebook_app_id',
152 'facebook_admins',
153 'enable_linkedin',
154 'enable_pinterest',
155 'pinterest_site_verification',
156 'enable_instagram',
157 'instagram_verification',
158 'enable_tiktok',
159 'tiktok_verification',
160 'enable_youtube',
161 'youtube_channel_id',
162 'enable_whatsapp',
163 'whatsapp_business_id',
164 'auto_generate_descriptions',
165 'fallback_to_excerpt',
166 'strip_html_tags',
167 'max_description_length'
168 ]
169 ],
170 'sitemap' => [
171 'name' => 'XML Sitemap Management',
172 'manager' => 'seo',
173 'keys' => [
174 'enabled',
175 'include_posts',
176 'include_pages',
177 'include_categories',
178 'include_tags',
179 'auto_generate',
180 'ping_search_engines',
181 'last_generated',
182 'exclude_posts',
183 'exclude_terms',
184 'exclude_password_protected',
185 'exclude_private_posts',
186 'enable_styling',
187 'custom_url_pattern',
188 'links_per_sitemap',
189 'include_images',
190 'include_featured_images',
191 'use_sitemap_index'
192 ]
193 ],
194 'site_identity' => [
195 'name' => 'Site Identity & Global SEO',
196 'manager' => 'seo',
197 'keys' => []
198 ],
199 'content_analysis' => [
200 'name' => 'AI Content Analysis',
201 'manager' => 'seo',
202 'keys' => []
203 ],
204 'content_optimization' => [
205 'name' => 'Content Optimization',
206 'manager' => 'seo',
207 'keys' => []
208 ],
209 'performance_monitoring' => [
210 'name' => 'Performance Monitoring',
211 'manager' => 'seo',
212 'keys' => []
213 ],
214 'schema_management' => [
215 'name' => 'Schema Management',
216 'manager' => 'seo',
217 'keys' => []
218 ],
219 'integrations' => [
220 'name' => 'Integrations',
221 'manager' => 'core',
222 'keys' => [
223 // Google API Keys
224 'google_analytics_api_key',
225 'google_search_console_api_key',
226 'google_pagespeed_api_key',
227 // Google OAuth Tokens
228 'google_access_token',
229 'google_refresh_token',
230 'google_token_expires_in',
231 'google_token_created',
232 'google_account_connected',
233 // API Configuration
234 'api_timeout',
235 'enable_rate_limiting',
236 'cache_duration',
237 // Connection settings
238 'auto_test_connections',
239 'retry_failed_requests'
240 ]
241 ],
242 'seo_analytics' => [
243 'name' => 'SEO Analytics & Intelligence',
244 'manager' => 'core',
245 'keys' => [
246 // Core settings
247 'seo_analytics_enabled',
248 'seo_analytics_setup_completed',
249
250 // Google Analytics configuration. NOTE: the account/property/
251 // data-stream picker that reads AND writes these three keys is
252 // thinkrank-pro's GoogleAnalyticsSettings.js (via this plugin's
253 // settings-management endpoint) — a free-repo grep will find no
254 // consumer. ga_analytics_data_stream_id was once removed as a
255 // "dead key" on that basis, which silently broke the Pro
256 // picker's stream selection persisting across reloads.
257 'seo_analytics_google_analytics_property_id',
258 'ga_analytics_account_id',
259 'ga_analytics_data_stream_id',
260
261 // GA4 Tracking Code Injection (Pro)
262 'ga4_auto_inject',
263 'ga4_measurement_id',
264
265 // Search Console configuration
266 'search_console_property',
267
268 // AI features
269 'seo_analytics_enable_ai_insights',
270 'seo_analytics_enable_automated_alerts',
271 'seo_analytics_enable_predictive_analysis',
272
273 // Monitoring settings
274 'seo_analytics_monitoring_frequency',
275 'seo_analytics_alert_thresholds',
276 'seo_analytics_report_schedule',
277
278 // Data retention
279 'seo_analytics_data_retention_days',
280 'seo_analytics_cache_analytics_data'
281 ]
282 ],
283
284 ];
285
286 /**
287 * Constructor
288 *
289 * @since 1.0.0
290 */
291 public function __construct() {
292 $this->core_settings = Settings::instance();
293 $this->seo_settings = new SEO_Settings_Manager();
294 }
295
296 /**
297 * Get settings for a specific category
298 *
299 * @since 1.0.0
300 *
301 * @param string $category Settings category
302 * @param string $context_type Optional. Context type for SEO settings
303 * @param int|null $context_id Optional. Context ID for SEO settings
304 * @return array Settings array
305 */
306 public function get_settings(string $category, string $context_type = 'site', ?int $context_id = null): array {
307 if (!isset($this->settings_categories[$category])) {
308 return [];
309 }
310
311 $category_config = $this->settings_categories[$category];
312
313 if ($category_config['manager'] === 'core') {
314 return $this->get_core_settings_by_category($category);
315 } else {
316 return $this->get_seo_settings_by_category($category, $context_type, $context_id);
317 }
318 }
319
320 /**
321 * Update settings for a specific category
322 *
323 * @since 1.0.0
324 *
325 * @param array $settings Settings to update
326 * @param string $category Settings category
327 * @param string $context_type Optional. Context type for SEO settings
328 * @param int|null $context_id Optional. Context ID for SEO settings
329 * @return bool Success status
330 */
331 public function update_settings(array $settings, string $category, string $context_type = 'site', ?int $context_id = null): bool {
332 if (!isset($this->settings_categories[$category])) {
333 return false;
334 }
335
336 $category_config = $this->settings_categories[$category];
337
338 // Reset here, not only in the core path: a SEO-category save must not
339 // leave a previous core save's failed keys readable.
340 $this->last_failed_keys = [];
341
342 if ($category_config['manager'] === 'core') {
343 return $this->update_core_settings_by_category($settings, $category);
344 } else {
345 return $this->update_seo_settings_by_category($settings, $category, $context_type, $context_id);
346 }
347 }
348
349 /**
350 * Keys the most recent update_settings() call could not persist.
351 *
352 * Empty on success, and reset at the start of every update_settings()
353 * call. SEO categories persist through their own manager and do not
354 * report per key, so this stays empty for them.
355 *
356 * @since 1.30.0
357 *
358 * @return string[] Setting keys that failed to save.
359 */
360 public function get_last_failed_keys(): array {
361 return $this->last_failed_keys;
362 }
363
364 /**
365 * Get all settings across categories
366 *
367 * @since 1.0.0
368 *
369 * @param array $categories Optional. Specific categories to retrieve
370 * @return array All settings organized by category
371 */
372 public function get_all_settings(array $categories = []): array {
373 $all_settings = [];
374 $target_categories = empty($categories) ? array_keys($this->settings_categories) : $categories;
375
376 foreach ($target_categories as $category) {
377 if (isset($this->settings_categories[$category])) {
378 $all_settings[$category] = $this->get_settings($category);
379 }
380 }
381
382 return $all_settings;
383 }
384
385 /**
386 * Update multiple categories of settings
387 *
388 * @since 1.0.0
389 *
390 * @param array $settings_by_category Settings organized by category
391 * @return array Update results for each category
392 */
393 public function update_multiple_settings(array $settings_by_category): array {
394 $results = [];
395
396 foreach ($settings_by_category as $category => $settings) {
397 try {
398 $success = $this->update_settings($settings, $category);
399 $results[$category] = [
400 'success' => $success,
401 'settings_count' => count($settings)
402 ];
403 } catch (\Exception $e) {
404 $results[$category] = [
405 'success' => false,
406 'error' => $e->getMessage()
407 ];
408 }
409 }
410
411 return $results;
412 }
413
414 /**
415 * Validate settings across categories
416 *
417 * @since 1.0.0
418 *
419 * @param array $settings_by_category Settings organized by category
420 * @return array Validation results for each category
421 */
422 public function validate_settings(array $settings_by_category): array {
423 $validation_results = [];
424
425 foreach ($settings_by_category as $category => $settings) {
426 if (!isset($this->settings_categories[$category])) {
427 $validation_results[$category] = [
428 'valid' => false,
429 'errors' => ['Invalid category'],
430 'warnings' => []
431 ];
432 continue;
433 }
434
435 $category_config = $this->settings_categories[$category];
436
437 if ($category_config['manager'] === 'core') {
438 $validation_results[$category] = $this->validate_core_settings($settings, $category);
439 } else {
440 $validation_results[$category] = $this->validate_seo_settings($settings, $category);
441 }
442 }
443
444 return $validation_results;
445 }
446
447 /**
448 * Get settings categories information
449 *
450 * @since 1.0.0
451 *
452 * @return array Categories information
453 */
454 public function get_categories(): array {
455 $categories = [];
456
457 foreach ($this->settings_categories as $key => $config) {
458 $categories[$key] = [
459 'name' => $config['name'],
460 'manager' => $config['manager'],
461 'key_count' => count($config['keys'])
462 ];
463 }
464
465 return $categories;
466 }
467
468 /**
469 * Export settings
470 *
471 * @since 1.0.0
472 *
473 * @param array $categories Optional. Categories to export
474 * @return array Export data with metadata
475 */
476 public function export_settings(array $categories = []): array {
477 $export_data = [
478 'metadata' => [
479 'export_timestamp' => current_time('mysql'),
480 'plugin_version' => defined('THINKRANK_VERSION') ? THINKRANK_VERSION : '1.0.0',
481 'wordpress_version' => get_bloginfo('version'),
482 'site_url' => home_url(),
483 'exported_categories' => empty($categories) ? array_keys($this->settings_categories) : $categories
484 ],
485 'settings' => $this->get_all_settings($categories)
486 ];
487
488 return $export_data;
489 }
490
491 /**
492 * Import settings
493 *
494 * @since 1.0.0
495 *
496 * @param array $import_data Import data with settings
497 * @param bool $validate_before_import Whether to validate before importing
498 * @return array Import results
499 */
500 public function import_settings(array $import_data, bool $validate_before_import = true): array {
501 $settings = $import_data['settings'] ?? $import_data;
502 $import_results = [];
503
504 // Validate if requested
505 if ($validate_before_import) {
506 $validation_results = $this->validate_settings($settings);
507
508 foreach ($validation_results as $category => $validation) {
509 if (!$validation['valid']) {
510 $import_results[$category] = [
511 'success' => false,
512 'error' => 'Validation failed',
513 'validation_errors' => $validation['errors']
514 ];
515 continue;
516 }
517 }
518 }
519
520 // Import settings for each category
521 foreach ($settings as $category => $category_settings) {
522 if (isset($import_results[$category])) {
523 continue; // Skip if validation failed
524 }
525
526 try {
527 $success = $this->update_settings($category_settings, $category);
528 $import_results[$category] = [
529 'success' => $success,
530 'settings_count' => count($category_settings)
531 ];
532 } catch (\Exception $e) {
533 $import_results[$category] = [
534 'success' => false,
535 'error' => $e->getMessage()
536 ];
537 }
538 }
539
540 return $import_results;
541 }
542
543 /**
544 * Reset settings to defaults
545 *
546 * @since 1.0.0
547 *
548 * @param array $categories Optional. Categories to reset
549 * @return array Reset results
550 */
551 public function reset_settings(array $categories = []): array {
552 $target_categories = empty($categories) ? array_keys($this->settings_categories) : $categories;
553 $reset_results = [];
554
555 foreach ($target_categories as $category) {
556 if (!isset($this->settings_categories[$category])) {
557 continue;
558 }
559
560 try {
561 $category_config = $this->settings_categories[$category];
562
563 if ($category_config['manager'] === 'core') {
564 $success = $this->reset_core_settings($category);
565 } else {
566 $success = $this->reset_seo_settings($category);
567 }
568
569 $reset_results[$category] = [
570 'success' => $success,
571 'reset_to_defaults' => true
572 ];
573 } catch (\Exception $e) {
574 $reset_results[$category] = [
575 'success' => false,
576 'error' => $e->getMessage()
577 ];
578 }
579 }
580
581 return $reset_results;
582 }
583
584 /**
585 * Get settings statistics
586 *
587 * @since 1.0.0
588 *
589 * @return array Settings statistics
590 */
591 public function get_settings_statistics(): array {
592 $stats = [
593 'total_categories' => count($this->settings_categories),
594 'core_categories' => 0,
595 'seo_categories' => 0,
596 'total_settings' => 0,
597 'last_updated' => get_option('thinkrank_settings_last_updated'),
598 'version' => get_option('thinkrank_settings_version', '1.0.0')
599 ];
600
601 foreach ($this->settings_categories as $category => $config) {
602 if ($config['manager'] === 'core') {
603 $stats['core_categories']++;
604 } else {
605 $stats['seo_categories']++;
606 }
607
608 $category_settings = $this->get_settings($category);
609 $stats['total_settings'] += count($category_settings);
610 }
611
612 return $stats;
613 }
614
615 /**
616 * Helper methods for core settings management
617 */
618
619 /**
620 * Get core settings by category
621 *
622 * @since 1.0.0
623 *
624 * @param string $category Category name
625 * @return array Core settings for category
626 */
627 private function get_core_settings_by_category(string $category): array {
628 $category_config = $this->settings_categories[$category];
629 $settings = [];
630
631 foreach ($category_config['keys'] as $key) {
632 $settings[$key] = $this->core_settings->get($key);
633 }
634
635 return $settings;
636 }
637
638 /**
639 * Update core settings by category
640 *
641 * @since 1.0.0
642 *
643 * @param array $settings Settings to update
644 * @param string $category Category name
645 * @return bool Success status
646 */
647 private function update_core_settings_by_category(array $settings, string $category): bool {
648 $category_config = $this->settings_categories[$category];
649 $total_count = 0;
650
651 $this->last_failed_keys = [];
652
653 // Sanitize per field before persisting. This is unconditional: callers
654 // (including the REST write routes, where the client can ask to skip
655 // validation) must not be able to reach Settings::set with unsanitized
656 // values — Settings::set only key-allowlists, it does not sanitize.
657 $settings = $this->core_settings->sanitize_settings($settings);
658
659 foreach ($settings as $key => $value) {
660 if (in_array($key, $category_config['keys'], true)) {
661 $total_count++;
662
663 if (!$this->core_settings->set($key, $value)) {
664 $this->last_failed_keys[] = $key;
665
666 // Name the key in the log: the UI can only ever show one
667 // message for the batch, so without this a single dropped
668 // setting is indistinguishable from a healthy save.
669 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- deliberate diagnostic, see above.
670 error_log(sprintf('ThinkRank [%s]: settings save failed — key \'%s\' was not stored', $category, $key));
671 }
672 }
673 }
674
675 // Every requested key must persist. A partial save used to pass on a 70%
676 // threshold, so a batch could silently drop up to a third of the user's
677 // settings while the UI reported success and the values were simply gone
678 // (#300). Note the write is not transactional: the keys that did save
679 // stay saved, which is why the failed keys are reported rather than just
680 // a bare false.
681 $success = $total_count > 0 && empty($this->last_failed_keys);
682
683 if ($success) {
684 update_option('thinkrank_settings_last_updated', current_time('mysql'));
685 }
686
687 return $success;
688 }
689
690 /**
691 * Get SEO settings by category
692 *
693 * @since 1.0.0
694 *
695 * @param string $category Category name
696 * @param string $context_type Context type
697 * @param int|null $context_id Context ID
698 * @return array SEO settings for category
699 */
700 private function get_seo_settings_by_category(string $category, string $context_type, ?int $context_id): array {
701 // For SEO categories, delegate to SEO Settings Manager
702 return $this->seo_settings->get_settings_by_category($context_type, $context_id, $category);
703 }
704
705 /**
706 * Update SEO settings by category
707 *
708 * @since 1.0.0
709 *
710 * @param array $settings Settings to update
711 * @param string $category Category name
712 * @param string $context_type Context type
713 * @param int|null $context_id Context ID
714 * @return bool Success status
715 */
716 private function update_seo_settings_by_category(array $settings, string $category, string $context_type, ?int $context_id): bool {
717 return $this->seo_settings->save_settings_by_category($context_type, $context_id, $settings, $category);
718 }
719
720 /**
721 * Validate core settings
722 *
723 * @since 1.0.0
724 *
725 * @param array $settings Settings to validate
726 * @param string $category Category name
727 * @return array Validation results
728 */
729 private function validate_core_settings(array $settings, string $category): array {
730 $validation = [
731 'valid' => true,
732 'errors' => [],
733 'warnings' => []
734 ];
735
736 $category_config = $this->settings_categories[$category];
737
738 foreach ($settings as $key => $value) {
739 if (!in_array($key, $category_config['keys'], true)) {
740 $validation['warnings'][] = "Unknown setting key: {$key}";
741 continue;
742 }
743
744 // Validate specific core settings
745 $field_validation = $this->validate_core_setting($key, $value);
746 if (!$field_validation['valid']) {
747 $validation['valid'] = false;
748 $validation['errors'] = array_merge($validation['errors'], $field_validation['errors']);
749 }
750 }
751
752 return $validation;
753 }
754
755 /**
756 * Validate SEO settings
757 *
758 * @since 1.0.0
759 *
760 * @param array $settings Settings to validate
761 * @param string $category Category name
762 * @return array Validation results
763 */
764 private function validate_seo_settings(array $settings, string $category): array {
765 // Delegate to SEO Settings Manager validation
766 return $this->seo_settings->validate_settings($settings);
767 }
768
769 /**
770 * Validate individual core setting
771 *
772 * @since 1.0.0
773 *
774 * @param string $key Setting key
775 * @param mixed $value Setting value
776 * @return array Validation result
777 */
778 private function validate_core_setting(string $key, $value): array {
779 $validation = ['valid' => true, 'errors' => []];
780
781 switch ($key) {
782 case 'openai_api_key':
783 case 'claude_api_key':
784 case 'openrouter_api_key':
785 if (!empty($value) && !is_string($value)) {
786 $validation['valid'] = false;
787 $validation['errors'][] = "{$key} must be a string";
788 }
789 break;
790
791 case 'max_tokens':
792 case 'cache_duration':
793 case 'max_requests_per_minute':
794 case 'seo_score_threshold':
795 case 'api_timeout':
796 case 'retry_attempts':
797 case 'data_retention_days':
798 if (!is_numeric($value) || $value < 0) {
799 $validation['valid'] = false;
800 $validation['errors'][] = "{$key} must be a positive number";
801 }
802 break;
803
804 case 'temperature':
805 if (!is_numeric($value) || $value < 0 || $value > 2) {
806 $validation['valid'] = false;
807 $validation['errors'][] = "temperature must be between 0 and 2";
808 }
809 break;
810
811 case 'ai_provider':
812 if (!in_array($value, ['openai', 'claude', 'gemini', 'openrouter'], true)) {
813 $validation['valid'] = false;
814 $validation['errors'][] = "ai_provider must be 'openai', 'claude', 'gemini', or 'openrouter'";
815 }
816 break;
817
818 case 'dashboard_widgets':
819 if (!is_array($value)) {
820 $validation['valid'] = false;
821 $validation['errors'][] = "dashboard_widgets must be an array";
822 }
823 break;
824 }
825
826 return $validation;
827 }
828
829 /**
830 * Reset core settings for category
831 *
832 * @since 1.0.0
833 *
834 * @param string $category Category name
835 * @return bool Success status
836 */
837 private function reset_core_settings(string $category): bool {
838 $category_config = $this->settings_categories[$category];
839 $success = true;
840
841 foreach ($category_config['keys'] as $key) {
842 if (!$this->core_settings->delete($key)) {
843 $success = false;
844 }
845 }
846
847 return $success;
848 }
849
850 /**
851 * Reset SEO settings for category
852 *
853 * @since 1.0.0
854 *
855 * @param string $category Category name
856 * @return bool Success status
857 */
858 private function reset_seo_settings(string $category): bool {
859 try {
860 // Map the settings category to the SEO context type
861 return $this->seo_settings->reset_to_defaults('site');
862 } catch (\Exception $e) {
863 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
864 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging only when WP_DEBUG is enabled.
865 error_log('ThinkRank: Failed to reset SEO settings for category "' . $category . '": ' . $e->getMessage());
866 }
867 return false;
868 }
869 }
870 }
871