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-global-seo-endpoint.php +31 -7 2.0.22.7.0 View file →
@@ -15,8 +15,9 @@
15 15 declare(strict_types=1);
16 16
17 17 namespace ThinkRank\API;
18 18
19 +use ThinkRank\SEO\Pattern_Resolver;
19 20 use WP_REST_Controller;
20 21 use WP_REST_Request;
21 22 use WP_REST_Response;
22 23 use WP_Error;
@@ -238,18 +239,28 @@
238 239 // Capture the previously-stored value so a genuine write failure can be
239 240 // told apart from a no-op save (payload identical to what's stored).
240 241 $previous = $all_settings[$post_type] ?? null;
241 242
243 + // MERGE, never replace. This entity row is shared: the Content Type
244 + // Matrix stores its per-feature tri-states (meta_enabled, schema_enabled,
245 + // open_graph_enabled, twitter_card_enabled, analytics_enabled) and its
246 + // sitemap_include flag under the SAME key, and normalize_settings_patch()
247 + // whitelists only the Global SEO fields — so a wholesale replace here
248 + // silently dropped every matrix choice the moment the user saved the
249 + // Global SEO screen next door. Global SEO's own keys still win, because
250 + // sanitize_settings() emits them complete.
251 + $merged = array_merge(is_array($previous) ? $previous : [], $sanitized_settings);
252 +
242 253 // Update settings for this post type
243 - $all_settings[$post_type] = $sanitized_settings;
254 + $all_settings[$post_type] = $merged;
244 255
245 256 // Save to database
246 257 $updated = update_option(self::OPTION_NAME, $all_settings);
247 258
248 - if ($updated || $previous === $sanitized_settings) {
259 + if ($updated || $previous === $merged) {
249 260 return new WP_REST_Response([
250 261 'success' => true,
251 - 'data' => $sanitized_settings,
262 + 'data' => $merged,
252 263 'post_type' => $post_type,
253 264 'message' => sprintf('Settings saved successfully for post type: %s', $post_type)
254 265 ], 200);
255 266 }
@@ -299,11 +310,22 @@
299 310
300 311 // Get all settings
301 312 $all_settings = get_option(self::OPTION_NAME, []);
302 313
303 - // Remove settings for this post type (will fall back to defaults)
304 - unset($all_settings[$post_type]);
314 + // Reset only what this screen owns. The entity row is shared with the
315 + // Content Type Matrix, so dropping the whole row would reset the user's
316 + // per-feature tri-states and sitemap choice as a side effect of a button
317 + // that says nothing about them. DEFAULT_SETTINGS is the exact set of
318 + // Global SEO keys, so anything outside it belongs to another screen.
319 + $stored = $all_settings[$post_type] ?? [];
320 + $kept = is_array($stored) ? array_diff_key($stored, self::DEFAULT_SETTINGS) : [];
305 321
322 + if ($kept === []) {
323 + unset($all_settings[$post_type]);
324 + } else {
325 + $all_settings[$post_type] = $kept;
326 + }
327 +
306 328 // Save updated settings
307 329 update_option(self::OPTION_NAME, $all_settings);
308 330
309 331 // Get default settings
@@ -471,13 +493,15 @@
471 493 */
472 494 public static function normalize_settings_patch(array $settings): array {
473 495 $out = [];
474 496
497 + // Templates, not plain text: sanitize_text_field() would eat %date% and
498 + // %category% as percent-encoding and store "te%" / "tegory%" (#521).
475 499 if (isset($settings['title'])) {
476 - $out['title'] = sanitize_text_field((string) $settings['title']);
500 + $out['title'] = Pattern_Resolver::sanitize_template((string) $settings['title']);
477 501 }
478 502 if (isset($settings['description'])) {
479 - $out['description'] = sanitize_text_field((string) $settings['description']);
503 + $out['description'] = Pattern_Resolver::sanitize_template((string) $settings['description']);
480 504 }
481 505 if (isset($settings['schema_type'])) {
482 506 $value = sanitize_text_field((string) $settings['schema_type']);
483 507 $out['schema_type'] = in_array($value, self::ALLOWED_SCHEMA_TYPES, true) ? $value : 'WebPage';