PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
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-external-importer.php +96 -60 2.6.26 → 2.7.1 View file →
@@ -176,8 +176,9 @@
176 176 $count = 0;
177 177
178 178 switch ($plugin) {
179 179 case 'yoast':
180 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
180 181 $count = (int) $wpdb->get_var("
181 182 SELECT COUNT(DISTINCT post_id)
182 183 FROM {$wpdb->postmeta}
183 184 WHERE meta_key LIKE '_yoast_wpseo_%'
@@ -185,22 +186,23 @@
185 186 ");
186 187 break;
187 188
188 189 case 'rankmath':
189 - $count = (int) $wpdb->get_var("
190 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
191 + $count = (int) $wpdb->get_var($wpdb->prepare("
190 192 SELECT COUNT(DISTINCT post_id)
191 193 FROM {$wpdb->postmeta}
192 - WHERE meta_key LIKE 'rank_math_%'
194 + WHERE meta_key LIKE %s
193 195 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
194 - ");
196 + ", 'rank_math_%'));
195 197 break;
196 198
197 199 case 'aioseo':
198 200 $table = $wpdb->prefix . 'aioseo_posts';
199 - if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
201 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
200 202 $count = (int) $wpdb->get_var("
201 203 SELECT COUNT(post_id)
202 - FROM {$table}
204 + FROM {$wpdb->prefix}aioseo_posts
203 205 WHERE post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
204 206 ");
205 207 }
206 208 break;
@@ -222,8 +224,9 @@
222 224 case 'yoast':
223 225 // Check for Yoast schema meta
224 226 // Yoast Premium stores schema type in _yoast_wpseo_schema_article_type
225 227 // Free version may use _yoast_wpseo_schema (JSON)
228 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
226 229 $count = (int) $wpdb->get_var("
227 230 SELECT COUNT(DISTINCT post_id)
228 231 FROM {$wpdb->postmeta}
229 232 WHERE (meta_key = '_yoast_wpseo_schema' OR meta_key = '_yoast_wpseo_schema_article_type')
@@ -234,8 +237,9 @@
234 237 case 'rankmath':
235 238 // Check for Rank Math schema meta (any schema type)
236 239 // Rank Math uses meta keys like: rank_math_schema_Article, rank_math_schema_BlogPosting, rank_math_schema_Product, etc.
237 240 // Exclude shortcode schemas (rank_math_shortcode_schema_*)
241 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
238 242 $count = (int) $wpdb->get_var("
239 243 SELECT COUNT(DISTINCT post_id)
240 244 FROM {$wpdb->postmeta}
241 245 WHERE meta_key LIKE 'rank_math_schema_%'
@@ -246,12 +250,12 @@
246 250
247 251 case 'aioseo':
248 252 // Check for AIOSEO schema in table
249 253 $table = $wpdb->prefix . 'aioseo_posts';
250 - if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
254 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
251 255 $count = (int) $wpdb->get_var("
252 256 SELECT COUNT(post_id)
253 - FROM {$table}
257 + FROM {$wpdb->prefix}aioseo_posts
254 258 WHERE (schema_type IS NOT NULL AND schema_type != '')
255 259 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
256 260 ");
257 261 }
@@ -272,8 +276,9 @@
272 276
273 277 switch ($plugin) {
274 278 case 'yoast':
275 279 // Check for Yoast SEO title or description
280 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
276 281 $count = (int) $wpdb->get_var("
277 282 SELECT COUNT(DISTINCT post_id)
278 283 FROM {$wpdb->postmeta}
279 284 WHERE (meta_key = '_yoast_wpseo_title' OR meta_key = '_yoast_wpseo_metadesc')
@@ -283,8 +288,9 @@
283 288 break;
284 289
285 290 case 'rankmath':
286 291 // Check for Rank Math title or description
292 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
287 293 $count = (int) $wpdb->get_var("
288 294 SELECT COUNT(DISTINCT post_id)
289 295 FROM {$wpdb->postmeta}
290 296 WHERE (meta_key = 'rank_math_title' OR meta_key = 'rank_math_description')
@@ -295,12 +301,12 @@
295 301
296 302 case 'aioseo':
297 303 // Check for AIOSEO title or description in their custom table
298 304 $table = $wpdb->prefix . 'aioseo_posts';
299 - if ($wpdb->get_var("SHOW TABLES LIKE '$table'") === $table) {
305 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
300 306 $count = (int) $wpdb->get_var("
301 307 SELECT COUNT(post_id)
302 - FROM {$table}
308 + FROM {$wpdb->prefix}aioseo_posts
303 309 WHERE ((title IS NOT NULL AND title != '') OR (description IS NOT NULL AND description != ''))
304 310 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
305 311 ");
306 312
@@ -315,9 +321,9 @@
315 321 if (!empty($templates)) {
316 322 $post_types = array_keys($templates);
317 323 $type_placeholders = implode(', ', array_fill(0, count($post_types), '%s'));
318 324
319 - $template_count = (int) $wpdb->get_var($wpdb->prepare(
325 + $template_count = (int) $wpdb->get_var($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
320 326 "SELECT COUNT(*)
321 327 FROM {$wpdb->posts}
322 328 WHERE post_status = 'publish'
323 329 AND post_type IN ({$type_placeholders})",
@@ -347,8 +353,9 @@
347 353 $count = 0;
348 354
349 355 switch ($plugin) {
350 356 case 'yoast':
357 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
351 358 $count = (int) $wpdb->get_var("
352 359 SELECT COUNT(DISTINCT post_id)
353 360 FROM {$wpdb->postmeta}
354 361 WHERE meta_key = '_yoast_wpseo_primary_category'
@@ -357,8 +364,9 @@
357 364 ");
358 365 break;
359 366
360 367 case 'rankmath':
368 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
361 369 $count = (int) $wpdb->get_var("
362 370 SELECT COUNT(DISTINCT post_id)
363 371 FROM {$wpdb->postmeta}
364 372 WHERE meta_key = 'rank_math_primary_category'
@@ -367,8 +375,9 @@
367 375 ");
368 376 break;
369 377
370 378 case 'aioseo':
379 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
371 380 $count = (int) $wpdb->get_var("
372 381 SELECT COUNT(DISTINCT post_id)
373 382 FROM {$wpdb->postmeta}
374 383 WHERE meta_key = '_aioseo_primary_category'
@@ -429,8 +438,9 @@
429 438 $batch_size = intval($options['batch_size']);
430 439 $offset = intval($options['offset']);
431 440 $overwrite = (bool) $options['overwrite_existing'];
432 441
442 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
433 443 $total_posts = (int) $wpdb->get_var("
434 444 SELECT COUNT(DISTINCT post_id)
435 445 FROM {$wpdb->postmeta}
436 446 WHERE meta_key = '_yoast_wpseo_primary_category'
@@ -437,8 +447,9 @@
437 447 AND meta_value != ''
438 448 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
439 449 ");
440 450
451 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
441 452 $posts = $wpdb->get_results($wpdb->prepare("
442 453 SELECT DISTINCT post_id
443 454 FROM {$wpdb->postmeta}
444 455 WHERE meta_key = '_yoast_wpseo_primary_category'
@@ -492,8 +503,9 @@
492 503 $batch_size = intval($options['batch_size']);
493 504 $offset = intval($options['offset']);
494 505 $overwrite = (bool) $options['overwrite_existing'];
495 506
507 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
496 508 $total_posts = (int) $wpdb->get_var("
497 509 SELECT COUNT(DISTINCT post_id)
498 510 FROM {$wpdb->postmeta}
499 511 WHERE meta_key = 'rank_math_primary_category'
@@ -500,8 +512,9 @@
500 512 AND meta_value != ''
501 513 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
502 514 ");
503 515
516 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
504 517 $posts = $wpdb->get_results($wpdb->prepare("
505 518 SELECT DISTINCT post_id
506 519 FROM {$wpdb->postmeta}
507 520 WHERE meta_key = 'rank_math_primary_category'
@@ -555,8 +568,9 @@
555 568 $batch_size = intval($options['batch_size']);
556 569 $offset = intval($options['offset']);
557 570 $overwrite = (bool) $options['overwrite_existing'];
558 571
572 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
559 573 $total_posts = (int) $wpdb->get_var("
560 574 SELECT COUNT(DISTINCT post_id)
561 575 FROM {$wpdb->postmeta}
562 576 WHERE meta_key = '_aioseo_primary_category'
@@ -563,8 +577,9 @@
563 577 AND meta_value != ''
564 578 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
565 579 ");
566 580
581 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
567 582 $posts = $wpdb->get_results($wpdb->prepare("
568 583 SELECT DISTINCT post_id
569 584 FROM {$wpdb->postmeta}
570 585 WHERE meta_key = '_aioseo_primary_category'
@@ -762,24 +777,26 @@
762 777 $batch_size = intval($options['batch_size']);
763 778 $offset = intval($options['offset']);
764 779
765 780 // Get total count (for progress tracking)
766 - $total = (int) $wpdb->get_var("
781 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
782 + $total = (int) $wpdb->get_var($wpdb->prepare("
767 783 SELECT COUNT(DISTINCT post_id)
768 784 FROM {$wpdb->postmeta}
769 - WHERE meta_key LIKE '_yoast_wpseo_%'
785 + WHERE meta_key LIKE %s
770 786 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
771 - ");
787 + ", '_yoast_wpseo_%'));
772 788
773 789 // Get batch of posts with Yoast robots meta
790 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
774 791 $posts = $wpdb->get_results($wpdb->prepare("
775 792 SELECT DISTINCT post_id
776 793 FROM {$wpdb->postmeta}
777 - WHERE meta_key LIKE '_yoast_wpseo_%'
794 + WHERE meta_key LIKE %s
778 795 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
779 796 ORDER BY post_id ASC
780 797 LIMIT %d OFFSET %d
781 - ", $batch_size, $offset));
798 + ", '_yoast_wpseo_%', $batch_size, $offset));
782 799
783 800 foreach ($posts as $post_obj) {
784 801 $post_id = $post_obj->post_id;
785 802 $has_changes = false;
@@ -903,24 +920,26 @@
903 920 $batch_size = intval($options['batch_size']);
904 921 $offset = intval($options['offset']);
905 922
906 923 // Get total count (for progress tracking)
907 - $total = (int) $wpdb->get_var("
924 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
925 + $total = (int) $wpdb->get_var($wpdb->prepare("
908 926 SELECT COUNT(DISTINCT post_id)
909 927 FROM {$wpdb->postmeta}
910 - WHERE meta_key LIKE 'rank_math_%'
928 + WHERE meta_key LIKE %s
911 929 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
912 - ");
930 + ", 'rank_math_%'));
913 931
914 932 // Get batch of posts with Rank Math robots meta
933 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
915 934 $posts = $wpdb->get_results($wpdb->prepare("
916 935 SELECT DISTINCT post_id
917 936 FROM {$wpdb->postmeta}
918 - WHERE meta_key LIKE 'rank_math_%'
937 + WHERE meta_key LIKE %s
919 938 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
920 939 ORDER BY post_id ASC
921 940 LIMIT %d OFFSET %d
922 - ", $batch_size, $offset));
941 + ", 'rank_math_%', $batch_size, $offset));
923 942
924 943 foreach ($posts as $post_obj) {
925 944 $post_id = $post_obj->post_id;
926 945 $has_changes = false;
@@ -1043,9 +1062,9 @@
1043 1062 // AIOSEO stores data in a custom table
1044 1063 $aioseo_table = $wpdb->prefix . 'aioseo_posts';
1045 1064
1046 1065 // Check if table exists
1047 - $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$aioseo_table'") === $aioseo_table;
1066 + $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($aioseo_table))) === $aioseo_table; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1048 1067
1049 1068 if (!$table_exists) {
1050 1069 return ['success' => false, 'message' => 'AIOSEO table not found.'];
1051 1070 }
@@ -1050,21 +1069,23 @@
1050 1069 return ['success' => false, 'message' => 'AIOSEO table not found.'];
1051 1070 }
1052 1071
1053 1072 // Get total count (for progress tracking)
1073 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1054 1074 $total = (int) $wpdb->get_var("
1055 1075 SELECT COUNT(*)
1056 - FROM {$aioseo_table}
1076 + FROM {$wpdb->prefix}aioseo_posts
1057 1077 WHERE post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
1058 1078 ");
1059 1079
1060 1080 // Get batch of posts with AIOSEO settings
1081 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1061 1082 $posts = $wpdb->get_results($wpdb->prepare("
1062 1083 SELECT post_id, robots_default, robots_noindex, robots_nofollow,
1063 1084 robots_noarchive, robots_nosnippet, robots_noimageindex,
1064 1085 robots_max_snippet, robots_max_imagepreview, robots_max_videopreview,
1065 1086 canonical_url
1066 - FROM {$aioseo_table}
1087 + FROM {$wpdb->prefix}aioseo_posts
1067 1088 WHERE post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
1068 1089 ORDER BY post_id ASC
1069 1090 LIMIT %d OFFSET %d
1070 1091 ", $batch_size, $offset));
@@ -1216,8 +1237,9 @@
1216 1237 global $wpdb;
1217 1238 $imported_count = 0;
1218 1239
1219 1240 // First, try to import from full schema JSON (free version or old approach)
1241 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1220 1242 $posts = $wpdb->get_results("
1221 1243 SELECT post_id, meta_value
1222 1244 FROM {$wpdb->postmeta}
1223 1245 WHERE meta_key = '_yoast_wpseo_schema'
@@ -1242,14 +1264,15 @@
1242 1264 // Convert Yoast schema to Metasync format
1243 1265 $metasync_schema = $this->convert_yoast_schema_to_metasync($yoast_schema, $post_id);
1244 1266
1245 1267 if (!empty($metasync_schema['types'])) {
1246 - update_post_meta($post_id, 'metasync_schema_markup', $metasync_schema);
1268 + update_post_meta($post_id, 'metasync_schema_markup', wp_slash(array_merge(is_array($existing_schema) ? $existing_schema : [], $metasync_schema)));
1247 1269 $imported_count++;
1248 1270 }
1249 1271 }
1250 1272
1251 1273 // Second, try to import from schema type (Premium version)
1274 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1252 1275 $posts = $wpdb->get_results("
1253 1276 SELECT post_id, meta_value
1254 1277 FROM {$wpdb->postmeta}
1255 1278 WHERE meta_key = '_yoast_wpseo_schema_article_type'
@@ -1285,9 +1308,9 @@
1285 1308 ]
1286 1309 ]
1287 1310 ];
1288 1311
1289 - update_post_meta($post_id, 'metasync_schema_markup', $metasync_schema);
1312 + update_post_meta($post_id, 'metasync_schema_markup', wp_slash(array_merge(is_array($existing_schema) ? $existing_schema : [], $metasync_schema)));
1290 1313 $imported_count++;
1291 1314 }
1292 1315 }
1293 1316
@@ -1303,8 +1326,9 @@
1303 1326 $imported_count = 0;
1304 1327
1305 1328 // Get all posts with any Rank Math schema (dynamically detect schema types)
1306 1329 // Exclude shortcode schemas
1330 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1307 1331 $posts = $wpdb->get_results("
1308 1332 SELECT DISTINCT pm.post_id, pm.meta_key, pm.meta_value
1309 1333 FROM {$wpdb->postmeta} pm
1310 1334 WHERE pm.meta_key LIKE 'rank_math_schema_%'
@@ -1341,9 +1365,9 @@
1341 1365 // Convert Rank Math schema to Metasync format
1342 1366 $metasync_schema = $this->convert_rankmath_schema_to_metasync($rm_schema, $schema_type, $post_id);
1343 1367
1344 1368 if (!empty($metasync_schema['types'])) {
1345 - update_post_meta($post_id, 'metasync_schema_markup', $metasync_schema);
1369 + update_post_meta($post_id, 'metasync_schema_markup', wp_slash(array_merge(is_array($existing_schema) ? $existing_schema : [], $metasync_schema)));
1346 1370 $imported_count++;
1347 1371 $processed_posts[] = $post_id; // Mark post as processed
1348 1372 }
1349 1373 }
@@ -1360,16 +1384,17 @@
1360 1384 $imported_count = 0;
1361 1385
1362 1386 // Check if AIOSEO table exists
1363 1387 $table = $wpdb->prefix . 'aioseo_posts';
1364 - if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) {
1388 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) !== $table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1365 1389 return 0;
1366 1390 }
1367 1391
1368 1392 // Get all posts with AIOSEO schema
1393 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
1369 1394 $posts = $wpdb->get_results("
1370 1395 SELECT post_id, schema_type, schema_type_options
1371 - FROM {$table}
1396 + FROM {$wpdb->prefix}aioseo_posts
1372 1397 WHERE (schema_type IS NOT NULL AND schema_type != '')
1373 1398 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
1374 1399 ");
1375 1400
@@ -1395,9 +1420,9 @@
1395 1420 $post_id
1396 1421 );
1397 1422
1398 1423 if (!empty($metasync_schema['types'])) {
1399 - update_post_meta($post_id, 'metasync_schema_markup', $metasync_schema);
1424 + update_post_meta($post_id, 'metasync_schema_markup', wp_slash(array_merge(is_array($existing_schema) ? $existing_schema : [], $metasync_schema)));
1400 1425 $imported_count++;
1401 1426 }
1402 1427 }
1403 1428
@@ -2701,9 +2726,9 @@
2701 2726 'resolved' => (string) $resolved,
2702 2727 ];
2703 2728
2704 2729 if (defined('WP_DEBUG') && WP_DEBUG) {
2705 - error_log(sprintf(
2730 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets
2706 2731 'MetaSync importer: unresolved %s placeholder for post %d (%s) — %s',
2707 2732 $plugin,
2708 2733 (int) $post_id,
2709 2734 $meta_key,
@@ -3102,22 +3127,23 @@
3102 3127 ];
3103 3128 }
3104 3129
3105 3130 // Get total count (for progress tracking)
3106 - $meta_keys_placeholders = implode(',', array_fill(0, count($meta_keys), '%s'));
3107 - $total_posts = (int) $wpdb->get_var($wpdb->prepare("
3131 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3132 + $total_posts = (int) $wpdb->get_var($wpdb->prepare('
3108 3133 SELECT COUNT(DISTINCT post_id)
3109 - FROM {$wpdb->postmeta}
3110 - WHERE meta_key IN ($meta_keys_placeholders)
3134 + FROM ' . $wpdb->postmeta . '
3135 + WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ")
3111 3136 AND meta_value != ''
3112 3137 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3113 3138 ", $meta_keys));
3114 3139
3115 3140 // Get batch of posts with Yoast data
3116 - $posts = $wpdb->get_results($wpdb->prepare("
3141 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3142 + $posts = $wpdb->get_results($wpdb->prepare('
3117 3143 SELECT DISTINCT post_id
3118 - FROM {$wpdb->postmeta}
3119 - WHERE meta_key IN ($meta_keys_placeholders)
3144 + FROM ' . $wpdb->postmeta . '
3145 + WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ")
3120 3146 AND meta_value != ''
3121 3147 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3122 3148 ORDER BY post_id ASC
3123 3149 LIMIT %d OFFSET %d
@@ -3304,22 +3330,23 @@
3304 3330 ];
3305 3331 }
3306 3332
3307 3333 // Get total count
3308 - $meta_keys_placeholders = implode(',', array_fill(0, count($meta_keys), '%s'));
3309 - $total_posts = (int) $wpdb->get_var($wpdb->prepare("
3334 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3335 + $total_posts = (int) $wpdb->get_var($wpdb->prepare('
3310 3336 SELECT COUNT(DISTINCT post_id)
3311 - FROM {$wpdb->postmeta}
3312 - WHERE meta_key IN ($meta_keys_placeholders)
3337 + FROM ' . $wpdb->postmeta . '
3338 + WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ")
3313 3339 AND meta_value != ''
3314 3340 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3315 3341 ", $meta_keys));
3316 3342
3317 3343 // Get batch of posts
3318 - $posts = $wpdb->get_results($wpdb->prepare("
3344 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3345 + $posts = $wpdb->get_results($wpdb->prepare('
3319 3346 SELECT DISTINCT post_id
3320 - FROM {$wpdb->postmeta}
3321 - WHERE meta_key IN ($meta_keys_placeholders)
3347 + FROM ' . $wpdb->postmeta . '
3348 + WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ")
3322 3349 AND meta_value != ''
3323 3350 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3324 3351 ORDER BY post_id ASC
3325 3352 LIMIT %d OFFSET %d
@@ -3481,9 +3508,9 @@
3481 3508 $overwrite = (bool) $options['overwrite_existing'];
3482 3509
3483 3510 // Check if AIOSEO table exists
3484 3511 $table = $wpdb->prefix . 'aioseo_posts';
3485 - if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) {
3512 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) !== $table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3486 3513 return [
3487 3514 'success' => false,
3488 3515 'message' => 'AIOSEO database table not found.'
3489 3516 ];
@@ -3573,16 +3600,18 @@
3573 3600 // one registered non-public — can still carry per-post overrides that
3574 3601 // the override-only walk always imported. The second arm keeps every
3575 3602 // row holding real AIOSEO data in scope whatever its post type, so
3576 3603 // adding a template never removes anything from the import.
3604 + // $joined_has_value is built only from the fixed $value_columns
3605 + // allowlist above; table names are inline literals.
3577 3606 $from_clause = "FROM {$wpdb->posts} p
3578 - LEFT JOIN {$table} a ON a.post_id = p.ID
3607 + LEFT JOIN {$wpdb->prefix}aioseo_posts a ON a.post_id = p.ID
3579 3608 WHERE p.post_status = 'publish'
3580 - AND (p.post_type IN ({$type_placeholders}) OR ({$joined_has_value}))";
3609 + AND (p.post_type IN (" . implode(', ', array_fill(0, count($template_post_types), '%s')) . ") OR ({$joined_has_value}))";
3581 3610
3582 - $total_posts = (int) $wpdb->get_var(
3583 - $wpdb->prepare("SELECT COUNT(*) {$from_clause}", $template_post_types)
3584 - );
3611 + // Assembled from the literal fragments above; post types bind via prepare().
3612 + $count_query = "SELECT COUNT(*) {$from_clause}";
3613 + $total_posts = (int) $wpdb->get_var($wpdb->prepare($count_query, $template_post_types)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- fragments built from fixed allowlists, values bound via prepare()
3585 3614
3586 3615 $joined_columns = array_map(
3587 3616 function ($column) {
3588 3617 return "a.{$column}";
@@ -3594,15 +3623,14 @@
3594 3623 // least one value column to select.
3595 3624 $joined_select = 'p.ID AS post_id, p.post_type AS post_type, '
3596 3625 . implode(', ', $joined_columns);
3597 3626
3598 - $posts = $wpdb->get_results($wpdb->prepare(
3599 - "SELECT {$joined_select}
3627 + // Assembled from the literal fragments above; post types bind via prepare().
3628 + $posts_query = "SELECT {$joined_select}
3600 3629 {$from_clause}
3601 3630 ORDER BY p.ID ASC
3602 - LIMIT %d OFFSET %d",
3603 - array_merge($template_post_types, [$batch_size, $offset])
3604 - ));
3631 + LIMIT %d OFFSET %d";
3632 + $posts = $wpdb->get_results($wpdb->prepare($posts_query, array_merge($template_post_types, [$batch_size, $offset]))); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- fragments built from fixed allowlists, values bound via prepare()
3605 3633 } else {
3606 3634 // No usable templates — e.g. only the social options are selected, or
3607 3635 // aioseo_options_dynamic is missing. Fall back to the original
3608 3636 // override-only walk so those sites behave exactly as before.
@@ -3607,23 +3635,31 @@
3607 3635 // aioseo_options_dynamic is missing. Fall back to the original
3608 3636 // override-only walk so those sites behave exactly as before.
3609 3637 $select_clause = implode(', ', array_merge(['post_id'], $value_columns));
3610 3638
3639 + // $where_clause / $select_clause are built only from the fixed
3640 + // $value_columns allowlist above.
3641 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where_clause is built only from the fixed $value_columns allowlist above
3642 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3611 3643 $total_posts = (int) $wpdb->get_var("
3612 3644 SELECT COUNT(post_id)
3613 - FROM {$table}
3645 + FROM {$wpdb->prefix}aioseo_posts
3614 3646 WHERE ({$where_clause})
3615 3647 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3616 3648 ");
3649 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
3617 3650
3651 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select_clause / $where_clause are built only from the fixed $value_columns allowlist above
3652 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
3618 3653 $posts = $wpdb->get_results($wpdb->prepare("
3619 3654 SELECT {$select_clause}
3620 - FROM {$table}
3655 + FROM {$wpdb->prefix}aioseo_posts
3621 3656 WHERE ({$where_clause})
3622 3657 AND post_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_status = 'publish')
3623 3658 ORDER BY post_id ASC
3624 3659 LIMIT %d OFFSET %d
3625 3660 ", $batch_size, $offset));
3661 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
3626 3662 }
3627 3663
3628 3664 $imported_count = 0;
3629 3665 $skipped_count = 0;
@@ -4097,9 +4133,9 @@
4097 4133 $overwrite = (bool) $options['overwrite_existing'];
4098 4134
4099 4135 $table = $wpdb->prefix . 'aioseo_terms';
4100 4136
4101 - $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
4137 + $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
4102 4138 if ($table_exists !== $table) {
4103 4139 return [
4104 4140 'success' => false,
4105 4141 'message' => 'AIOSEO terms table not found.',
@@ -4105,11 +4141,11 @@
4105 4141 'message' => 'AIOSEO terms table not found.',
4106 4142 ];
4107 4143 }
4108 4144
4109 - $rows = $wpdb->get_results($wpdb->prepare(
4145 + $rows = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- one-shot import tool — bulk reads from third-party SEO plugin storage; no bulk WordPress API exists
4110 4146 "SELECT term_id, title, description, og_title, og_description, canonical_url, robots_noindex
4111 - FROM {$table}
4147 + FROM {$wpdb->prefix}aioseo_terms
4112 4148 ORDER BY term_id ASC
4113 4149 LIMIT %d OFFSET %d",
4114 4150 $batch_size,
4115 4151 $offset