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 | database/class-db-migrations.php +196 -133 2.7.0 → 2.7.2 View file →
@@ -43,9 +43,10 @@
43 43 // Create 404 Monitor Table
44 44 require_once dirname(__FILE__, 2) . '/404-monitor/class-metasync-404-monitor-database.php';
45 45 $tableName = esc_sql($wpdb->prefix . Metasync_Error_Monitor_Database::$table_name);
46 46
47 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tableName)) != $tableName) {
47 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tableName)) != $tableName) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
48 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
48 49 $table_sql = "CREATE TABLE {$tableName} (
49 50 id BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,
50 51 uri VARCHAR(255) NOT NULL,
51 52 date_time DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
@@ -61,10 +62,11 @@
61 62 // Create Redirections Table
62 63 require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-database.php';
63 64 $tableNameRedirection = esc_sql($wpdb->prefix . Metasync_Redirection_Database::$table_name);
64 65
65 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameRedirection)) != $tableNameRedirection) {
66 - $table_sql = "CREATE TABLE {$tableNameRedirection} (
66 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameRedirection)) != $tableNameRedirection) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
67 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
68 + $table_sql = "CREATE TABLE {$wpdb->prefix}metasync_redirections (
67 69 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
68 70 sources_from TEXT NOT NULL,
69 71 url_redirect_to TEXT NOT NULL,
70 72 http_code SMALLINT(4) unsigned NOT NULL DEFAULT 301,
@@ -85,41 +87,41 @@
85 87
86 88 dbDelta($table_sql);
87 89 } else {
88 90 // Check if new columns exist and add them if they don't
89 - $columns = $wpdb->get_col("DESCRIBE {$tableNameRedirection}");
91 + $columns = $wpdb->get_col("DESCRIBE {$wpdb->prefix}metasync_redirections"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
90 92
91 93 if (!in_array('pattern_type', $columns)) {
92 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex', 'wildcard') NOT NULL DEFAULT 'exact' AFTER status");
94 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex', 'wildcard') NOT NULL DEFAULT 'exact' AFTER status"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
93 95 }
94 96
95 97 if (!in_array('regex_pattern', $columns)) {
96 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN regex_pattern TEXT NULL AFTER pattern_type");
98 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN regex_pattern TEXT NULL AFTER pattern_type"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
97 99 }
98 100
99 101 if (!in_array('description', $columns)) {
100 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN description TEXT NULL AFTER regex_pattern");
102 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN description TEXT NULL AFTER regex_pattern"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
101 103 }
102 104
103 105 // Add indexes if they don't exist
104 - $indexes = $wpdb->get_results("SHOW INDEX FROM {$tableNameRedirection}");
106 + $indexes = $wpdb->get_results("SHOW INDEX FROM {$wpdb->prefix}metasync_redirections"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
105 107 $index_names = array_column($indexes, 'Key_name');
106 108
107 109 if (!in_array('pattern_type', $index_names)) {
108 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD KEY pattern_type (pattern_type)");
110 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD KEY pattern_type (pattern_type)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
109 111 }
110 112
111 113 if (!in_array('created_at', $index_names)) {
112 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD KEY created_at (created_at)");
114 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD KEY created_at (created_at)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
113 115 }
114 116
115 117 // PERFORMANCE OPTIMIZATION: Add composite index for active redirects lookup
116 118 if (!in_array('idx_active_redirects', $index_names)) {
117 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD KEY idx_active_redirects (status, sources_from(191))");
119 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD KEY idx_active_redirects (status, sources_from(191))"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
118 120 }
119 121
120 122 // Set default pattern_type for existing records
121 - $wpdb->query("UPDATE {$tableNameRedirection} SET pattern_type = 'exact' WHERE pattern_type IS NULL OR pattern_type = ''");
123 + $wpdb->query("UPDATE {$wpdb->prefix}metasync_redirections SET pattern_type = 'exact' WHERE pattern_type IS NULL OR pattern_type = ''"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
122 124 }
123 125
124 126 // One-time migration: auto-enable external redirects if the site already has any
125 127 if (!get_option('metasync_external_redirects_migrated')) {
@@ -128,27 +130,38 @@
128 130 // destinations such as 'https://example.com/about' on a
129 131 // www-prefixed site count as external and silently flip the setting.
130 132 $home_host = wp_parse_url(home_url(), PHP_URL_HOST);
131 133 $home_host = is_string($home_host) ? strtolower(preg_replace('/^www\./i', '', $home_host)) : '';
132 - $params = ['http%'];
134 +
133 135 if ($home_host !== '') {
134 - $not_like = '';
135 - foreach (['http://', 'https://'] as $scheme) {
136 - foreach ([$home_host, 'www.' . $home_host] as $host) {
137 - $not_like .= ' AND url_redirect_to NOT LIKE %s';
138 - $params[] = $wpdb->esc_like($scheme . $host) . '%';
139 - }
140 - }
136 + // NOT LIKE per home-URL variant, preserving the original
137 + // scheme × host ordering (http/https × bare/www).
138 + $external_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
139 + $wpdb->prepare(
140 + "SELECT COUNT(*) FROM {$wpdb->prefix}metasync_redirections
141 + WHERE url_redirect_to LIKE %s
142 + AND url_redirect_to NOT LIKE %s
143 + AND url_redirect_to NOT LIKE %s
144 + AND url_redirect_to NOT LIKE %s
145 + AND url_redirect_to NOT LIKE %s",
146 + 'http%',
147 + $wpdb->esc_like('http://' . $home_host) . '%',
148 + $wpdb->esc_like('http://www.' . $home_host) . '%',
149 + $wpdb->esc_like('https://' . $home_host) . '%',
150 + $wpdb->esc_like('https://www.' . $home_host) . '%'
151 + )
152 + );
141 153 } else {
142 - $not_like = ' AND url_redirect_to NOT LIKE %s';
143 - $params[] = $wpdb->esc_like(trailingslashit(home_url())) . '%';
154 + $external_count = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
155 + $wpdb->prepare(
156 + "SELECT COUNT(*) FROM {$wpdb->prefix}metasync_redirections
157 + WHERE url_redirect_to LIKE %s
158 + AND url_redirect_to NOT LIKE %s",
159 + 'http%',
160 + $wpdb->esc_like(trailingslashit(home_url())) . '%'
161 + )
162 + );
144 163 }
145 - $external_count = $wpdb->get_var(
146 - $wpdb->prepare(
147 - "SELECT COUNT(*) FROM {$tableNameRedirection} WHERE url_redirect_to LIKE %s{$not_like}",
148 - ...$params
149 - )
150 - );
151 164 if ($external_count > 0) {
152 165 update_option('metasync_allow_external_redirects', 1, true);
153 166 }
154 167 update_option('metasync_external_redirects_migrated', 1, true);
@@ -157,9 +170,10 @@
157 170 // Create HeartBeat Error Monitor Table
158 171 require_once dirname(__FILE__, 2) . '/heartbeat-error-monitor/class-metasync-heartbeat-error-monitor-database.php';
159 172 $tableNameHeartBeatErrorMonitor = esc_sql($wpdb->prefix . Metasync_HeartBeat_Error_Monitor_Database::$table_name);
160 173
161 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameHeartBeatErrorMonitor)) != $tableNameHeartBeatErrorMonitor) {
174 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameHeartBeatErrorMonitor)) != $tableNameHeartBeatErrorMonitor) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
175 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
162 176 $table_sql = "CREATE TABLE {$tableNameHeartBeatErrorMonitor} (
163 177 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
164 178 attribute_name VARCHAR(25) NOT NULL DEFAULT '',
165 179 object_count VARCHAR(25) NOT NULL DEFAULT '',
@@ -173,9 +187,10 @@
173 187
174 188 // Create Headless Refresh History Table
175 189 require_once dirname(__FILE__, 2) . '/headless-refresh-history/class-metasync-headless-refresh-history-database.php';
176 190 $tableNameHeadlessRefreshHistory = esc_sql($wpdb->prefix . Metasync_Headless_Refresh_History::$table_name);
177 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameHeadlessRefreshHistory)) != $tableNameHeadlessRefreshHistory) {
191 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameHeadlessRefreshHistory)) != $tableNameHeadlessRefreshHistory) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
192 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
178 193 $table_sql = "CREATE TABLE {$tableNameHeadlessRefreshHistory} (
179 194 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
180 195 object_type VARCHAR(20) NOT NULL DEFAULT '',
181 196 object_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
@@ -196,10 +211,11 @@
196 211 // Create Sync History Table
197 212 require_once dirname(__FILE__, 2) . '/sync-history/class-metasync-sync-history-database.php';
198 213 $tableNameSyncHistory = esc_sql($wpdb->prefix . Metasync_Sync_History_Database::$table_name);
199 214
200 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameSyncHistory)) != $tableNameSyncHistory) {
201 - $table_sql = "CREATE TABLE {$tableNameSyncHistory} (
215 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameSyncHistory)) != $tableNameSyncHistory) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
216 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
217 + $table_sql = "CREATE TABLE {$wpdb->prefix}metasync_sync_history (
202 218 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
203 219 title VARCHAR(255) NOT NULL DEFAULT '',
204 220 source VARCHAR(50) NOT NULL DEFAULT '',
205 221 status VARCHAR(25) NOT NULL DEFAULT 'draft',
@@ -218,19 +234,19 @@
218 234 dbDelta($table_sql);
219 235 } else {
220 236 // PERFORMANCE OPTIMIZATION: Add composite indexes to existing tables
221 237 // Check and add indexes if they don't exist
222 - $indexes = $wpdb->get_results("SHOW INDEX FROM {$tableNameSyncHistory}");
238 + $indexes = $wpdb->get_results("SHOW INDEX FROM {$wpdb->prefix}metasync_sync_history"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
223 239 $index_names = array_column($indexes, 'Key_name');
224 240
225 241 // Add deduplication index (source, created_at)
226 242 if (!in_array('idx_dedup', $index_names)) {
227 - $wpdb->query("ALTER TABLE {$tableNameSyncHistory} ADD KEY idx_dedup (source, created_at)");
243 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_sync_history ADD KEY idx_dedup (source, created_at)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
228 244 }
229 245
230 246 // Add search index (title(50), source, created_at)
231 247 if (!in_array('idx_search', $index_names)) {
232 - $wpdb->query("ALTER TABLE {$tableNameSyncHistory} ADD KEY idx_search (title(50), source, created_at)");
248 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_sync_history ADD KEY idx_search (title(50), source, created_at)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
233 249 }
234 250 }
235 251
236 252 // Create OTTO Excluded URLs Table
@@ -236,10 +252,11 @@
236 252 // Create OTTO Excluded URLs Table
237 253 require_once dirname(__FILE__, 2) . '/otto/class-metasync-otto-excluded-urls-database.php';
238 254 $tableNameOttoExcludedURLs = esc_sql($wpdb->prefix . Metasync_Otto_Excluded_URLs_Database::$table_name);
239 255
240 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameOttoExcludedURLs)) != $tableNameOttoExcludedURLs) {
241 - $table_sql = "CREATE TABLE {$tableNameOttoExcludedURLs} (
256 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameOttoExcludedURLs)) != $tableNameOttoExcludedURLs) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
257 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
258 + $table_sql = "CREATE TABLE {$wpdb->prefix}metasync_otto_excluded_urls (
242 259 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
243 260 url_pattern TEXT NOT NULL,
244 261 pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact',
245 262 description TEXT NULL,
@@ -264,9 +281,9 @@
264 281 // Create Robots.txt Backups Table
265 282 require_once dirname(__FILE__, 2) . '/robots-txt/class-metasync-robots-txt-database.php';
266 283 $robots_db = Metasync_Robots_Txt_Database::get_instance();
267 284 $table_name_robots = esc_sql($wpdb->prefix . 'metasync_robots_txt_backups');
268 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name_robots)) != $table_name_robots) {
285 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name_robots)) != $table_name_robots) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
269 286 $robots_db->create_table();
270 287 }
271 288
272 289 }
@@ -349,11 +366,12 @@
349 366 // Enhanced 404 Error Monitor Table
350 367 require_once dirname(__FILE__, 2) . '/404-monitor/class-metasync-404-monitor-database.php';
351 368 $tableName404Monitor = esc_sql($wpdb->prefix . Metasync_Error_Monitor_Database::$table_name);
352 369
353 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableName404Monitor)) != $tableName404Monitor) {
370 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableName404Monitor)) != $tableName404Monitor) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
354 371 // Table doesn't exist, create enhanced version
355 - $table_sql = "CREATE TABLE {$tableName404Monitor} (
372 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
373 + $table_sql = "CREATE TABLE {$wpdb->prefix}metasync_404_logs (
356 374 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
357 375 uri TEXT NOT NULL,
358 376 hits_count BIGINT(20) unsigned NOT NULL DEFAULT '1',
359 377 date_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -368,42 +386,42 @@
368 386
369 387 dbDelta($table_sql);
370 388 } else {
371 389 // Table exists, check for missing columns and add them
372 - $columns = $wpdb->get_col("DESCRIBE {$tableName404Monitor}");
390 + $columns = $wpdb->get_col("DESCRIBE {$wpdb->prefix}metasync_404_logs"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
373 391
374 392 // Add referer column if it doesn't exist
375 393 if (!in_array('referer', $columns)) {
376 - $wpdb->query("ALTER TABLE {$tableName404Monitor} ADD COLUMN referer TEXT NULL AFTER user_agent");
394 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs ADD COLUMN referer TEXT NULL AFTER user_agent"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
377 395 }
378 396
379 397 // Add ip_address column if it doesn't exist
380 398 if (!in_array('ip_address', $columns)) {
381 - $wpdb->query("ALTER TABLE {$tableName404Monitor} ADD COLUMN ip_address VARCHAR(45) NULL AFTER referer");
399 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs ADD COLUMN ip_address VARCHAR(45) NULL AFTER referer"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
382 400 }
383 401
384 402 // Update uri column to TEXT if it's VARCHAR(255)
385 - $uri_column = $wpdb->get_row("SHOW COLUMNS FROM {$tableName404Monitor} LIKE 'uri'");
403 + $uri_column = $wpdb->get_row("SHOW COLUMNS FROM {$wpdb->prefix}metasync_404_logs LIKE 'uri'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
386 404 if ($uri_column && strpos($uri_column->Type, 'varchar') !== false) {
387 - $wpdb->query("ALTER TABLE {$tableName404Monitor} MODIFY COLUMN uri TEXT NOT NULL");
405 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs MODIFY COLUMN uri TEXT NOT NULL"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
388 406 }
389 407
390 408 // Update user_agent column to TEXT if it's VARCHAR(255)
391 - $ua_column = $wpdb->get_row("SHOW COLUMNS FROM {$tableName404Monitor} LIKE 'user_agent'");
409 + $ua_column = $wpdb->get_row("SHOW COLUMNS FROM {$wpdb->prefix}metasync_404_logs LIKE 'user_agent'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
392 410 if ($ua_column && strpos($ua_column->Type, 'varchar') !== false) {
393 - $wpdb->query("ALTER TABLE {$tableName404Monitor} MODIFY COLUMN user_agent TEXT NULL");
411 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs MODIFY COLUMN user_agent TEXT NULL"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
394 412 }
395 413
396 414 // Add missing indexes
397 - $indexes = $wpdb->get_results("SHOW INDEX FROM {$tableName404Monitor}");
415 + $indexes = $wpdb->get_results("SHOW INDEX FROM {$wpdb->prefix}metasync_404_logs"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
398 416 $index_names = array_column($indexes, 'Key_name');
399 417
400 418 if (!in_array('hits_count', $index_names)) {
401 - $wpdb->query("ALTER TABLE {$tableName404Monitor} ADD KEY hits_count (hits_count)");
419 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs ADD KEY hits_count (hits_count)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
402 420 }
403 421
404 422 if (!in_array('date_time', $index_names)) {
405 - $wpdb->query("ALTER TABLE {$tableName404Monitor} ADD KEY date_time (date_time)");
423 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_404_logs ADD KEY date_time (date_time)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
406 424 }
407 425 }
408 426
409 427 // Enhanced Redirections Table with new columns
@@ -409,54 +427,54 @@
409 427 // Enhanced Redirections Table with new columns
410 428 require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-database.php';
411 429 $tableNameRedirection = esc_sql($wpdb->prefix . Metasync_Redirection_Database::$table_name);
412 430
413 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameRedirection)) == $tableNameRedirection) {
431 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameRedirection)) == $tableNameRedirection) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
414 432 // Table exists, check for new columns
415 - $columns = $wpdb->get_col("DESCRIBE {$tableNameRedirection}");
433 + $columns = $wpdb->get_col("DESCRIBE {$wpdb->prefix}metasync_redirections");
416 434
417 435 // Add pattern_type column if it doesn't exist
418 436 if (!in_array('pattern_type', $columns)) {
419 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex', 'wildcard') NOT NULL DEFAULT 'exact' AFTER status");
437 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex', 'wildcard') NOT NULL DEFAULT 'exact' AFTER status"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
420 438 }
421 439
422 440 // Add regex_pattern column if it doesn't exist
423 441 if (!in_array('regex_pattern', $columns)) {
424 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN regex_pattern TEXT NULL AFTER pattern_type");
442 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN regex_pattern TEXT NULL AFTER pattern_type"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
425 443 }
426 444
427 445 // Add description column if it doesn't exist
428 446 if (!in_array('description', $columns)) {
429 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN description TEXT NULL AFTER regex_pattern");
447 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN description TEXT NULL AFTER regex_pattern"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
430 448 }
431 449
432 450 // Add timestamp columns if they don't exist
433 451 if (!in_array('created_at', $columns)) {
434 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN created_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER description");
452 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN created_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER description"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
435 453 }
436 454
437 455 if (!in_array('updated_at', $columns)) {
438 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN updated_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER created_at");
456 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN updated_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER created_at"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
439 457 }
440 458
441 459 if (!in_array('last_accessed_at', $columns)) {
442 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD COLUMN last_accessed_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER updated_at");
460 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD COLUMN last_accessed_at DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER updated_at"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
443 461 }
444 462
445 463 // Add indexes if they don't exist
446 - $indexes = $wpdb->get_results("SHOW INDEX FROM {$tableNameRedirection}");
464 + $indexes = $wpdb->get_results("SHOW INDEX FROM {$wpdb->prefix}metasync_redirections"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
447 465 $index_names = array_column($indexes, 'Key_name');
448 466
449 467 if (!in_array('pattern_type', $index_names)) {
450 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD KEY pattern_type (pattern_type)");
468 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD KEY pattern_type (pattern_type)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
451 469 }
452 470
453 471 if (!in_array('created_at', $index_names)) {
454 - $wpdb->query("ALTER TABLE {$tableNameRedirection} ADD KEY created_at (created_at)");
472 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_redirections ADD KEY created_at (created_at)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
455 473 }
456 474
457 475 // Set default pattern_type for existing records
458 - $wpdb->query("UPDATE {$tableNameRedirection} SET pattern_type = 'exact' WHERE pattern_type IS NULL OR pattern_type = ''");
476 + $wpdb->query("UPDATE {$wpdb->prefix}metasync_redirections SET pattern_type = 'exact' WHERE pattern_type IS NULL OR pattern_type = ''"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
459 477 }
460 478 }
461 479
462 480 /**
@@ -471,9 +489,9 @@
471 489 $robots_db = Metasync_Robots_Txt_Database::get_instance();
472 490 $table_name = esc_sql($wpdb->prefix . 'metasync_robots_txt_backups');
473 491
474 492 // Check if table already exists
475 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) {
493 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
476 494 // Table doesn't exist, create it
477 495 $robots_db->create_table();
478 496 }
479 497 }
@@ -493,11 +511,12 @@
493 511 require_once dirname(__FILE__, 2) . '/otto/class-metasync-otto-excluded-urls-database.php';
494 512 $tableNameOttoExcludedURLs = esc_sql($wpdb->prefix . Metasync_Otto_Excluded_URLs_Database::$table_name);
495 513
496 514 // Check if table already exists
497 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tableNameOttoExcludedURLs)) != $tableNameOttoExcludedURLs) {
515 + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tableNameOttoExcludedURLs)) != $tableNameOttoExcludedURLs) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
498 516 // Table doesn't exist, create it
499 - $table_sql = "CREATE TABLE {$tableNameOttoExcludedURLs} (
517 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
518 + $table_sql = "CREATE TABLE {$wpdb->prefix}metasync_otto_excluded_urls (
500 519 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
501 520 url_pattern TEXT NOT NULL,
502 521 pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact',
503 522 description TEXT NULL,
@@ -522,72 +541,72 @@
522 541 // Log successful migration
523 542 // error_log('MetaSync: OTTO Excluded URLs table created successfully (v2.5.9)');
524 543 } else {
525 544 // Table exists, verify structure and add any missing columns if needed
526 - $columns = $wpdb->get_col("DESCRIBE {$tableNameOttoExcludedURLs}");
545 + $columns = $wpdb->get_col("DESCRIBE {$wpdb->prefix}metasync_otto_excluded_urls"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
527 546
528 547 // Check for required columns and add if missing
529 548 $missing_columns = false;
530 549
531 550 if (!in_array('pattern_type', $columns)) {
532 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact' AFTER url_pattern");
551 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact' AFTER url_pattern"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
533 552 $missing_columns = true;
534 553 }
535 554
536 555 if (!in_array('description', $columns)) {
537 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN description TEXT NULL AFTER pattern_type");
556 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN description TEXT NULL AFTER pattern_type"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
538 557 $missing_columns = true;
539 558 }
540 559
541 560 if (!in_array('status', $columns)) {
542 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN status VARCHAR(25) NOT NULL DEFAULT 'active' AFTER description");
561 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN status VARCHAR(25) NOT NULL DEFAULT 'active' AFTER description"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
543 562 $missing_columns = true;
544 563 }
545 564
546 565 if (!in_array('is_permanent', $columns)) {
547 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN is_permanent TINYINT(1) NOT NULL DEFAULT 0 AFTER status");
548 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY is_permanent (is_permanent)");
566 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN is_permanent TINYINT(1) NOT NULL DEFAULT 0 AFTER status"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
567 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY is_permanent (is_permanent)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
549 568 }
550 569
551 570 if (!in_array('auto_excluded', $columns)) {
552 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN auto_excluded TINYINT(1) NOT NULL DEFAULT 0 AFTER is_permanent");
553 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY auto_excluded (auto_excluded)");
571 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN auto_excluded TINYINT(1) NOT NULL DEFAULT 0 AFTER is_permanent"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
572 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY auto_excluded (auto_excluded)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
554 573 // Backfill: mark existing 404 exclusions as auto_excluded
555 - $wpdb->query("UPDATE {$tableNameOttoExcludedURLs} SET auto_excluded = 1 WHERE description = 'Auto-excluded: 404'");
574 + $wpdb->query("UPDATE {$wpdb->prefix}metasync_otto_excluded_urls SET auto_excluded = 1 WHERE description = 'Auto-excluded: 404'"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
556 575 }
557 576
558 577 if (!in_array('recheck_after', $columns)) {
559 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN recheck_after DATETIME NULL DEFAULT NULL AFTER auto_excluded");
560 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY recheck_after (recheck_after)");
578 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD COLUMN recheck_after DATETIME NULL DEFAULT NULL AFTER auto_excluded"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
579 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY recheck_after (recheck_after)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
561 580 // Backfill: set recheck_after = created_at + 7 days for auto-excluded URLs
562 - $wpdb->query("UPDATE {$tableNameOttoExcludedURLs} SET recheck_after = DATE_ADD(created_at, INTERVAL 7 DAY) WHERE auto_excluded = 1 AND (recheck_after IS NULL OR recheck_after = '0000-00-00 00:00:00')");
581 + $wpdb->query("UPDATE {$wpdb->prefix}metasync_otto_excluded_urls SET recheck_after = DATE_ADD(created_at, INTERVAL 7 DAY) WHERE auto_excluded = 1 AND (recheck_after IS NULL OR recheck_after = '0000-00-00 00:00:00')"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
563 582 }
564 583
565 584 // Check and add indexes if they don't exist
566 - $indexes = $wpdb->get_results("SHOW INDEX FROM {$tableNameOttoExcludedURLs}");
585 + $indexes = $wpdb->get_results("SHOW INDEX FROM {$wpdb->prefix}metasync_otto_excluded_urls"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
567 586 $index_names = array_column($indexes, 'Key_name');
568 587
569 588 if (!in_array('status', $index_names)) {
570 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY status (status)");
589 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY status (status)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
571 590 }
572 591
573 592 if (!in_array('pattern_type', $index_names)) {
574 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY pattern_type (pattern_type)");
593 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY pattern_type (pattern_type)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
575 594 }
576 595
577 596 if (!in_array('created_at', $index_names)) {
578 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY created_at (created_at)");
597 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY created_at (created_at)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
579 598 }
580 599
581 600 // Add unique index on url_pattern + pattern_type to prevent duplicates at database level
582 601 // Note: TEXT columns need a prefix length for indexing (767 is max for UTF8)
583 602 if (!in_array('url_pattern_type_unique', $index_names)) {
584 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD UNIQUE KEY url_pattern_type_unique (url_pattern(191), pattern_type)");
603 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD UNIQUE KEY url_pattern_type_unique (url_pattern(191), pattern_type)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
585 604 }
586 605
587 606 // Composite index for the cache-miss path in metasync_is_otto_url_manually_excluded()
588 607 if (!in_array('status_auto_excluded', $index_names)) {
589 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD KEY status_auto_excluded (status, auto_excluded)");
608 + $wpdb->query("ALTER TABLE {$wpdb->prefix}metasync_otto_excluded_urls ADD KEY status_auto_excluded (status, auto_excluded)"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
590 609 }
591 610
592 611 // if ($missing_columns) {
593 612 // error_log('MetaSync: OTTO Excluded URLs table structure updated (v2.5.9)');
@@ -611,46 +630,75 @@
611 630
612 631 $meta_keys = array('meta_canonical', '_metasync_canonical_url', '_yoast_wpseo_canonical', 'rank_math_canonical_url');
613 632 $bad_values = array('array', 'http://array', 'https://array');
614 633
615 - $keys_placeholders = implode(',', array_fill(0, count($meta_keys), '%s'));
616 - $vals_placeholders = implode(',', array_fill(0, count($bad_values), '%s'));
617 -
618 - // Normalized comparison: trailing slashes stripped in SQL so
619 - // "http://Array/" and "http://Array//" both match the literals.
620 - $norm_meta = "LOWER(TRIM(TRAILING '/' FROM TRIM(meta_value)))";
621 -
622 634 // 1. Post meta + term meta: delete exact-match corrupted rows.
623 635 // Batched and deleted by primary key so huge postmeta tables aren't
624 636 // range-locked in one statement, with per-object meta-cache
625 637 // invalidation — raw SQL alone would leave persistent object caches
626 638 // (Redis/Memcached) serving the deleted value to Yoast/RankMath
627 - // readers indefinitely.
628 - $meta_targets = array(
629 - array($wpdb->postmeta, 'post_id', 'post_meta'),
630 - array($wpdb->termmeta, 'term_id', 'term_meta'),
631 - );
632 - foreach ($meta_targets as $target) {
633 - list($table, $object_col, $cache_group) = $target;
634 - for ($batch = 0; $batch < 50; $batch++) {
635 - $rows = $wpdb->get_results($wpdb->prepare(
636 - "SELECT meta_id, {$object_col} AS object_id FROM {$table} WHERE meta_key IN ({$keys_placeholders}) AND {$norm_meta} IN ({$vals_placeholders}) ORDER BY meta_id LIMIT 500",
637 - array_merge($meta_keys, $bad_values)
638 - ));
639 - if (empty($rows)) {
640 - break;
641 - }
642 - $meta_ids = implode(',', array_map('intval', wp_list_pluck($rows, 'meta_id')));
643 - $wpdb->query("DELETE FROM {$table} WHERE meta_id IN ({$meta_ids})");
644 - foreach ($rows as $row) {
645 - wp_cache_delete((int) $row->object_id, $cache_group);
646 - }
647 - if (count($rows) < 500) {
648 - break;
649 - }
639 + // readers indefinitely. The two targets are written out explicitly
640 + // so every table/column identifier is a literal in the statement.
641 + //
642 + // Normalized comparison: trailing slashes stripped in SQL so
643 + // "http://Array/" and "http://Array//" both match the literals.
644 +
645 + // 1a. Post meta.
646 + for ($batch = 0; $batch < 50; $batch++) {
647 + $rows = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
648 + 'SELECT meta_id, post_id AS object_id FROM ' . $wpdb->postmeta
649 + . ' WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ')'
650 + . " AND LOWER(TRIM(TRAILING '/' FROM TRIM(meta_value)))"
651 + . ' IN (' . implode(',', array_fill(0, count($bad_values), '%s')) . ')'
652 + . ' ORDER BY meta_id LIMIT 500',
653 + array_merge($meta_keys, $bad_values)
654 + ));
655 + if (empty($rows)) {
656 + break;
650 657 }
658 + $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
659 + $wpdb->prepare(
660 + 'DELETE FROM ' . $wpdb->postmeta
661 + . ' WHERE meta_id IN (' . implode(',', array_fill(0, count($rows), '%d')) . ')',
662 + wp_list_pluck($rows, 'meta_id')
663 + )
664 + );
665 + foreach ($rows as $row) {
666 + wp_cache_delete((int) $row->object_id, 'post_meta');
667 + }
668 + if (count($rows) < 500) {
669 + break;
670 + }
651 671 }
652 672
673 + // 1b. Term meta.
674 + for ($batch = 0; $batch < 50; $batch++) {
675 + $rows = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
676 + 'SELECT meta_id, term_id AS object_id FROM ' . $wpdb->termmeta
677 + . ' WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ')'
678 + . " AND LOWER(TRIM(TRAILING '/' FROM TRIM(meta_value)))"
679 + . ' IN (' . implode(',', array_fill(0, count($bad_values), '%s')) . ')'
680 + . ' ORDER BY meta_id LIMIT 500',
681 + array_merge($meta_keys, $bad_values)
682 + ));
683 + if (empty($rows)) {
684 + break;
685 + }
686 + $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
687 + $wpdb->prepare(
688 + 'DELETE FROM ' . $wpdb->termmeta
689 + . ' WHERE meta_id IN (' . implode(',', array_fill(0, count($rows), '%d')) . ')',
690 + wp_list_pluck($rows, 'meta_id')
691 + )
692 + );
693 + foreach ($rows as $row) {
694 + wp_cache_delete((int) $row->object_id, 'term_meta');
695 + }
696 + if (count($rows) < 500) {
697 + break;
698 + }
699 + }
700 +
653 701 // 2. Rows still stored as serialized arrays (the raw material the
654 702 // "Array" casts came from): repair MetaSync's own keys to the first
655 703 // usable URL inside; third-party keys are delete-only (never invent
656 704 // a value inside another plugin's storage). Written with direct SQL
@@ -659,11 +707,14 @@
659 707 // row; loops until exhausted (repaired rows stop matching LIKE).
660 708 if (class_exists('Metasync_Canonical_Sanitizer')) {
661 709 $own_keys = array('meta_canonical', '_metasync_canonical_url');
662 710 for ($batch = 0; $batch < 50; $batch++) {
663 - $rows = $wpdb->get_results($wpdb->prepare(
664 - "SELECT meta_id, post_id, meta_key, meta_value FROM {$wpdb->postmeta} WHERE meta_key IN ({$keys_placeholders}) AND meta_value LIKE 'a:%%' ORDER BY meta_id LIMIT 500",
665 - $meta_keys
711 + $rows = $wpdb->get_results($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
712 + 'SELECT meta_id, post_id, meta_key, meta_value FROM ' . $wpdb->postmeta
713 + . ' WHERE meta_key IN (' . implode(',', array_fill(0, count($meta_keys), '%s')) . ')'
714 + . " AND meta_value LIKE %s"
715 + . ' ORDER BY meta_id LIMIT 500',
716 + array_merge($meta_keys, array('a:%'))
666 717 ));
667 718 if (empty($rows)) {
668 719 break;
669 720 }
@@ -672,11 +723,11 @@
672 723 if (in_array($row->meta_key, $own_keys, true)) {
673 724 $repaired = Metasync_Canonical_Sanitizer::sanitize(maybe_unserialize($row->meta_value));
674 725 }
675 726 if ($repaired !== '') {
676 - $wpdb->update($wpdb->postmeta, array('meta_value' => $repaired), array('meta_id' => (int) $row->meta_id));
727 + $wpdb->update($wpdb->postmeta, array('meta_value' => $repaired), array('meta_id' => (int) $row->meta_id)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
677 728 } else {
678 - $wpdb->delete($wpdb->postmeta, array('meta_id' => (int) $row->meta_id));
729 + $wpdb->delete($wpdb->postmeta, array('meta_id' => (int) $row->meta_id)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
679 730 }
680 731 wp_cache_delete((int) $row->post_id, 'post_meta');
681 732 }
682 733 if (count($rows) < 500) {
@@ -687,25 +738,37 @@
687 738
688 739 // 3. Yoast indexable cache: null corrupted canonical columns so the
689 740 // frontend and sitemaps stop serving the bad value immediately.
690 741 $indexable_table = $wpdb->prefix . 'yoast_indexable';
691 - if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($indexable_table))) === $indexable_table) {
742 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($indexable_table))) === $indexable_table) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
692 743 $wpdb->query($wpdb->prepare(
693 - "UPDATE {$indexable_table} SET canonical = NULL WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical))) IN ({$vals_placeholders})",
744 + 'UPDATE ' . $wpdb->prefix . 'yoast_indexable SET canonical = NULL'
745 + . " WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical)))"
746 + . ' IN (' . implode(',', array_fill(0, count($bad_values), '%s')) . ')',
694 747 $bad_values
695 748 ));
696 749 }
697 750
698 751 // 4. AIOSEO custom tables: null corrupted canonical_url columns.
699 - foreach (array('aioseo_posts', 'aioseo_terms') as $aioseo_table) {
700 - $table = $wpdb->prefix . $aioseo_table;
701 - if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table) {
702 - $wpdb->query($wpdb->prepare(
703 - "UPDATE {$table} SET canonical_url = NULL WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical_url))) IN ({$vals_placeholders})",
704 - $bad_values
705 - ));
706 - }
752 + // Both table names are fixed literals, written out explicitly.
753 + $aioseo_posts = $wpdb->prefix . 'aioseo_posts';
754 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($aioseo_posts))) === $aioseo_posts) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
755 + $wpdb->query($wpdb->prepare(
756 + 'UPDATE ' . $wpdb->prefix . 'aioseo_posts SET canonical_url = NULL'
757 + . " WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical_url)))"
758 + . ' IN (' . implode(',', array_fill(0, count($bad_values), '%s')) . ')',
759 + $bad_values
760 + ));
707 761 }
762 + $aioseo_terms = $wpdb->prefix . 'aioseo_terms';
763 + if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($aioseo_terms))) === $aioseo_terms) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- activation/upgrade migration routine — schema changes are gated to activation and version bumps, never ordinary requests
764 + $wpdb->query($wpdb->prepare(
765 + 'UPDATE ' . $wpdb->prefix . 'aioseo_terms SET canonical_url = NULL'
766 + . " WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical_url)))"
767 + . ' IN (' . implode(',', array_fill(0, count($bad_values), '%s')) . ')',
768 + $bad_values
769 + ));
770 + }
708 771
709 772 // 5. Yoast stores term canonicals in the wpseo_taxonomy_meta option,
710 773 // not termmeta. Strip only exact corruption literals.
711 774 if (class_exists('Metasync_Canonical_Sanitizer')) {
@@ -828,9 +891,9 @@
828 891 $scan_failed += $result['scan_failed'];
829 892 }
830 893
831 894 if ($scan_failed) {
832 - error_log(sprintf(
895 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
833 896 'MetaSync: could not inspect %d wp-config.php backup directory scan(s); cleanup will be retried.',
834 897 $scan_failed
835 898 ));
836 899 return;
@@ -838,9 +901,9 @@
838 901
839 902 if ($failed) {
840 903 // A copy left behind is exactly the exposure this cleanup exists to remove,
841 904 // so surface it rather than failing silently.
842 - error_log(sprintf(
905 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
843 906 'MetaSync: could not delete %d leftover wp-config.php copy/copies in %s - remove them manually.',
844 907 $failed,
845 908 implode(', ', array_keys($sweptDirs))
846 909 ));
@@ -849,9 +912,9 @@
849 912
850 913 if (!$located) {
851 914 // wp-config.php was not in any directory we swept, so it is relocated somewhere
852 915 // we could not resolve. Leave the flag unset so a later upgrade tries again.
853 - error_log('MetaSync: wp-config.php was not found while cleaning up legacy backup copies in '
916 + error_log('MetaSync: wp-config.php was not found while cleaning up legacy backup copies in ' // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
854 917 . implode(', ', array_keys($sweptDirs)) . ' - cleanup will be retried.');
855 918 return;
856 919 }
857 920
@@ -887,9 +950,9 @@
887 950 if (!is_file($backup)) {
888 951 continue;
889 952 }
890 953
891 - if (@unlink($backup)) {
954 + if (@unlink($backup)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- boolean return value is consumed
892 955 $removed++;
893 956 } else {
894 957 $failed++;
895 958 }
@@ -912,9 +975,9 @@
912 975 if (false === $mtime || abs(time() - $mtime) < self::WPCONFIG_TMP_MAX_AGE) {
913 976 continue;
914 977 }
915 978
916 - if (@unlink($temp)) {
979 + if (@unlink($temp)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- boolean return value is consumed
917 980 $removed++;
918 981 } else {
919 982 $failed++;
920 983 }