PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
← All changes | includes/core/class-database.php +51 -44 1.10.02.7.0 View file →
@@ -96,16 +96,16 @@
96 96 */
97 97 public function query(string $query, array $args = []) {
98 98 if (!empty($args)) {
99 99 // Prepare the query first, then execute
100 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared in the line below
100 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared in the line below
101 101 $prepared_query = $this->wpdb->prepare($query, $args);
102 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Using prepared query from above
102 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Using prepared query from above
103 103 return $this->wpdb->query($prepared_query);
104 104 }
105 105
106 106 // For queries without parameters, execute directly (safe for static queries)
107 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- No user input in static queries
107 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- No user input in static queries
108 108 return $this->wpdb->query($query);
109 109 }
110 110
111 111 /**
@@ -118,16 +118,16 @@
118 118 */
119 119 public function get_row(string $query, array $args = [], string $output = OBJECT) {
120 120 if (!empty($args)) {
121 121 // Prepare the query first, then execute
122 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared in the line below
122 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared in the line below
123 123 $prepared_query = $this->wpdb->prepare($query, $args);
124 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Using prepared query from above
124 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Using prepared query from above
125 125 return $this->wpdb->get_row($prepared_query, $output);
126 126 }
127 127
128 128 // For queries without parameters, execute directly (safe for static queries)
129 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- No user input in static queries
129 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- No user input in static queries
130 130 return $this->wpdb->get_row($query, $output);
131 131 }
132 132
133 133 /**
@@ -140,15 +140,15 @@
140 140 */
141 141 public function get_results(string $query, array $args = [], string $output = OBJECT): array {
142 142 if (!empty($args)) {
143 143 // Prepare the query first, then execute
144 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared in the line below
144 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared in the line below
145 145 $prepared_query = $this->wpdb->prepare($query, $args);
146 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Using prepared query from above
146 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Using prepared query from above
147 147 $results = $this->wpdb->get_results($prepared_query, $output);
148 148 } else {
149 149 // For queries without parameters, execute directly (safe for static queries)
150 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- No user input in static queries
150 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- No user input in static queries
151 151 $results = $this->wpdb->get_results($query, $output);
152 152 }
153 153
154 154 return is_array($results) ? $results : [];
@@ -163,16 +163,16 @@
163 163 */
164 164 public function get_var(string $query, array $args = []) {
165 165 if (!empty($args)) {
166 166 // Prepare the query first, then execute
167 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared in the line below
167 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared in the line below
168 168 $prepared_query = $this->wpdb->prepare($query, $args);
169 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Using prepared query from above
169 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Using prepared query from above
170 170 return $this->wpdb->get_var($prepared_query);
171 171 }
172 172
173 173 // For queries without parameters, execute directly (safe for static queries)
174 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- No user input in static queries
174 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- No user input in static queries
175 175 return $this->wpdb->get_var($query);
176 176 }
177 177
178 178 /**
@@ -303,11 +303,13 @@
303 303 // Use %i placeholder for table identifier (WordPress 6.2+)
304 304 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder
305 305 $deleted = $this->wpdb->query(
306 306 $this->wpdb->prepare(
307 - 'DELETE FROM %i WHERE expires_at < %s',
307 + // expires_at is a bigint unix timestamp (see Cache_Manager),
308 + // so compare against time(), not a MySQL datetime string.
309 + 'DELETE FROM %i WHERE expires_at < %d',
308 310 $cache_table,
309 - current_time('mysql')
311 + time()
310 312 )
311 313 );
312 314 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder
313 315 } else {
@@ -315,10 +317,10 @@
315 317 $escaped_table = esc_sql($cache_table);
316 318 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
317 319 $deleted = $this->wpdb->query(
318 320 $this->wpdb->prepare(
319 - "DELETE FROM `{$escaped_table}` WHERE expires_at < %s",
320 - current_time('mysql')
321 + "DELETE FROM `{$escaped_table}` WHERE expires_at < %d",
322 + time()
321 323 )
322 324 );
323 325 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
324 326 }
@@ -350,9 +352,9 @@
350 352 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnsupportedIdentifierPlaceholder
351 353 } else {
352 354 // Fallback for older WordPress versions - table name is from our controlled list
353 355 $escaped_table = esc_sql($table);
354 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is properly escaped and from controlled source
356 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly escaped and from controlled source
355 357 $count = $this->wpdb->get_var("SELECT COUNT(*) FROM `{$escaped_table}`");
356 358 }
357 359 $stats[$key] = (int) $count;
358 360 }
@@ -441,12 +443,13 @@
441 443 $results = [];
442 444
443 445 // Define retention periods (in days)
444 446 $retention_config = [
445 - 'ai_usage' => 365, // 1 year
446 - 'seo_scores' => 180, // 6 months
447 - 'content_briefs' => 90, // 3 months
448 - 'seo_performance' => 90 // 3 months (performance data grows fast)
447 + 'ai_usage' => 365, // 1 year
448 + 'seo_scores' => 180, // 6 months
449 + 'content_briefs' => 90, // 3 months
450 + 'seo_performance' => 90, // 3 months (performance data grows fast)
451 + 'instant_indexing_logs' => 90 // 3 months (one row per URL per submit)
449 452 ];
450 453
451 454 foreach ($retention_config as $table_key => $retention_days) {
452 455 try {
@@ -508,15 +511,15 @@
508 511
509 512 $date_column = $date_columns[$table_key] ?? 'created_at';
510 513
511 514 // Delete old records
512 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Analytics cleanup requires direct database access, table and column names are validated internally
515 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Analytics cleanup requires direct database access, table and column names are validated internally
513 516 $deleted = $this->wpdb->query(
514 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL is properly prepared with placeholders
517 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- SQL is properly prepared with placeholders
515 518 $this->wpdb->prepare(
516 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table and column names are validated internally
517 - "DELETE FROM `{$table_name}` WHERE {$date_column} < %s",
518 - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $cutoff_date is validated and used as parameter
519 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table and column names are validated internally
520 + "DELETE FROM `{$table_name}` WHERE `{$date_column}` < %s",
521 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $cutoff_date is validated and used as parameter
519 522 $cutoff_date
520 523 )
521 524 );
522 525
@@ -548,15 +551,19 @@
548 551 }
549 552 }
550 553
551 554 // Also clear any user-specific analytics cache
552 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cache cleanup requires direct database access
553 - $user_cache_deleted = $this->wpdb->query(
554 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- wpdb->options is WordPress core table, safe to use
555 - "DELETE FROM {$this->wpdb->options}
556 - WHERE option_name LIKE '_transient_thinkrank_analytics_%'
557 - OR option_name LIKE '_transient_timeout_thinkrank_analytics_%'"
555 + $analytics_like = $this->wpdb->esc_like('_transient_thinkrank_analytics_') . '%';
556 + $timeout_like = $this->wpdb->esc_like('_transient_timeout_thinkrank_analytics_') . '%';
557 + $options_table = $this->wpdb->options;
558 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $options_table is from $wpdb->options, a WordPress core table name.
559 + $prepared_sql = $this->wpdb->prepare(
560 + "DELETE FROM {$options_table} WHERE option_name LIKE %s OR option_name LIKE %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
561 + $analytics_like,
562 + $timeout_like
558 563 );
564 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared above.
565 + $user_cache_deleted = $this->wpdb->query( $prepared_sql );
559 566
560 567 return [
561 568 'transients_cleared' => $cleared_count,
562 569 'user_cache_cleared' => $user_cache_deleted !== false ? (int) $user_cache_deleted : 0,
@@ -582,21 +589,21 @@
582 589
583 590 foreach ($tables as $table_key) {
584 591 $table_name = $this->get_table($table_key);
585 592
586 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Table size monitoring requires direct database access
587 - $size_result = $this->wpdb->get_row(
588 - $this->wpdb->prepare(
589 - "SELECT
590 - table_name,
591 - ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb,
592 - table_rows
593 - FROM information_schema.TABLES
594 - WHERE table_schema = %s AND table_name = %s",
595 - DB_NAME,
596 - $table_name
597 - )
593 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared with placeholders below.
594 + $prepared_sql = $this->wpdb->prepare(
595 + "SELECT
596 + table_name,
597 + ROUND(((data_length + index_length) / 1024 / 1024), 2) AS size_mb,
598 + table_rows
599 + FROM information_schema.TABLES
600 + WHERE table_schema = %s AND table_name = %s",
601 + DB_NAME,
602 + $table_name
598 603 );
604 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared above.
605 + $size_result = $this->wpdb->get_row( $prepared_sql );
599 606
600 607 if ($size_result) {
601 608 $sizes[$table_key] = [
602 609 'size_mb' => (float) $size_result->size_mb,