PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.10.0 2.9.0 2.8.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 All 51 releases
← All changes | includes/api/class-settings-management-endpoint.php +66 -11 2.4.0 → 2.10.0 View file →
@@ -159,8 +159,39 @@
159 159 return $this->seo_managers[$category];
160 160 }
161 161
162 162 /**
163 + * Read a category from whichever store actually owns it.
164 + *
165 + * The generic store returns `[]` for the eight SEO categories: it looks for
166 + * rows whose key carries a `<category>_` prefix, and the rows carry no such
167 + * prefix — `social_media` is stored as `social_meta`, `schema_management` as
168 + * `schema_management_system`, and their keys are bare (`og_site_name`). So a
169 + * direct `Settings_Manager::get_settings()` reports a configured site as
170 + * having no settings at all.
171 + *
172 + * Writes never had the problem, because the write path already falls back to
173 + * the owning manager. That asymmetry is what made this invisible from the UI
174 + * and dangerous underneath it: the pre-reset rollback snapshotted `[]` and
175 + * then defaults were written over live settings, so Reset could not be undone
176 + * (#689). Every read goes through here now, so there is one place to be wrong.
177 + *
178 + * @since 2.7.0
179 + *
180 + * @param string $category Category key.
181 + * @param string $context_type Optional. Context type. Default 'site'.
182 + * @param int|null $context_id Optional. Context ID.
183 + * @return array The category's stored settings.
184 + */
185 + private function read_category(string $category, string $context_type = 'site', ?int $context_id = null): array {
186 + if ($this->has_seo_manager($category)) {
187 + return (array) $this->get_seo_manager($category)->get_settings($context_type, $context_id);
188 + }
189 +
190 + return (array) $this->settings_manager->get_settings($category, $context_type, $context_id);
191 + }
192 +
193 + /**
163 194 * Register API routes
164 195 *
165 196 * @since 1.0.0
166 197 */
@@ -331,8 +362,9 @@
331 362 'openai_api_key',
332 363 'claude_api_key',
333 364 'gemini_api_key',
334 365 'openrouter_api_key',
366 + 'openai_compatible_api_key',
335 367 'google_analytics_api_key',
336 368 'google_search_console_api_key',
337 369 'google_pagespeed_api_key',
338 370 'google_access_token',
@@ -496,10 +528,10 @@
496 528 if (!isset($this->setting_categories[$category])) {
497 529 continue;
498 530 }
499 531
500 - // Get settings for each category using Settings Manager
501 - $category_settings = $this->settings_manager->get_settings($category);
532 + // Get settings for each category from the store that owns it.
533 + $category_settings = $this->read_category($category);
502 534 $global_settings[$category] = $category_settings;
503 535
504 536 // Get schema if requested
505 537 if ($include_schema && $this->has_seo_manager($category)) {
@@ -635,9 +667,9 @@
635 667 // Get updated settings
636 668 $updated_settings = [];
637 669 foreach (array_keys($settings) as $category) {
638 670 if (isset($this->setting_categories[$category])) {
639 - $updated_settings[$category] = $this->settings_manager->get_settings($category);
671 + $updated_settings[$category] = $this->read_category($category);
640 672 }
641 673 }
642 674
643 675 return new WP_REST_Response([
@@ -682,9 +714,9 @@
682 714 );
683 715 }
684 716
685 717 // Get category settings
686 - $category_settings = $this->settings_manager->get_settings($category);
718 + $category_settings = $this->read_category($category);
687 719
688 720 // Get schema if requested
689 721 $schema = [];
690 722 if ($include_schema && $this->has_seo_manager($category)) {
@@ -916,11 +948,16 @@
916 948 // Get updated settings. Read them back from whichever store actually
917 949 // owns the category: the generic store returns [] for the categories it
918 950 // does not know, which would report a successful save as zero settings
919 951 // and hand the UI an empty form to render (#371).
920 - $updated_settings = null === $generic_update && $this->has_seo_manager($category)
921 - ? $this->get_seo_manager($category)->get_settings($context_type, $context_id)
922 - : $this->settings_manager->get_settings($category, $context_type, $context_id);
952 + //
953 + // Which store *accepted the write* is the wrong question to ask here,
954 + // and `sitemap` is the case that proves it: the generic store claims
955 + // that write (update_settings() returns true, not null) and then reads
956 + // the category back as [], so keying off $generic_update sent the one
957 + // read path that had been fixed straight back into the empty store.
958 + // Ownership is a property of the category, not of the last write (#689).
959 + $updated_settings = $this->read_category($category, $context_type, $context_id);
923 960
924 961 return new WP_REST_Response([
925 962 'success' => true,
926 963 'data' => [
@@ -1097,9 +1134,9 @@
1097 1134 if (!isset($this->setting_categories[$category])) {
1098 1135 continue;
1099 1136 }
1100 1137
1101 - $export_data[$category] = $this->settings_manager->get_settings($category);
1138 + $export_data[$category] = $this->read_category($category);
1102 1139 }
1103 1140
1104 1141 // Never let secrets (API keys, OAuth tokens) leave the site in an
1105 1142 // export file — strip them entirely.
@@ -1235,9 +1272,9 @@
1235 1272 }
1236 1273
1237 1274 try {
1238 1275 // Check if settings exist and handle overwrite
1239 - $existing_settings = $this->settings_manager->get_settings($category);
1276 + $existing_settings = $this->read_category($category);
1240 1277
1241 1278 if (!empty($existing_settings) && !$overwrite_existing) {
1242 1279 $import_results[$category] = [
1243 1280 'success' => false,
@@ -1310,9 +1347,9 @@
1310 1347 // Create backup data
1311 1348 $backup_data = [];
1312 1349 foreach ($categories as $category) {
1313 1350 if (isset($this->setting_categories[$category])) {
1314 - $backup_data[$category] = $this->settings_manager->get_settings($category);
1351 + $backup_data[$category] = $this->read_category($category);
1315 1352 }
1316 1353 }
1317 1354
1318 1355 // Create backup metadata
@@ -2236,9 +2273,27 @@
2236 2273 private function create_settings_snapshot(array $categories, string $label): string {
2237 2274 $backup_data = [];
2238 2275 foreach ($categories as $category) {
2239 2276 if (isset($this->setting_categories[$category])) {
2240 - $backup_data[$category] = $this->settings_manager->get_settings($category);
2277 + $backup_data[$category] = $this->read_category($category);
2278 + }
2279 + }
2280 +
2281 + // A snapshot that captured nothing for a category that does hold settings
2282 + // is worse than no snapshot: reset checks only that an id came back, so an
2283 + // empty one is accepted as a rollback point and the defaults go over live
2284 + // data that can no longer be recovered. That is exactly what #689 was.
2285 + //
2286 + // Ask the owning manager directly rather than trusting read_category(),
2287 + // so this stays a real check if a future edit sends a read back to the
2288 + // wrong store instead of quietly agreeing with it.
2289 + foreach ($backup_data as $category => $captured) {
2290 + if (!empty($captured) || !$this->has_seo_manager($category)) {
2291 + continue;
2292 + }
2293 +
2294 + if (!empty((array) $this->get_seo_manager($category)->get_settings('site', null))) {
2295 + return '';
2241 2296 }
2242 2297 }
2243 2298
2244 2299 $backup_metadata = [