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/api/class-settings-management-endpoint.php +65 -11 2.6.02.7.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 */
@@ -496,10 +527,10 @@
496 527 if (!isset($this->setting_categories[$category])) {
497 528 continue;
498 529 }
499 530
500 - // Get settings for each category using Settings Manager
501 - $category_settings = $this->settings_manager->get_settings($category);
531 + // Get settings for each category from the store that owns it.
532 + $category_settings = $this->read_category($category);
502 533 $global_settings[$category] = $category_settings;
503 534
504 535 // Get schema if requested
505 536 if ($include_schema && $this->has_seo_manager($category)) {
@@ -635,9 +666,9 @@
635 666 // Get updated settings
636 667 $updated_settings = [];
637 668 foreach (array_keys($settings) as $category) {
638 669 if (isset($this->setting_categories[$category])) {
639 - $updated_settings[$category] = $this->settings_manager->get_settings($category);
670 + $updated_settings[$category] = $this->read_category($category);
640 671 }
641 672 }
642 673
643 674 return new WP_REST_Response([
@@ -682,9 +713,9 @@
682 713 );
683 714 }
684 715
685 716 // Get category settings
686 - $category_settings = $this->settings_manager->get_settings($category);
717 + $category_settings = $this->read_category($category);
687 718
688 719 // Get schema if requested
689 720 $schema = [];
690 721 if ($include_schema && $this->has_seo_manager($category)) {
@@ -916,11 +947,16 @@
916 947 // Get updated settings. Read them back from whichever store actually
917 948 // owns the category: the generic store returns [] for the categories it
918 949 // does not know, which would report a successful save as zero settings
919 950 // 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);
951 + //
952 + // Which store *accepted the write* is the wrong question to ask here,
953 + // and `sitemap` is the case that proves it: the generic store claims
954 + // that write (update_settings() returns true, not null) and then reads
955 + // the category back as [], so keying off $generic_update sent the one
956 + // read path that had been fixed straight back into the empty store.
957 + // Ownership is a property of the category, not of the last write (#689).
958 + $updated_settings = $this->read_category($category, $context_type, $context_id);
923 959
924 960 return new WP_REST_Response([
925 961 'success' => true,
926 962 'data' => [
@@ -1097,9 +1133,9 @@
1097 1133 if (!isset($this->setting_categories[$category])) {
1098 1134 continue;
1099 1135 }
1100 1136
1101 - $export_data[$category] = $this->settings_manager->get_settings($category);
1137 + $export_data[$category] = $this->read_category($category);
1102 1138 }
1103 1139
1104 1140 // Never let secrets (API keys, OAuth tokens) leave the site in an
1105 1141 // export file — strip them entirely.
@@ -1235,9 +1271,9 @@
1235 1271 }
1236 1272
1237 1273 try {
1238 1274 // Check if settings exist and handle overwrite
1239 - $existing_settings = $this->settings_manager->get_settings($category);
1275 + $existing_settings = $this->read_category($category);
1240 1276
1241 1277 if (!empty($existing_settings) && !$overwrite_existing) {
1242 1278 $import_results[$category] = [
1243 1279 'success' => false,
@@ -1310,9 +1346,9 @@
1310 1346 // Create backup data
1311 1347 $backup_data = [];
1312 1348 foreach ($categories as $category) {
1313 1349 if (isset($this->setting_categories[$category])) {
1314 - $backup_data[$category] = $this->settings_manager->get_settings($category);
1350 + $backup_data[$category] = $this->read_category($category);
1315 1351 }
1316 1352 }
1317 1353
1318 1354 // Create backup metadata
@@ -2236,9 +2272,27 @@
2236 2272 private function create_settings_snapshot(array $categories, string $label): string {
2237 2273 $backup_data = [];
2238 2274 foreach ($categories as $category) {
2239 2275 if (isset($this->setting_categories[$category])) {
2240 - $backup_data[$category] = $this->settings_manager->get_settings($category);
2276 + $backup_data[$category] = $this->read_category($category);
2277 + }
2278 + }
2279 +
2280 + // A snapshot that captured nothing for a category that does hold settings
2281 + // is worse than no snapshot: reset checks only that an id came back, so an
2282 + // empty one is accepted as a rollback point and the defaults go over live
2283 + // data that can no longer be recovered. That is exactly what #689 was.
2284 + //
2285 + // Ask the owning manager directly rather than trusting read_category(),
2286 + // so this stays a real check if a future edit sends a read back to the
2287 + // wrong store instead of quietly agreeing with it.
2288 + foreach ($backup_data as $category => $captured) {
2289 + if (!empty($captured) || !$this->has_seo_manager($category)) {
2290 + continue;
2291 + }
2292 +
2293 + if (!empty((array) $this->get_seo_manager($category)->get_settings('site', null))) {
2294 + return '';
2241 2295 }
2242 2296 }
2243 2297
2244 2298 $backup_metadata = [