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 | database/class-db-migrations.php +226 -138 2.6.25 → 2.7.1 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 '',
@@ -170,14 +184,38 @@
170 184
171 185 dbDelta($table_sql);
172 186 }
173 187
174 - // Create Sync History Table
188 + // Create Headless Refresh History Table
189 + require_once dirname(__FILE__, 2) . '/headless-refresh-history/class-metasync-headless-refresh-history-database.php';
190 + $tableNameHeadlessRefreshHistory = esc_sql($wpdb->prefix . Metasync_Headless_Refresh_History::$table_name);
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
193 + $table_sql = "CREATE TABLE {$tableNameHeadlessRefreshHistory} (
194 + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
195 + object_type VARCHAR(20) NOT NULL DEFAULT '',
196 + object_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
197 + url_hash CHAR(64) NOT NULL DEFAULT '',
198 + outcome VARCHAR(32) NOT NULL DEFAULT '',
199 + reason VARCHAR(64) NOT NULL DEFAULT '',
200 + duration_ms INT UNSIGNED NOT NULL DEFAULT 0,
201 + object_count SMALLINT UNSIGNED NOT NULL DEFAULT 0,
202 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
203 + PRIMARY KEY (id),
204 + KEY idx_created_at (created_at),
205 + KEY idx_object (object_type, object_id, created_at),
206 + KEY idx_outcome (outcome, created_at)
207 + ) $collate;";
208 + dbDelta($table_sql);
209 + }
210 +
211 + // Create Sync History Table
175 212 require_once dirname(__FILE__, 2) . '/sync-history/class-metasync-sync-history-database.php';
176 213 $tableNameSyncHistory = esc_sql($wpdb->prefix . Metasync_Sync_History_Database::$table_name);
177 214
178 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameSyncHistory)) != $tableNameSyncHistory) {
179 - $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 (
180 218 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
181 219 title VARCHAR(255) NOT NULL DEFAULT '',
182 220 source VARCHAR(50) NOT NULL DEFAULT '',
183 221 status VARCHAR(25) NOT NULL DEFAULT 'draft',
@@ -196,19 +234,19 @@
196 234 dbDelta($table_sql);
197 235 } else {
198 236 // PERFORMANCE OPTIMIZATION: Add composite indexes to existing tables
199 237 // Check and add indexes if they don't exist
200 - $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
201 239 $index_names = array_column($indexes, 'Key_name');
202 240
203 241 // Add deduplication index (source, created_at)
204 242 if (!in_array('idx_dedup', $index_names)) {
205 - $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
206 244 }
207 245
208 246 // Add search index (title(50), source, created_at)
209 247 if (!in_array('idx_search', $index_names)) {
210 - $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
211 249 }
212 250 }
213 251
214 252 // Create OTTO Excluded URLs Table
@@ -214,10 +252,11 @@
214 252 // Create OTTO Excluded URLs Table
215 253 require_once dirname(__FILE__, 2) . '/otto/class-metasync-otto-excluded-urls-database.php';
216 254 $tableNameOttoExcludedURLs = esc_sql($wpdb->prefix . Metasync_Otto_Excluded_URLs_Database::$table_name);
217 255
218 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s ", $tableNameOttoExcludedURLs)) != $tableNameOttoExcludedURLs) {
219 - $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 (
220 259 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
221 260 url_pattern TEXT NOT NULL,
222 261 pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact',
223 262 description TEXT NULL,
@@ -242,9 +281,9 @@
242 281 // Create Robots.txt Backups Table
243 282 require_once dirname(__FILE__, 2) . '/robots-txt/class-metasync-robots-txt-database.php';
244 283 $robots_db = Metasync_Robots_Txt_Database::get_instance();
245 284 $table_name_robots = esc_sql($wpdb->prefix . 'metasync_robots_txt_backups');
246 - 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
247 286 $robots_db->create_table();
248 287 }
249 288
250 289 }
@@ -268,13 +307,16 @@
268 307 /* drop wp_metasync_redirections table */
269 308 // $sql = "DROP TABLE IF EXISTS `$tableNameRedirection` ";
270 309 // $wpdb->query($sql);
271 310
272 - require_once dirname(__FILE__, 2) . '/heartbeat-error-monitor/class-metasync-heartbeat-error-monitor-database.php';
273 - $tableNameHeartBeatErrorMonitor = esc_sql($wpdb->prefix . Metasync_HeartBeat_Error_Monitor_Database::$table_name);
274 - /* drop wp_metasync_redirections table */
275 - $sql = "DROP TABLE IF EXISTS `$tableNameHeartBeatErrorMonitor` ";
276 - $wpdb->query($sql);
311 + // Data tables are preserved on deactivation so a deactivate/reactivate cycle
312 + // does not silently destroy logged data (e.g. heartbeat error history).
313 + // Removal belongs to uninstall, not deactivation.
314 + // require_once dirname(__FILE__, 2) . '/heartbeat-error-monitor/class-metasync-heartbeat-error-monitor-database.php';
315 + // $tableNameHeartBeatErrorMonitor = esc_sql($wpdb->prefix . Metasync_HeartBeat_Error_Monitor_Database::$table_name);
316 + /* drop wp_metasync_heartbeat_error_logs table */
317 + // $sql = "DROP TABLE IF EXISTS `$tableNameHeartBeatErrorMonitor` ";
318 + // $wpdb->query($sql);
277 319 }
278 320
279 321 /**
280 322 * Run version-specific migrations
@@ -324,11 +366,12 @@
324 366 // Enhanced 404 Error Monitor Table
325 367 require_once dirname(__FILE__, 2) . '/404-monitor/class-metasync-404-monitor-database.php';
326 368 $tableName404Monitor = esc_sql($wpdb->prefix . Metasync_Error_Monitor_Database::$table_name);
327 369
328 - 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
329 371 // Table doesn't exist, create enhanced version
330 - $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 (
331 374 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
332 375 uri TEXT NOT NULL,
333 376 hits_count BIGINT(20) unsigned NOT NULL DEFAULT '1',
334 377 date_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -343,42 +386,42 @@
343 386
344 387 dbDelta($table_sql);
345 388 } else {
346 389 // Table exists, check for missing columns and add them
347 - $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
348 391
349 392 // Add referer column if it doesn't exist
350 393 if (!in_array('referer', $columns)) {
351 - $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
352 395 }
353 396
354 397 // Add ip_address column if it doesn't exist
355 398 if (!in_array('ip_address', $columns)) {
356 - $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
357 400 }
358 401
359 402 // Update uri column to TEXT if it's VARCHAR(255)
360 - $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
361 404 if ($uri_column && strpos($uri_column->Type, 'varchar') !== false) {
362 - $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
363 406 }
364 407
365 408 // Update user_agent column to TEXT if it's VARCHAR(255)
366 - $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
367 410 if ($ua_column && strpos($ua_column->Type, 'varchar') !== false) {
368 - $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
369 412 }
370 413
371 414 // Add missing indexes
372 - $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
373 416 $index_names = array_column($indexes, 'Key_name');
374 417
375 418 if (!in_array('hits_count', $index_names)) {
376 - $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
377 420 }
378 421
379 422 if (!in_array('date_time', $index_names)) {
380 - $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
381 424 }
382 425 }
383 426
384 427 // Enhanced Redirections Table with new columns
@@ -384,54 +427,54 @@
384 427 // Enhanced Redirections Table with new columns
385 428 require_once dirname(__FILE__, 2) . '/redirections/class-metasync-redirection-database.php';
386 429 $tableNameRedirection = esc_sql($wpdb->prefix . Metasync_Redirection_Database::$table_name);
387 430
388 - 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
389 432 // Table exists, check for new columns
390 - $columns = $wpdb->get_col("DESCRIBE {$tableNameRedirection}");
433 + $columns = $wpdb->get_col("DESCRIBE {$wpdb->prefix}metasync_redirections");
391 434
392 435 // Add pattern_type column if it doesn't exist
393 436 if (!in_array('pattern_type', $columns)) {
394 - $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
395 438 }
396 439
397 440 // Add regex_pattern column if it doesn't exist
398 441 if (!in_array('regex_pattern', $columns)) {
399 - $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
400 443 }
401 444
402 445 // Add description column if it doesn't exist
403 446 if (!in_array('description', $columns)) {
404 - $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
405 448 }
406 449
407 450 // Add timestamp columns if they don't exist
408 451 if (!in_array('created_at', $columns)) {
409 - $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
410 453 }
411 454
412 455 if (!in_array('updated_at', $columns)) {
413 - $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
414 457 }
415 458
416 459 if (!in_array('last_accessed_at', $columns)) {
417 - $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
418 461 }
419 462
420 463 // Add indexes if they don't exist
421 - $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
422 465 $index_names = array_column($indexes, 'Key_name');
423 466
424 467 if (!in_array('pattern_type', $index_names)) {
425 - $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
426 469 }
427 470
428 471 if (!in_array('created_at', $index_names)) {
429 - $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
430 473 }
431 474
432 475 // Set default pattern_type for existing records
433 - $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
434 477 }
435 478 }
436 479
437 480 /**
@@ -446,9 +489,9 @@
446 489 $robots_db = Metasync_Robots_Txt_Database::get_instance();
447 490 $table_name = esc_sql($wpdb->prefix . 'metasync_robots_txt_backups');
448 491
449 492 // Check if table already exists
450 - 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
451 494 // Table doesn't exist, create it
452 495 $robots_db->create_table();
453 496 }
454 497 }
@@ -468,11 +511,12 @@
468 511 require_once dirname(__FILE__, 2) . '/otto/class-metasync-otto-excluded-urls-database.php';
469 512 $tableNameOttoExcludedURLs = esc_sql($wpdb->prefix . Metasync_Otto_Excluded_URLs_Database::$table_name);
470 513
471 514 // Check if table already exists
472 - 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
473 516 // Table doesn't exist, create it
474 - $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 (
475 519 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
476 520 url_pattern TEXT NOT NULL,
477 521 pattern_type ENUM('exact', 'contain', 'start', 'end', 'regex') NOT NULL DEFAULT 'exact',
478 522 description TEXT NULL,
@@ -497,72 +541,72 @@
497 541 // Log successful migration
498 542 // error_log('MetaSync: OTTO Excluded URLs table created successfully (v2.5.9)');
499 543 } else {
500 544 // Table exists, verify structure and add any missing columns if needed
501 - $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
502 546
503 547 // Check for required columns and add if missing
504 548 $missing_columns = false;
505 549
506 550 if (!in_array('pattern_type', $columns)) {
507 - $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
508 552 $missing_columns = true;
509 553 }
510 554
511 555 if (!in_array('description', $columns)) {
512 - $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
513 557 $missing_columns = true;
514 558 }
515 559
516 560 if (!in_array('status', $columns)) {
517 - $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
518 562 $missing_columns = true;
519 563 }
520 564
521 565 if (!in_array('is_permanent', $columns)) {
522 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN is_permanent TINYINT(1) NOT NULL DEFAULT 0 AFTER status");
523 - $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
524 568 }
525 569
526 570 if (!in_array('auto_excluded', $columns)) {
527 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN auto_excluded TINYINT(1) NOT NULL DEFAULT 0 AFTER is_permanent");
528 - $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
529 573 // Backfill: mark existing 404 exclusions as auto_excluded
530 - $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
531 575 }
532 576
533 577 if (!in_array('recheck_after', $columns)) {
534 - $wpdb->query("ALTER TABLE {$tableNameOttoExcludedURLs} ADD COLUMN recheck_after DATETIME NULL DEFAULT NULL AFTER auto_excluded");
535 - $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
536 580 // Backfill: set recheck_after = created_at + 7 days for auto-excluded URLs
537 - $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
538 582 }
539 583
540 584 // Check and add indexes if they don't exist
541 - $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
542 586 $index_names = array_column($indexes, 'Key_name');
543 587
544 588 if (!in_array('status', $index_names)) {
545 - $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
546 590 }
547 591
548 592 if (!in_array('pattern_type', $index_names)) {
549 - $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
550 594 }
551 595
552 596 if (!in_array('created_at', $index_names)) {
553 - $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
554 598 }
555 599
556 600 // Add unique index on url_pattern + pattern_type to prevent duplicates at database level
557 601 // Note: TEXT columns need a prefix length for indexing (767 is max for UTF8)
558 602 if (!in_array('url_pattern_type_unique', $index_names)) {
559 - $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
560 604 }
561 605
562 606 // Composite index for the cache-miss path in metasync_is_otto_url_manually_excluded()
563 607 if (!in_array('status_auto_excluded', $index_names)) {
564 - $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
565 609 }
566 610
567 611 // if ($missing_columns) {
568 612 // error_log('MetaSync: OTTO Excluded URLs table structure updated (v2.5.9)');
@@ -586,46 +630,75 @@
586 630
587 631 $meta_keys = array('meta_canonical', '_metasync_canonical_url', '_yoast_wpseo_canonical', 'rank_math_canonical_url');
588 632 $bad_values = array('array', 'http://array', 'https://array');
589 633
590 - $keys_placeholders = implode(',', array_fill(0, count($meta_keys), '%s'));
591 - $vals_placeholders = implode(',', array_fill(0, count($bad_values), '%s'));
592 -
593 - // Normalized comparison: trailing slashes stripped in SQL so
594 - // "http://Array/" and "http://Array//" both match the literals.
595 - $norm_meta = "LOWER(TRIM(TRAILING '/' FROM TRIM(meta_value)))";
596 -
597 634 // 1. Post meta + term meta: delete exact-match corrupted rows.
598 635 // Batched and deleted by primary key so huge postmeta tables aren't
599 636 // range-locked in one statement, with per-object meta-cache
600 637 // invalidation — raw SQL alone would leave persistent object caches
601 638 // (Redis/Memcached) serving the deleted value to Yoast/RankMath
602 - // readers indefinitely.
603 - $meta_targets = array(
604 - array($wpdb->postmeta, 'post_id', 'post_meta'),
605 - array($wpdb->termmeta, 'term_id', 'term_meta'),
606 - );
607 - foreach ($meta_targets as $target) {
608 - list($table, $object_col, $cache_group) = $target;
609 - for ($batch = 0; $batch < 50; $batch++) {
610 - $rows = $wpdb->get_results($wpdb->prepare(
611 - "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",
612 - array_merge($meta_keys, $bad_values)
613 - ));
614 - if (empty($rows)) {
615 - break;
616 - }
617 - $meta_ids = implode(',', array_map('intval', wp_list_pluck($rows, 'meta_id')));
618 - $wpdb->query("DELETE FROM {$table} WHERE meta_id IN ({$meta_ids})");
619 - foreach ($rows as $row) {
620 - wp_cache_delete((int) $row->object_id, $cache_group);
621 - }
622 - if (count($rows) < 500) {
623 - break;
624 - }
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;
625 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 + }
626 671 }
627 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 +
628 701 // 2. Rows still stored as serialized arrays (the raw material the
629 702 // "Array" casts came from): repair MetaSync's own keys to the first
630 703 // usable URL inside; third-party keys are delete-only (never invent
631 704 // a value inside another plugin's storage). Written with direct SQL
@@ -634,11 +707,14 @@
634 707 // row; loops until exhausted (repaired rows stop matching LIKE).
635 708 if (class_exists('Metasync_Canonical_Sanitizer')) {
636 709 $own_keys = array('meta_canonical', '_metasync_canonical_url');
637 710 for ($batch = 0; $batch < 50; $batch++) {
638 - $rows = $wpdb->get_results($wpdb->prepare(
639 - "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",
640 - $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:%'))
641 717 ));
642 718 if (empty($rows)) {
643 719 break;
644 720 }
@@ -647,11 +723,11 @@
647 723 if (in_array($row->meta_key, $own_keys, true)) {
648 724 $repaired = Metasync_Canonical_Sanitizer::sanitize(maybe_unserialize($row->meta_value));
649 725 }
650 726 if ($repaired !== '') {
651 - $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
652 728 } else {
653 - $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
654 730 }
655 731 wp_cache_delete((int) $row->post_id, 'post_meta');
656 732 }
657 733 if (count($rows) < 500) {
@@ -662,25 +738,37 @@
662 738
663 739 // 3. Yoast indexable cache: null corrupted canonical columns so the
664 740 // frontend and sitemaps stop serving the bad value immediately.
665 741 $indexable_table = $wpdb->prefix . 'yoast_indexable';
666 - 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
667 743 $wpdb->query($wpdb->prepare(
668 - "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')) . ')',
669 747 $bad_values
670 748 ));
671 749 }
672 750
673 751 // 4. AIOSEO custom tables: null corrupted canonical_url columns.
674 - foreach (array('aioseo_posts', 'aioseo_terms') as $aioseo_table) {
675 - $table = $wpdb->prefix . $aioseo_table;
676 - if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))) === $table) {
677 - $wpdb->query($wpdb->prepare(
678 - "UPDATE {$table} SET canonical_url = NULL WHERE LOWER(TRIM(TRAILING '/' FROM TRIM(canonical_url))) IN ({$vals_placeholders})",
679 - $bad_values
680 - ));
681 - }
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 + ));
682 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 + }
683 771
684 772 // 5. Yoast stores term canonicals in the wpseo_taxonomy_meta option,
685 773 // not termmeta. Strip only exact corruption literals.
686 774 if (class_exists('Metasync_Canonical_Sanitizer')) {
@@ -803,9 +891,9 @@
803 891 $scan_failed += $result['scan_failed'];
804 892 }
805 893
806 894 if ($scan_failed) {
807 - error_log(sprintf(
895 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
808 896 'MetaSync: could not inspect %d wp-config.php backup directory scan(s); cleanup will be retried.',
809 897 $scan_failed
810 898 ));
811 899 return;
@@ -813,9 +901,9 @@
813 901
814 902 if ($failed) {
815 903 // A copy left behind is exactly the exposure this cleanup exists to remove,
816 904 // so surface it rather than failing silently.
817 - error_log(sprintf(
905 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
818 906 'MetaSync: could not delete %d leftover wp-config.php copy/copies in %s - remove them manually.',
819 907 $failed,
820 908 implode(', ', array_keys($sweptDirs))
821 909 ));
@@ -824,9 +912,9 @@
824 912
825 913 if (!$located) {
826 914 // wp-config.php was not in any directory we swept, so it is relocated somewhere
827 915 // we could not resolve. Leave the flag unset so a later upgrade tries again.
828 - 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
829 917 . implode(', ', array_keys($sweptDirs)) . ' - cleanup will be retried.');
830 918 return;
831 919 }
832 920
@@ -862,9 +950,9 @@
862 950 if (!is_file($backup)) {
863 951 continue;
864 952 }
865 953
866 - if (@unlink($backup)) {
954 + if (@unlink($backup)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- boolean return value is consumed
867 955 $removed++;
868 956 } else {
869 957 $failed++;
870 958 }
@@ -887,9 +975,9 @@
887 975 if (false === $mtime || abs(time() - $mtime) < self::WPCONFIG_TMP_MAX_AGE) {
888 976 continue;
889 977 }
890 978
891 - if (@unlink($temp)) {
979 + if (@unlink($temp)) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- boolean return value is consumed
892 980 $removed++;
893 981 } else {
894 982 $failed++;
895 983 }