← All changes
|
sync-history/class-metasync-sync-history-database.php
+40
-54
2.6.21
→
2.7.1
View file →
| @@ -11,8 +11,10 @@ | ||
| 11 | 11 | class Metasync_Sync_History_Database |
| 12 | 12 | { |
| 13 | 13 | public static $table_name = "metasync_sync_history"; |
| 14 | 14 | |
| 15 | + private static $structure_verified = false; | |
| 16 | + | |
| 15 | 17 | private function get_table_name() |
| 16 | 18 | { |
| 17 | 19 | global $wpdb; |
| 18 | 20 | return $wpdb->prefix . self::$table_name; |
| @@ -22,13 +24,18 @@ | ||
| 22 | 24 | * Check if the table exists and create it if it doesn't |
| 23 | 25 | */ |
| 24 | 26 | public function maybe_create_table() |
| 25 | 27 | { |
| 28 | + // Run schema inspection at most once per request — table schema only | |
| 29 | + // changes on activation/upgrade, handled by class-db-migrations.php. | |
| 30 | + if (self::$structure_verified) { return; } | |
| 31 | + self::$structure_verified = true; | |
| 32 | + | |
| 26 | 33 | global $wpdb; |
| 27 | 34 | $table_name = $this->get_table_name(); |
| 28 | 35 | |
| 29 | 36 | // Check if table exists |
| 30 | - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { | |
| 37 | + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- lazy table-creation backstop, once per request via the $structure_verified gate; primary creation runs in the upgrade routine | |
| 31 | 38 | $this->create_table(); |
| 32 | 39 | } |
| 33 | 40 | } |
| 34 | 41 | |
| @@ -72,14 +79,15 @@ | ||
| 72 | 79 | $result = dbDelta($sql); |
| 73 | 80 | |
| 74 | 81 | // Log result for debugging |
| 75 | 82 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 83 | + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r -- debug-gated, no secrets | |
| 76 | 84 | error_log("Metasync Sync History Table Creation Result: " . print_r($result, true)); |
| 77 | 85 | } |
| 78 | 86 | |
| 79 | 87 | // Verify table was created |
| 80 | - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { | |
| 81 | - error_log("Failed to create metasync_sync_history table: " . $wpdb->last_error); | |
| 88 | + if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) != $table_name) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- post-create verification inside the lazy table-creation backstop, once per request | |
| 89 | + error_log("Failed to create metasync_sync_history table: " . $wpdb->last_error); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.DB.DirectDatabaseQuery -- genuine failure path, bounded, no secrets; error_log() message text is not SQL (the word "create" trips DirectDatabaseQuery) | |
| 82 | 90 | } |
| 83 | 91 | } |
| 84 | 92 | |
| 85 | 93 | public function getAllRecords($limit = 30, $offset = 0, $filters = []) |
| @@ -87,9 +95,8 @@ | ||
| 87 | 95 | // Ensure table exists |
| 88 | 96 | $this->maybe_create_table(); |
| 89 | 97 | |
| 90 | 98 | global $wpdb; |
| 91 | - $tableName = $this->get_table_name(); | |
| 92 | 99 | |
| 93 | 100 | $where_conditions = []; |
| 94 | 101 | $where_values = []; |
| 95 | 102 | |
| @@ -119,25 +126,19 @@ | ||
| 119 | 126 | if (!empty($filters['date_to'])) { |
| 120 | 127 | $where_conditions[] = "created_at <= %s"; |
| 121 | 128 | $where_values[] = $filters['date_to']; |
| 122 | 129 | } |
| 123 | - | |
| 124 | - $where_clause = ''; | |
| 125 | - if (!empty($where_conditions)) { | |
| 126 | - $where_clause = 'WHERE ' . implode(' AND ', $where_conditions); | |
| 127 | - } | |
| 128 | 130 | |
| 129 | 131 | # PERFORMANCE OPTIMIZATION: Select specific columns instead of * |
| 130 | 132 | # Reduces data transfer and memory usage by 20-30% |
| 131 | - $query = "SELECT id, title, source, status, content_type, url, created_at FROM `$tableName` $where_clause ORDER BY created_at DESC LIMIT %d OFFSET %d"; | |
| 133 | + // Query assembled from literal placeholder fragments only; values bind via prepare(). | |
| 134 | + $query = "SELECT id, title, source, status, content_type, url, created_at FROM `{$wpdb->prefix}metasync_sync_history`" | |
| 135 | + . (!empty($where_conditions) ? ' WHERE ' . implode(' AND ', $where_conditions) : '') | |
| 136 | + . ' ORDER BY created_at DESC LIMIT %d OFFSET %d'; | |
| 132 | 137 | $where_values[] = $limit; |
| 133 | 138 | $where_values[] = $offset; |
| 134 | - | |
| 135 | - if (!empty($where_values)) { | |
| 136 | - return $wpdb->get_results($wpdb->prepare($query, $where_values)); | |
| 137 | - } else { | |
| 138 | - return $wpdb->get_results($wpdb->prepare($query, $limit, $offset)); | |
| 139 | - } | |
| 139 | + | |
| 140 | + return $wpdb->get_results($wpdb->prepare($query, $where_values)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- literal fragments only, values bound via prepare() | |
| 140 | 141 | } |
| 141 | 142 | |
| 142 | 143 | /** |
| 143 | 144 | * Add a sync history record. |
| @@ -168,9 +169,9 @@ | ||
| 168 | 169 | if ($limit && $this->get_count() >= $limit) { |
| 169 | 170 | $this->cleanup_old_records(); |
| 170 | 171 | } |
| 171 | 172 | |
| 172 | - return $wpdb->insert($this->get_table_name(), $args); | |
| 173 | + return $wpdb->insert($this->get_table_name(), $args); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 173 | 174 | } |
| 174 | 175 | |
| 175 | 176 | /** |
| 176 | 177 | * Get total number of sync history items. |
| @@ -181,9 +182,8 @@ | ||
| 181 | 182 | // Ensure table exists |
| 182 | 183 | $this->maybe_create_table(); |
| 183 | 184 | |
| 184 | 185 | global $wpdb; |
| 185 | - $tableName = $this->get_table_name(); | |
| 186 | 186 | |
| 187 | 187 | $where_conditions = []; |
| 188 | 188 | $where_values = []; |
| 189 | 189 | |
| @@ -203,31 +203,27 @@ | ||
| 203 | 203 | $where_conditions[] = "status = %s"; |
| 204 | 204 | $where_values[] = $filters['status']; |
| 205 | 205 | } |
| 206 | 206 | } |
| 207 | - | |
| 207 | + | |
| 208 | 208 | if (!empty($filters['date_from'])) { |
| 209 | 209 | $where_conditions[] = "created_at >= %s"; |
| 210 | 210 | $where_values[] = $filters['date_from']; |
| 211 | 211 | } |
| 212 | - | |
| 212 | + | |
| 213 | 213 | if (!empty($filters['date_to'])) { |
| 214 | 214 | $where_conditions[] = "created_at <= %s"; |
| 215 | 215 | $where_values[] = $filters['date_to']; |
| 216 | 216 | } |
| 217 | - | |
| 218 | - $where_clause = ''; | |
| 219 | - if (!empty($where_conditions)) { | |
| 220 | - $where_clause = 'WHERE ' . implode(' AND ', $where_conditions); | |
| 221 | - } | |
| 222 | - | |
| 223 | - $query = "SELECT COUNT(*) FROM `$tableName` $where_clause"; | |
| 224 | - | |
| 217 | + | |
| 218 | + // Query assembled from literal placeholder fragments only; values bind via prepare(). | |
| 219 | + $query = "SELECT COUNT(*) FROM `{$wpdb->prefix}metasync_sync_history`" | |
| 220 | + . (!empty($where_conditions) ? ' WHERE ' . implode(' AND ', $where_conditions) : ''); | |
| 221 | + | |
| 225 | 222 | if (!empty($where_values)) { |
| 226 | - return (int) $wpdb->get_var($wpdb->prepare($query, $where_values)); | |
| 227 | - } else { | |
| 228 | - return (int) $wpdb->get_var($query); | |
| 223 | + return (int) $wpdb->get_var($wpdb->prepare($query, $where_values)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery -- literal fragments only, values bound via prepare() | |
| 229 | 224 | } |
| 225 | + return (int) $wpdb->get_var("SELECT COUNT(*) FROM `{$wpdb->prefix}metasync_sync_history`"); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 230 | 226 | } |
| 231 | 227 | |
| 232 | 228 | /** |
| 233 | 229 | * Get a single record by ID. |
| @@ -236,11 +232,10 @@ | ||
| 236 | 232 | */ |
| 237 | 233 | public function get_by_id($id) |
| 238 | 234 | { |
| 239 | 235 | global $wpdb; |
| 240 | - $tableName = $this->get_table_name(); | |
| 241 | - return $wpdb->get_row($wpdb->prepare( | |
| 242 | - "SELECT * FROM `$tableName` WHERE id = %d LIMIT 1", | |
| 236 | + return $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 237 | + "SELECT * FROM `{$wpdb->prefix}metasync_sync_history` WHERE id = %d LIMIT 1", | |
| 243 | 238 | intval($id) |
| 244 | 239 | )); |
| 245 | 240 | } |
| 246 | 241 | |
| @@ -251,11 +246,10 @@ | ||
| 251 | 246 | */ |
| 252 | 247 | public function delete_older_than_days($days = 90) |
| 253 | 248 | { |
| 254 | 249 | global $wpdb; |
| 255 | - $tableName = $this->get_table_name(); | |
| 256 | - return (int) $wpdb->query($wpdb->prepare( | |
| 257 | - "DELETE FROM `$tableName` WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)", | |
| 250 | + return (int) $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 251 | + "DELETE FROM `{$wpdb->prefix}metasync_sync_history` WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)", | |
| 258 | 252 | intval($days) |
| 259 | 253 | )); |
| 260 | 254 | } |
| 261 | 255 | |
| @@ -266,15 +260,11 @@ | ||
| 266 | 260 | */ |
| 267 | 261 | public function delete($items) |
| 268 | 262 | { |
| 269 | 263 | global $wpdb; |
| 270 | - $tableName = $this->get_table_name(); | |
| 271 | 264 | if (!is_array($items) || empty($items)) return 0; |
| 272 | - $ids = implode(',', array_fill(0, count($items), '%d')); | |
| 273 | - return $wpdb->query($wpdb->prepare( | |
| 274 | - " | |
| 275 | - DELETE FROM `$tableName` | |
| 276 | - WHERE `id` IN ($ids) ", | |
| 265 | + return $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 266 | + 'DELETE FROM `' . $wpdb->prefix . 'metasync_sync_history` WHERE `id` IN (' . implode(',', array_fill(0, count($items), '%d')) . ')', | |
| 277 | 267 | $items |
| 278 | 268 | )); |
| 279 | 269 | } |
| 280 | 270 | |
| @@ -283,10 +273,9 @@ | ||
| 283 | 273 | */ |
| 284 | 274 | public function clear_logs() |
| 285 | 275 | { |
| 286 | 276 | global $wpdb; |
| 287 | - $tableName = $this->get_table_name(); | |
| 288 | - $wpdb->query("TRUNCATE TABLE {$tableName}"); | |
| 277 | + $wpdb->query("TRUNCATE TABLE {$wpdb->prefix}metasync_sync_history"); | |
| 289 | 278 | } |
| 290 | 279 | |
| 291 | 280 | /** |
| 292 | 281 | * Clean up old records, keeping only the most recent ones. |
| @@ -298,12 +287,11 @@ | ||
| 298 | 287 | */ |
| 299 | 288 | public function cleanup_old_records($keep_count = 500) |
| 300 | 289 | { |
| 301 | 290 | global $wpdb; |
| 302 | - $tableName = $this->get_table_name(); | |
| 303 | 291 | |
| 304 | - $ids = $wpdb->get_col($wpdb->prepare( | |
| 305 | - "SELECT id FROM `$tableName` ORDER BY created_at DESC LIMIT %d", | |
| 292 | + $ids = $wpdb->get_col($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 293 | + "SELECT id FROM `{$wpdb->prefix}metasync_sync_history` ORDER BY created_at DESC LIMIT %d", | |
| 306 | 294 | $keep_count |
| 307 | 295 | )); |
| 308 | 296 | |
| 309 | 297 | if (empty($ids)) { |
| @@ -316,12 +304,10 @@ | ||
| 316 | 304 | if (count($ids) < $keep_count) { |
| 317 | 305 | return; |
| 318 | 306 | } |
| 319 | 307 | |
| 320 | - $placeholders = implode(',', array_fill(0, count($ids), '%d')); | |
| 321 | - | |
| 322 | - $wpdb->query($wpdb->prepare( | |
| 323 | - "DELETE FROM `$tableName` WHERE id NOT IN ($placeholders)", | |
| 308 | + $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 309 | + 'DELETE FROM `' . $wpdb->prefix . 'metasync_sync_history` WHERE id NOT IN (' . implode(',', array_fill(0, count($ids), '%d')) . ')', | |
| 324 | 310 | $ids |
| 325 | 311 | )); |
| 326 | 312 | } |
| 327 | 313 | |
| @@ -330,11 +316,11 @@ | ||
| 330 | 316 | */ |
| 331 | 317 | public function get_statistics() |
| 332 | 318 | { |
| 333 | 319 | global $wpdb; |
| 334 | - $tableName = $this->get_table_name(); | |
| 335 | 320 | |
| 336 | 321 | // Count both 'publish' and 'published' for published_count (handles legacy data) |
| 322 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- custom-table repository — no WordPress API exists for plugin tables; reads feed admin/cron views needing fresh rows | |
| 337 | 323 | $stats = $wpdb->get_row(" |
| 338 | 324 | SELECT |
| 339 | 325 | COUNT(*) as total_records, |
| 340 | 326 | SUM(CASE WHEN status IN ('published', 'publish') THEN 1 ELSE 0 END) as published_count, |
| @@ -340,9 +326,9 @@ | ||
| 340 | 326 | SUM(CASE WHEN status IN ('published', 'publish') THEN 1 ELSE 0 END) as published_count, |
| 341 | 327 | SUM(CASE WHEN status = 'draft' THEN 1 ELSE 0 END) as draft_count, |
| 342 | 328 | SUM(CASE WHEN source = 'OTTO SEO' THEN 1 ELSE 0 END) as otto_count, |
| 343 | 329 | SUM(CASE WHEN source = 'Content Genius' THEN 1 ELSE 0 END) as content_genius_count |
| 344 | - FROM `$tableName` | |
| 330 | + FROM `{$wpdb->prefix}metasync_sync_history` | |
| 345 | 331 | "); |
| 346 | 332 | |
| 347 | 333 | return $stats; |
| 348 | 334 | } |