PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | includes/class-metasync-plugin-sync.php +30 -15 2.7.0 → 2.7.2 View file →
@@ -61,8 +61,9 @@
61 61 '_metasync_seo_title',
62 62 '_metasync_seo_desc',
63 63 '_metasync_metatitle',
64 64 '_metasync_metadesc',
65 + '_metasync_metakeywords',
65 66 '_metasync_robots_index',
66 67 '_metasync_robots_advanced',
67 68 '_metasync_og_title',
68 69 '_metasync_og_description',
@@ -80,8 +81,9 @@
80 81 '_metasync_otto_og_description',
81 82 '_metasync_otto_twitter_title',
82 83 '_metasync_otto_twitter_description',
83 84 '_metasync_otto_keywords',
85 + '_metasync_otto_canonical',
84 86 ];
85 87
86 88 /**
87 89 * Internal OTTO meta keys mapped to the OTTO Persistence setting that
@@ -102,8 +104,9 @@
102 104 const OTTO_PERSISTENCE_KEYS = [
103 105 '_metasync_otto_title' => 'meta_title',
104 106 '_metasync_otto_description' => 'meta_description',
105 107 '_metasync_otto_keywords' => 'meta_keywords',
108 + '_metasync_metakeywords' => 'meta_keywords',
106 109 '_metasync_otto_og_title' => 'og_title',
107 110 '_metasync_otto_og_description' => 'og_description',
108 111 '_metasync_otto_twitter_title' => 'twitter_title',
109 112 '_metasync_otto_twitter_description' => 'twitter_description',
@@ -253,8 +256,9 @@
253 256 '_metasync_otto_og_description' => 'og_desc',
254 257 '_metasync_otto_twitter_title' => 'twitter_title',
255 258 '_metasync_otto_twitter_description' => 'twitter_desc',
256 259 '_metasync_otto_keywords' => 'focus_keyword',
260 + '_metasync_metakeywords' => 'focus_keyword',
257 261 // Legacy meta box keys → rebuild JSON
258 262 'metasync_common_robots' => '_legacy_robots_to_json',
259 263 'metasync_advance_robots' => '_legacy_robots_to_json',
260 264 ];
@@ -559,9 +563,13 @@
559 563 // Canonical, focus keyword, breadcrumb
560 564 // Canonical is validated at the source so a corrupted value ("Array")
561 565 // never propagates into Yoast/RankMath/AIOSEO storage.
562 566 $data['canonical'] = Metasync_Canonical_Sanitizer::sanitize($get('_metasync_canonical_url'));
563 - $data['focus_keyword'] = $first('_metasync_focus_keyword', '_metasync_otto_keywords');
567 + // The keyword takes its order from the resolver — customer value, then
568 + // OTTO's volatile tier, then OTTO's persisted tier — so the persistence
569 + // gating in $first() reaches the persisted copy too. Previously the
570 + // persisted copy sat on the customer key, outranking the live one.
571 + $data['focus_keyword'] = $first(...Metasync_Seo_Precedence::keys(Metasync_Seo_Precedence::FIELD_FOCUS_KEYWORD));
564 572 $data['breadcrumb_title'] = $get('_metasync_breadcrumb_title');
565 573
566 574 return $this->apply_feature_flags_to_payload($data);
567 575 }
@@ -857,9 +865,9 @@
857 865 $updates['twitter_title'] = mb_substr($data['twitter_title'], 0, 191);
858 866 }
859 867 if (!empty($data['twitter_card'])) {
860 868 // twitter_card column only exists in newer Yoast versions; skip if absent
861 - $col_check = $wpdb->get_var($wpdb->prepare(
869 + $col_check = $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
862 870 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'twitter_card'",
863 871 DB_NAME,
864 872 $indexable_table
865 873 ));
@@ -877,10 +885,10 @@
877 885 $updates['breadcrumb_title'] = mb_substr($data['breadcrumb_title'], 0, 191);
878 886 }
879 887
880 888 if (!empty($updates)) {
881 - $row_exists = $wpdb->get_var($wpdb->prepare(
882 - "SELECT id FROM {$indexable_table} WHERE object_id = %d AND object_type = 'post'",
889 + $row_exists = $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
890 + "SELECT id FROM {$wpdb->prefix}yoast_indexable WHERE object_id = %d AND object_type = 'post'",
883 891 $post_id
884 892 ));
885 893
886 894 // This probe decides two things at once: the value of the write-once
@@ -906,9 +914,9 @@
906 914 return $wrote;
907 915 }
908 916
909 917 if ($row_exists) {
910 - $wpdb->update(
918 + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
911 919 $indexable_table,
912 920 $updates,
913 921 ['object_id' => $post_id, 'object_type' => 'post']
914 922 );
@@ -944,9 +952,9 @@
944 952 // still reports what landed. Claiming nothing was written would
945 953 // tell the conflict handler this post is unsynced to Yoast while
946 954 // Yoast's own meta holds our values, and it would hand tag
947 955 // ownership to the wrong plugin.
948 - if ($wpdb->insert($indexable_table, $insert) === false) {
956 + if ($wpdb->insert($indexable_table, $insert) === false) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
949 957 Metasync_Seo_Backup::discard_backups('post', $post_id, $backups_created);
950 958 return $wrote;
951 959 }
952 960 }
@@ -1006,11 +1014,13 @@
1006 1014
1007 1015 $current = null;
1008 1016 if ($row_existed) {
1009 1017 $select = '`' . implode('`, `', array_map('esc_sql', $columns)) . '`';
1010 - $current = $wpdb->get_row(
1018 + // Column list is esc_sql()'d above; the table is a fixed literal.
1019 + $current = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1011 1020 $wpdb->prepare(
1012 - "SELECT {$select} FROM {$table} WHERE object_id = %d AND object_type = 'post'",
1021 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- column list esc_sql()'d above, table is a fixed literal
1022 + "SELECT {$select} FROM {$wpdb->prefix}yoast_indexable WHERE object_id = %d AND object_type = 'post'",
1013 1023 $post_id
1014 1024 ),
1015 1025 ARRAY_A
1016 1026 );
@@ -1245,10 +1255,15 @@
1245 1255
1246 1256 $current = null;
1247 1257 if ($row_existed) {
1248 1258 $select = '`' . implode('`, `', array_map('esc_sql', $columns)) . '`';
1249 - $current = $wpdb->get_row(
1250 - $wpdb->prepare("SELECT {$select} FROM {$table} WHERE post_id = %d", $post_id),
1259 + // Column list is esc_sql()'d above; the table is a fixed literal.
1260 + $current = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1261 + $wpdb->prepare(
1262 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- column list esc_sql()'d above, table is a fixed literal
1263 + "SELECT {$select} FROM {$wpdb->prefix}aioseo_posts WHERE post_id = %d",
1264 + $post_id
1265 + ),
1251 1266 ARRAY_A
1252 1267 );
1253 1268
1254 1269 // The row was there a moment ago, so a null answer now is a failed
@@ -1317,9 +1332,9 @@
1317 1332
1318 1333 $table = $wpdb->prefix . 'aioseo_posts';
1319 1334
1320 1335 // Bail if the AIOSEO post table does not exist (plugin not initialised).
1321 - $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
1336 + $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1322 1337 if ($table_exists !== $table) {
1323 1338 return false;
1324 1339 }
1325 1340
@@ -1426,10 +1441,10 @@
1426 1441 }
1427 1442
1428 1443 $row['updated'] = current_time('mysql');
1429 1444
1430 - $existing_id = $wpdb->get_var($wpdb->prepare(
1431 - "SELECT id FROM {$table} WHERE post_id = %d",
1445 + $existing_id = $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1446 + "SELECT id FROM {$wpdb->prefix}aioseo_posts WHERE post_id = %d",
1432 1447 $post_id
1433 1448 ));
1434 1449
1435 1450 // An unreadable probe cannot be treated as "no row". It would commit a
@@ -1448,9 +1463,9 @@
1448 1463 if (!$this->backup_aioseo_columns($post_id, $table, $row, true, $backups_created)) {
1449 1464 return false;
1450 1465 }
1451 1466
1452 - return ($wpdb->update($table, $row, ['post_id' => $post_id]) !== false) && $wrote;
1467 + return ($wpdb->update($table, $row, ['post_id' => $post_id]) !== false) && $wrote; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1453 1468 }
1454 1469
1455 1470 $backups_created = [];
1456 1471 if (!$this->backup_aioseo_columns($post_id, $table, $row, false, $backups_created)) {
@@ -1471,9 +1486,9 @@
1471 1486 'robots_notranslate' => 0,
1472 1487 ];
1473 1488 $row = array_merge($robot_defaults, $row);
1474 1489
1475 - $inserted = $wpdb->insert($table, $row);
1490 + $inserted = $wpdb->insert($table, $row); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- mirrors SEO meta into third-party plugin storage (Yoast indexables/AIOSEO tables) — no WordPress API for their schemas
1476 1491
1477 1492 // The row_existed='0' marker was recorded before the insert, because a
1478 1493 // marker that will not save has to be able to veto the write. If the
1479 1494 // insert then failed there is no row of ours, and leaving the marker