PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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
← All changes | includes/core/class-settings-manager.php +39 -11 1.30.02.7.0 View file →
@@ -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
@@ -325,13 +321,17 @@
325 321 * @param array $settings Settings to update
326 322 * @param string $category Settings category
327 323 * @param string $context_type Optional. Context type for SEO settings
328 324 * @param int|null $context_id Optional. Context ID for SEO settings
329 - * @return bool Success status
325 + * @return bool|null True on success, false on failure, null when this store
326 + * does not own the category (nothing was attempted).
330 327 */
331 - public function update_settings(array $settings, string $category, string $context_type = 'site', ?int $context_id = null): bool {
328 + public function update_settings(array $settings, string $category, string $context_type = 'site', ?int $context_id = null): ?bool {
329 + // Unknown here means "not this store's category", not "the write
330 + // failed" — the caller may still have a dedicated manager that owns it
331 + // (#371). Failing closed made those saves report 500 after committing.
332 332 if (!isset($this->settings_categories[$category])) {
333 - return false;
333 + return null;
334 334 }
335 335
336 336 $category_config = $this->settings_categories[$category];
337 337
@@ -450,8 +450,23 @@
450 450 * @since 1.0.0
451 451 *
452 452 * @return array Categories information
453 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 +
454 469 public function get_categories(): array {
455 470 $categories = [];
456 471
457 472 foreach ($this->settings_categories as $key => $config) {
@@ -625,8 +640,16 @@
625 640 * @return array Core settings for category
626 641 */
627 642 private function get_core_settings_by_category(string $category): array {
628 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 +
629 652 $settings = [];
630 653
631 654 foreach ($category_config['keys'] as $key) {
632 655 $settings[$key] = $this->core_settings->get($key);
@@ -710,11 +733,12 @@
710 733 * @param array $settings Settings to update
711 734 * @param string $category Category name
712 735 * @param string $context_type Context type
713 736 * @param int|null $context_id Context ID
714 - * @return bool Success status
737 + * @return bool|null True on success, false on failure, null when the SEO
738 + * store does not own the category.
715 739 */
716 - private function update_seo_settings_by_category(array $settings, string $category, string $context_type, ?int $context_id): bool {
740 + private function update_seo_settings_by_category(array $settings, string $category, string $context_type, ?int $context_id): ?bool {
717 741 return $this->seo_settings->save_settings_by_category($context_type, $context_id, $settings, $category);
718 742 }
719 743
720 744 /**
@@ -808,11 +832,15 @@
808 832 }
809 833 break;
810 834
811 835 case 'ai_provider':
812 - 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)) {
813 840 $validation['valid'] = false;
814 - $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);
815 843 }
816 844 break;
817 845
818 846 case 'dashboard_widgets':