| @@ -257,12 +257,8 @@ | ||
| 257 | 257 | 'seo_analytics_google_analytics_property_id', |
| 258 | 258 | 'ga_analytics_account_id', |
| 259 | 259 | 'ga_analytics_data_stream_id', |
| 260 | 260 | |
| 261 | - // GA4 Tracking Code Injection (Pro) | |
| 262 | - 'ga4_auto_inject', | |
| 263 | - 'ga4_measurement_id', | |
| 264 | - | |
| 265 | 261 | // Search Console configuration |
| 266 | 262 | 'search_console_property', |
| 267 | 263 | |
| 268 | 264 | // AI features |
| @@ -454,8 +450,23 @@ | ||
| 454 | 450 | * @since 1.0.0 |
| 455 | 451 | * |
| 456 | 452 | * @return array Categories information |
| 457 | 453 | */ |
| 454 | + /** | |
| 455 | + * The setting keys a category defines. | |
| 456 | + * | |
| 457 | + * Exposed so callers can reject keys a category does not define instead of | |
| 458 | + * persisting whatever they are handed (#395). | |
| 459 | + * | |
| 460 | + * @since 2.0.1 | |
| 461 | + * | |
| 462 | + * @param string $category Category name. | |
| 463 | + * @return string[] Setting keys, or [] when the category is unknown here. | |
| 464 | + */ | |
| 465 | + public function get_category_keys(string $category): array { | |
| 466 | + return $this->settings_categories[$category]['keys'] ?? []; | |
| 467 | + } | |
| 468 | + | |
| 458 | 469 | public function get_categories(): array { |
| 459 | 470 | $categories = []; |
| 460 | 471 | |
| 461 | 472 | foreach ($this->settings_categories as $key => $config) { |
| @@ -629,8 +640,16 @@ | ||
| 629 | 640 | * @return array Core settings for category |
| 630 | 641 | */ |
| 631 | 642 | private function get_core_settings_by_category(string $category): array { |
| 632 | 643 | $category_config = $this->settings_categories[$category]; |
| 644 | + | |
| 645 | + // Prime the option cache in one query before the loop. Every | |
| 646 | + // thinkrank_* option is autoload=off, so WordPress cannot serve them | |
| 647 | + // from `alloptions` and each Settings->get() below was its own | |
| 648 | + // round-trip — 16 of them on every anonymous front-end request, on | |
| 649 | + // pages that use none of the values (#393). | |
| 650 | + $this->core_settings->prime($category_config['keys']); | |
| 651 | + | |
| 633 | 652 | $settings = []; |
| 634 | 653 | |
| 635 | 654 | foreach ($category_config['keys'] as $key) { |
| 636 | 655 | $settings[$key] = $this->core_settings->get($key); |
| @@ -813,11 +832,15 @@ | ||
| 813 | 832 | } |
| 814 | 833 | break; |
| 815 | 834 | |
| 816 | 835 | case 'ai_provider': |
| 817 | - if (!in_array($value, ['openai', 'claude', 'gemini', 'openrouter'], true)) { | |
| 836 | + // '' is legal: it is Settings::AI_PROVIDER_NONE, the state a | |
| 837 | + // fresh install starts in and the one a user returns to by | |
| 838 | + // deselecting their provider (#572). | |
| 839 | + if (!in_array($value, \ThinkRank\Core\Settings::selectable_ai_providers(), true)) { | |
| 818 | 840 | $validation['valid'] = false; |
| 819 | - $validation['errors'][] = "ai_provider must be 'openai', 'claude', 'gemini', or 'openrouter'"; | |
| 841 | + $validation['errors'][] = "ai_provider must be empty (no provider) or one of: " | |
| 842 | + . implode(', ', \ThinkRank\Core\Settings::SUPPORTED_AI_PROVIDERS); | |
| 820 | 843 | } |
| 821 | 844 | break; |
| 822 | 845 | |
| 823 | 846 | case 'dashboard_widgets': |