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 | 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 }