PluginProbe ʕ •ᴥ•ʔ
Check & Log Email – Easy Email Testing & Mail logging / 2.0.15
Check & Log Email – Easy Email Testing & Mail logging v2.0.15
2.0.15 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 2.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.13.1 2.0.13.2 2.0.14 2.0.2 2.0.3 2.0.4 2.0.5 2.0.5.1 2.0.6 2.0.7 2.0.8 2.0.9 trunk 0.5.7 0.6.0 0.6.1 0.6.2 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.12.1 1.0.13 1.0.13.1 1.0.2 1.0.3
check-email / include / Core / DB / Check_Email_Table_Manager.php
check-email / include / Core / DB Last commit date
Check_Email_Table_Manager.php 5 days ago
Check_Email_Table_Manager.php
918 lines
1 <?php namespace CheckEmail\Core\DB;
2
3 /**
4 * Handle installation and db table creation.
5 */
6 use CheckEmail\Core\Loadie;
7 use CheckEmail\Util;
8
9 defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
10
11 /**
12 * Helper class to create table.
13 */
14 class Check_Email_Table_Manager implements Loadie {
15
16 /* Database table name */
17 const LOG_TABLE_NAME = 'check_email_log';
18 const ERROR_TRACKER_TABLE_NAME = 'check_email_error_logs';
19
20 /* Database option name */
21 const DB_OPTION_NAME = 'check_email-log-db';
22
23 /* Database version */
24 const DB_VERSION = '0.3';
25
26 /**
27 * Setup hooks.
28 */
29 public function load() {
30 add_action( 'wpmu_new_blog', array( $this, 'create_table_for_new_blog' ) );
31
32 add_filter( 'wpmu_drop_tables', array( $this, 'delete_table_from_deleted_blog' ) );
33
34 add_filter( 'admin_init', array( $this, 'add_backtrace_segment_field' ) );
35 add_filter( 'admin_init', array( $this, 'add_open_count_field' ) );
36
37 $option = get_option( 'check-email-log-core' );
38 if ((isset($option['is_retention_amount_enable']) && $option['is_retention_amount_enable']) || (isset($option['is_retention_period_enable']) && $option['is_retention_period_enable'])) {
39 add_action('admin_init', array( $this, 'ck_mail_cron_schedule' ));
40 add_action('check_mail_cron_hook', array( $this, 'ck_mail_cron_execute' ));
41 }
42
43 // Do any DB upgrades.
44 $this->update_table_if_needed();
45 }
46
47 public function on_activate( $network_wide ) {
48 if ( is_multisite() && $network_wide ) {
49 // Note: if there are more than 10,000 blogs or
50 // if `wp_is_large_network` filter is set, then this may fail.
51 $sites = get_sites();
52
53 foreach ( $sites as $site ) {
54 switch_to_blog( $site->blog_id );
55 $this->create_table_if_needed();
56 restore_current_blog();
57 if (function_exists('ck_mail_create_error_logs') ) {
58 ck_mail_create_error_logs();
59 }
60 if (function_exists('ck_mail_create_spam_analyzer_table') ) {
61 ck_mail_create_spam_analyzer_table();
62 }
63 }
64 } else {
65 $this->create_table_if_needed();
66 if (function_exists('ck_mail_create_error_logs') ) {
67 ck_mail_create_error_logs();
68 }
69 if (function_exists('ck_mail_create_spam_analyzer_table') ) {
70 ck_mail_create_spam_analyzer_table();
71 }
72 }
73 }
74
75 /**
76 * Create email log table when a new blog is created.
77 */
78 public function create_table_for_new_blog( $blog_id ) {
79 if ( is_plugin_active_for_network( 'check-email-log/check-email.php' ) ) {
80 switch_to_blog( $blog_id );
81 $this->create_table_if_needed();
82 restore_current_blog();
83 }
84 }
85
86 /**
87 * Add email log table to the list of tables deleted when a blog is deleted.
88 */
89 public function delete_table_from_deleted_blog( $tables ) {
90 $tables[] = $this->get_log_table_name();
91
92 return $tables;
93 }
94
95 /**
96 * Get email log table name.
97 */
98 public function get_log_table_name() {
99 global $wpdb;
100
101 return $wpdb->prefix . self::LOG_TABLE_NAME;
102 }
103 public function get_error_tracker_table_name() {
104 global $wpdb;
105
106 return $wpdb->prefix . self::ERROR_TRACKER_TABLE_NAME;
107 }
108
109 public function insert_log( $data ) {
110 global $wpdb;
111
112 $table_name = $this->get_log_table_name();
113 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: custom table on insert
114 $wpdb->insert( $table_name, $data );
115 }
116
117 public function delete_logs( $ids ) {
118 global $wpdb;
119
120 $table_name = $this->get_log_table_name();
121
122 // ✅ Convert to safe integer array
123 if ( is_string( $ids ) ) {
124 $ids_array = explode(',', $ids);
125 } elseif ( is_array( $ids ) ) {
126 $ids_array = $ids;
127 } else {
128 return false;
129 }
130
131 $ids_array = array_map( 'absint', $ids_array );
132 $ids_array = array_filter( $ids_array ); // remove 0 / invalid
133
134 if ( empty( $ids_array ) ) {
135 return false;
136 }
137 $placeholders = implode( ',', array_fill( 0, count( $ids_array ), '%d' ) );
138 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
139 $query = $wpdb->prepare("DELETE FROM {$table_name} WHERE id IN ($placeholders)",
140 ...$ids_array
141 );
142 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
143 $result = $wpdb->query( $query );
144 if ( $result !== false ) {
145 foreach ( $ids_array as $id ) {
146 wp_cache_delete( $id, 'check_mail_log' );
147 }
148 }
149
150 return $result;
151 }
152
153 public function delete_all_logs() {
154 global $wpdb;
155
156 $table_name = $this->get_log_table_name();
157 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason: $table_name
158 $result = $wpdb->query( "DELETE FROM {$table_name}" );
159
160 if ($result !== false) {
161 wp_cache_delete('check_mail_log','check_mail_log');
162 }
163
164 return $result;
165 }
166
167 public function delete_error_tracker( $ids ) {
168 global $wpdb;
169
170 $table_name = $this->get_error_tracker_table_name();
171 if ( is_string( $ids ) ) {
172 $ids_array = explode( ',', $ids );
173 } elseif ( is_array( $ids ) ) {
174 $ids_array = $ids;
175 } else {
176 return false;
177 }
178 $ids_array = array_map( 'absint', $ids_array );
179 $ids_array = array_filter( $ids_array );
180
181 if ( empty( $ids_array ) ) {
182 return false;
183 }
184 $placeholders = implode( ',', array_fill( 0, count( $ids_array ), '%d' ) );
185 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
186 $query = $wpdb->prepare("DELETE FROM {$table_name} WHERE id IN ($placeholders)",
187 ...$ids_array
188 );
189 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
190 $result = $wpdb->query( $query );
191 if ( $result !== false ) {
192 foreach ( $ids_array as $id ) {
193 wp_cache_delete( $id, 'check_mail_log' );
194 }
195 }
196
197 return $result;
198 }
199
200 public function delete_all_error_tracker() {
201 global $wpdb;
202
203 $table_name = $this->get_error_tracker_table_name();
204 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason: $table_name
205 $result = $wpdb->query( "DELETE FROM {$table_name}" );
206
207 if ($result !== false) {
208 wp_cache_delete('check_mail_log','check_mail_log');
209 }
210
211 return $result;
212 }
213
214 public function delete_logs_older_than( $interval_in_days ) {
215 global $wpdb;
216 $table_name = $this->get_log_table_name();
217 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
218 $query = $wpdb->prepare( "DELETE FROM {$table_name} WHERE sent_date < DATE_SUB( CURDATE(), INTERVAL %d DAY )", $interval_in_days );
219 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- already prepare in query
220 $deleted_rows_count = $wpdb->query( $query );
221
222 return $deleted_rows_count;
223 }
224
225 public function fetch_log_items_by_id( $ids = array(), $additional_args = array() ) {
226 global $wpdb;
227 $table_name = $this->get_log_table_name();
228
229 $query = "SELECT * FROM {$table_name}";
230
231 $date_column_format_key = 'date_column_format';
232 if ( array_key_exists( $date_column_format_key, $additional_args ) && ! empty( $additional_args[ $date_column_format_key ] ) ) {
233 $query = "SELECT DATE_FORMAT(sent_date, \"{$additional_args[ $date_column_format_key ]}\") as sent_date_custom, el.* FROM {$table_name} as el";
234 }
235
236 if ( ! empty( $ids ) ) {
237 $ids = array_map( 'absint', $ids );
238
239 // Can't use wpdb->prepare for the below query.
240 $ids_list = esc_sql( implode( ',', $ids ) );
241
242 $query .= " where id IN ( {$ids_list} )";
243 }
244
245 return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine
246 }
247
248 public function fetch_log_items( $request, $per_page, $current_page_no ) {
249 global $wpdb;
250 $table_name = $this->get_log_table_name();
251
252 $query = 'SELECT * FROM ' . $table_name;
253 $count_query = 'SELECT count(*) FROM ' . $table_name;
254 $query_cond = '';
255 $where_sql = '';
256 $where_args = array();
257
258 if ( isset( $request['s'] ) && is_string( $request['s'] ) && $request['s'] !== '' ) {
259 $search_term = sanitize_text_field( wp_unslash( $request['s'] ) );
260 $search_term = trim( $search_term );
261
262 if ( Util\wp_chill_check_email_advanced_search_term( $search_term ) ) {
263 $predicates = Util\wp_chill_check_email_get_advanced_search_term_predicates( $search_term );
264
265 foreach ( $predicates as $column => $email ) {
266 switch ( $column ) {
267 case 'id':
268 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
269 $where_sql .= 'id = %d';
270 $where_args[] = (int) $email;
271 break;
272
273 case 'to':
274 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
275 $where_sql .= 'to_email LIKE %s';
276 $where_args[] = '%' . $wpdb->esc_like( $email ) . '%';
277 break;
278
279 case 'email':
280 $like = '%' . $wpdb->esc_like( $email ) . '%';
281 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
282 $where_sql .= ' ( ( to_email LIKE %s OR subject LIKE %s ) OR ( headers <> \'\' AND ( headers REGEXP %s OR headers REGEXP %s OR headers REGEXP %s OR headers REGEXP %s ) ) ) ';
283 $where_args[] = $like;
284 $where_args[] = $like;
285 $where_args[] = '[F|f]rom:.*' . $email;
286 $where_args[] = '[CC|Cc|cc]:.*' . $email;
287 $where_args[] = '[BCC|Bcc|bcc]:.*' . $email;
288 $where_args[] = '[R|r]eply-[T|t]o:.*' . $email;
289 break;
290
291 case 'cc':
292 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
293 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
294 $where_args[] = '[CC|Cc|cc]:.*' . $email;
295 break;
296
297 case 'bcc':
298 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
299 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
300 $where_args[] = '[BCC|Bcc|bcc]:.*' . $email;
301 break;
302
303 case 'reply-to':
304 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
305 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
306 $where_args[] = '[R|r]eply-to:.*' . $email;
307 break;
308 }
309 }
310 } else {
311 $like = '%' . $wpdb->esc_like( $search_term ) . '%';
312 $where_sql .= ' WHERE ( to_email LIKE %s OR subject LIKE %s OR message LIKE %s ) ';
313 $where_args[] = $like;
314 $where_args[] = $like;
315 $where_args[] = $like;
316 }
317 }
318
319 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
320 $search_date = sanitize_text_field( wp_unslash( $request['d'] ) );
321 $search_date = trim( $search_date );
322
323 // Only accept a real date shape; anything else is dropped rather than trusted.
324 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $search_date ) ) {
325 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
326 $where_sql .= 'sent_date BETWEEN %s AND %s';
327 $where_args[] = $search_date . ' 00:00:00';
328 $where_args[] = $search_date . ' 23:59:59';
329 }
330 }
331
332 if ( ! empty( $where_args ) ) {
333 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- placeholders are bound below
334 $query_cond .= $wpdb->prepare( $where_sql, $where_args );
335 }
336
337 if ( isset( $request['status'] ) && $request['status'] !== '' ) {
338 $status = trim( esc_sql( $request['status'] ) );
339 switch( $status ) {
340 case 'failed':
341 $query_cond .= empty( $query_cond ) ? " WHERE `result` IS NULL OR `result` = ''" : " AND ( `result` IS NULL OR `result` = '' )";
342 break;
343 case 'complete':
344 $query_cond .= empty( $query_cond ) ? " WHERE `result` IS NOT NULL AND `result` != ''" : " AND ( `result` IS NOT NULL AND `result` != '' )";
345 break;
346 default:
347 break;
348 }
349 }
350
351 // Ordering parameters.
352 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'sent_date';
353 if ( isset( $request['order'] ) ) {
354 $order = in_array( strtoupper($request['order']), array( 'DESC', 'ASC' ) ) ? esc_sql( $request['order'] ) : 'DESC';
355 }else{
356 $order = 'DESC';
357 }
358
359
360 if ( ! empty( $orderby ) & ! empty( $order ) ) {
361 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
362 }
363
364 // Find total number of items.
365 $count_query = $count_query . $query_cond;
366 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
367 $total_items = $wpdb->get_var( $count_query );
368
369 // Adjust the query to take pagination into account.
370 if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
371 $offset = ( $current_page_no - 1 ) * $per_page;
372 $query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
373 }
374
375 // Fetch the items.
376 $query = $query . $query_cond;
377 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason: Due to critical query not used prepare $table_name
378 $items = $wpdb->get_results( $query );
379
380 return array( $items, $total_items );
381 }
382
383 /*public function create_table_if_needed() {
384 global $wpdb;
385
386 $table_name = $this->get_log_table_name();
387 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
388 if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s",$wpdb->esc_like( $table_name ))) != $table_name ) {
389
390 $sql = $this->get_create_table_query();
391
392 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
393 dbDelta( $sql );
394
395 add_option( self::DB_OPTION_NAME, self::DB_VERSION );
396 }
397 }*/
398
399 public function create_table_if_needed() {
400 global $wpdb;
401
402 $table_name = $this->get_log_table_name();
403 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
404 $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
405
406 // If the table does NOT exist...
407 if ( ! $table_exists ) {
408 $sql = $this->get_create_table_query();
409
410 if ( ! function_exists( 'dbDelta' ) ) {
411 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
412 }
413 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared
414 $wpdb->query( $sql );
415 }
416
417 if ( ! function_exists( 'dbDelta' ) ) {
418 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
419 }
420
421 $sql = $this->get_create_table_query();
422 dbDelta( $sql );
423 }
424
425 public function get_logs_count() {
426 global $wpdb;
427 $table_name = $this->get_log_table_name();
428 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
429 // $query = $wpdb->prepare("SELECT count(*) FROM `$table_name`");
430 $query = "SELECT count(*) FROM `$table_name`";
431 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason:already used prepare
432 return $wpdb->get_var( $query );
433 }
434
435 public function fetch_log_id_by_data( $data ) {
436 if ( empty( $data ) || ! is_array( $data ) ) {
437 return 0;
438 }
439
440 global $wpdb;
441 $table_name = $this->get_log_table_name();
442
443 $query = "SELECT ID FROM {$table_name}";
444 $query_cond = '';
445 $where = array();
446
447 // Execute the following `if` conditions only when $data is array.
448 if ( array_key_exists( 'to', $data ) ) {
449 // Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
450 $to_email = Util\wp_chill_check_email_stringify( $data['to'] );
451
452 $to_email = trim( esc_sql( $to_email ) );
453 $where[] = $wpdb->prepare("to_email = %s",$to_email);
454 }
455
456 if ( array_key_exists( 'subject', $data ) ) {
457 $subject = trim( esc_sql( $data['subject'] ) );
458 $where[] = $wpdb->prepare("subject = %s",$subject);
459 }
460
461 if ( array_key_exists( 'attachments', $data ) ) {
462 if ( is_array( $data['attachments'] ) ) {
463 $attachments = count( $data['attachments'] ) > 0 ? 'true' : 'false';
464 } else {
465 $attachments = empty( $data['attachments'] ) ? 'false' : 'true';
466 }
467 $attachments = trim( esc_sql( $attachments ) );
468 $where[] = $wpdb->prepare("attachments = %s",$attachments);
469 }
470
471 foreach ( $where as $index => $value ) {
472 $query_cond .= 0 === $index ? ' WHERE ' : ' AND ';
473 $query_cond .= $value;
474 }
475
476 // Get only the latest logged item when multiple rows match.
477 $query_cond .= ' ORDER BY id DESC LIMIT 1';
478
479 $query = $query . $query_cond;
480 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
481 return absint( $wpdb->get_var( $query ) );
482 }
483
484 public function mark_log_as_failed( $log_item_id, $message ) {
485 global $wpdb;
486 $table_name = $this->get_log_table_name();
487 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
488 $wpdb->update(
489 $table_name,
490 array(
491 'result' => '0',
492 'error_message' => $message,
493 ),
494 array( 'ID' => $log_item_id ),
495 array(
496 '%d', // `result` format.
497 '%s', // `error_message` format.
498 ),
499 array(
500 '%d', // `ID` format.
501 )
502 );
503 }
504
505 private function update_table_if_needed() {
506 if ( get_option( self::DB_OPTION_NAME, false ) === self::DB_VERSION ) {
507 return;
508 }
509
510 $sql = $this->get_create_table_query();
511
512 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
513 dbDelta( $sql );
514
515 update_option( self::DB_OPTION_NAME, self::DB_VERSION );
516 }
517
518 private function get_create_table_query() {
519 global $wpdb;
520 $table_name = $this->get_log_table_name();
521 $charset_collate = $wpdb->get_charset_collate();
522
523 $sql = 'CREATE TABLE ' . $table_name . ' (
524 id mediumint(9) NOT NULL AUTO_INCREMENT,
525 to_email VARCHAR(500) NOT NULL,
526 subject VARCHAR(500) NOT NULL,
527 message TEXT NOT NULL,
528 backtrace_segment TEXT NOT NULL,
529 headers TEXT NOT NULL,
530 attachments TEXT NOT NULL,
531 sent_date timestamp NOT NULL,
532 attachment_name VARCHAR(1000),
533 ip_address VARCHAR(15),
534 result TINYINT(1),
535 error_message VARCHAR(1000),
536 PRIMARY KEY (id)
537 ) ' . $charset_collate . ';';
538
539 return $sql;
540 }
541
542 private function validate_columns( $column ) {
543 return in_array( $column, array( 'to' ), true );
544 }
545
546 public function query_log_items_by_column( $columns ) {
547 if ( ! is_array( $columns ) ) {
548 return;
549 }
550
551 $columns_keys = array_keys( $columns );
552 if ( ! array_filter( $columns_keys, array( $this, 'validate_columns' ) ) ) {
553 return;
554 }
555
556 global $wpdb;
557
558 $table_name = $this->get_log_table_name();
559 $query = "SELECT id, sent_date, to_email, subject FROM {$table_name}";
560 $query_cond = '';
561 $where = array();
562
563 // Execute the following `if` conditions only when $data is array.
564 if ( array_key_exists( 'to', $columns ) ) {
565 // Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
566 $to_email = Util\wp_chill_check_email_stringify( $columns['to'] );
567
568 $to_email = trim( esc_sql( $to_email ) );
569 $where[] = "to_email = '$to_email'";
570
571 foreach ( $where as $index => $value ) {
572 $query_cond .= 0 === $index ? ' WHERE ' : ' AND ';
573 $query_cond .= $value;
574 }
575
576 // Get only the latest logged item when multiple rows match.
577 $query_cond .= ' ORDER BY id DESC';
578
579 $query = $query . $query_cond;
580 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
581 return $wpdb->get_results( $query );
582 }
583 }
584
585 /**
586 * Add new backtrace_segment field to check_email_log table
587 * @since 1.0.12
588 * */
589 public function add_backtrace_segment_field(){
590 global $wpdb;
591 $table_name = $this->get_log_table_name();
592
593 // Field to check
594 $field_name = 'backtrace_segment';
595
596 // Query to check if the field exists in the table
597 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
598 $field_exists = $wpdb->get_results(
599 $wpdb->prepare(
600 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
601 "SHOW COLUMNS FROM $table_name LIKE %s",
602 $field_name
603 )
604 );
605
606 if(empty($field_exists)){
607 $query = "ALTER TABLE $table_name ADD backtrace_segment TEXT NULL DEFAULT NULL AFTER message";
608 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
609 $wpdb->query($query);
610 }
611 }
612 /**
613 * Add new open_count field to check_email_log table = will check email is opened count by user
614 * @since 1.0.12
615 * */
616 public function add_open_count_field(){
617 global $wpdb;
618 $table_name = $this->get_log_table_name();
619
620 // Field to check
621 $field_name = 'open_count';
622
623 // Query to check if the field exists in the table
624 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
625 $field_exists = $wpdb->get_results(
626 $wpdb->prepare(
627 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
628 "SHOW COLUMNS FROM $table_name LIKE %s",
629 $field_name
630 )
631 );
632
633 if(empty($field_exists)){
634 $query = "ALTER TABLE $table_name ADD open_tracking_id TEXT NULL DEFAULT NULL, ADD open_count TEXT NULL DEFAULT NULL AFTER message";
635 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
636 $wpdb->query($query);
637 }
638 }
639
640 public function fetch_log_count_by_status( $request, $per_page, $current_page_no,$status='all' ) {
641 global $wpdb;
642 $table_name = $this->get_log_table_name();
643
644 $count_query = 'SELECT count(*) FROM ' . $table_name;
645 $query_cond = '';
646 $where_sql = '';
647 $where_args = array();
648
649 if ( isset( $request['s'] ) && is_string( $request['s'] ) && $request['s'] !== '' ) {
650 $search_term = sanitize_text_field( wp_unslash( $request['s'] ) );
651 $search_term = trim( $search_term );
652
653 if ( Util\wp_chill_check_email_advanced_search_term( $search_term ) ) {
654 $predicates = Util\wp_chill_check_email_get_advanced_search_term_predicates( $search_term );
655
656 foreach ( $predicates as $column => $email ) {
657 switch ( $column ) {
658 case 'id':
659 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
660 $where_sql .= 'id = %d';
661 $where_args[] = (int) $email;
662 break;
663
664 case 'to':
665 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
666 $where_sql .= 'to_email LIKE %s';
667 $where_args[] = '%' . $wpdb->esc_like( $email ) . '%';
668 break;
669
670 case 'email':
671 $like = '%' . $wpdb->esc_like( $email ) . '%';
672 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
673 $where_sql .= ' ( ( to_email LIKE %s OR subject LIKE %s ) OR ( headers <> \'\' AND ( headers REGEXP %s OR headers REGEXP %s OR headers REGEXP %s OR headers REGEXP %s ) ) ) ';
674 $where_args[] = $like;
675 $where_args[] = $like;
676 $where_args[] = '[F|f]rom:.*' . $email;
677 $where_args[] = '[CC|Cc|cc]:.*' . $email;
678 $where_args[] = '[BCC|Bcc|bcc]:.*' . $email;
679 $where_args[] = '[R|r]eply-[T|t]o:.*' . $email;
680 break;
681
682 case 'cc':
683 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
684 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
685 $where_args[] = '[CC|Cc|cc]:.*' . $email;
686 break;
687
688 case 'bcc':
689 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
690 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
691 $where_args[] = '[BCC|Bcc|bcc]:.*' . $email;
692 break;
693
694 case 'reply-to':
695 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
696 $where_sql .= ' ( headers <> \'\' AND ( headers REGEXP %s ) ) ';
697 $where_args[] = '[R|r]eply-to:.*' . $email;
698 break;
699 }
700 }
701 } else {
702 $like = '%' . $wpdb->esc_like( $search_term ) . '%';
703 $where_sql .= ' WHERE ( to_email LIKE %s OR subject LIKE %s ) ';
704 $where_args[] = $like;
705 $where_args[] = $like;
706 }
707 }
708
709 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
710 $search_date = sanitize_text_field( wp_unslash( $request['d'] ) );
711 $search_date = trim( $search_date );
712
713 // TODO: confirm this matches the actual format your datepicker submits.
714 if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $search_date ) ) {
715 $where_sql .= empty( $where_sql ) ? ' WHERE ' : ' AND ';
716 $where_sql .= 'sent_date BETWEEN %s AND %s';
717 $where_args[] = $search_date . ' 00:00:00';
718 $where_args[] = $search_date . ' 23:59:59';
719 }
720 }
721
722 if ( ! empty( $where_args ) ) {
723 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- placeholders are bound below
724 $query_cond .= $wpdb->prepare( $where_sql, $where_args );
725 }
726
727 if ( ! empty( $status ) ) {
728 $status = trim( esc_sql( $status ) );
729 if ( $status !== 'all' ) {
730 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
731
732 switch ( $status ) {
733 case 'failed':
734 $query_cond .= ' `result` = 0';
735 break;
736 case 'complete':
737 $query_cond .= ' `result` != 0';
738 break;
739 default:
740 break;
741 }
742 }
743 }
744
745 // Ordering parameters (matches original — harmless on a COUNT query, kept for parity).
746 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'sent_date';
747 if ( isset( $request['order'] ) ) {
748 $order = in_array( strtoupper( $request['order'] ), array( 'DESC', 'ASC' ), true ) ? esc_sql( $request['order'] ) : 'DESC';
749 } else {
750 $order = 'DESC';
751 }
752
753 if ( ! empty( $orderby ) & ! empty( $order ) ) {
754 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
755 }
756
757 $count_query = $count_query . $query_cond;
758 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- $query_cond built via $wpdb->prepare() above
759 $total_items = $wpdb->get_var( $count_query );
760
761 return $total_items;
762 }
763
764 public function delete_log_older_than($timeInterval = null)
765 {
766 global $wpdb;
767 $table_name = $this->get_log_table_name();
768 $option = get_option( 'check-email-log-core' );
769 if (isset($option['is_retention_amount_enable']) && isset($option['retention_amount']) && $option['is_retention_amount_enable']) {
770 $limit= intval($option['retention_amount']);
771 if(!empty($limit)){
772 $count_query = 'SELECT count(*) FROM ' . $table_name;
773 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
774 $total_items = $wpdb->get_var( $count_query );
775 if ($total_items > $limit) {
776 $data_to_delete = $total_items - $limit;
777 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
778 $old_posts = $wpdb->get_col( $wpdb->prepare(
779 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
780 "SELECT ID FROM $table_name
781 ORDER BY ID ASC
782 LIMIT %d",$data_to_delete) );
783
784 // Delete the logs
785 foreach ($old_posts as $column_value) {
786 $sql = $wpdb->prepare(
787 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
788 "DELETE FROM $table_name WHERE ID = %d",
789 $column_value
790 );
791 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
792 $wpdb->query($sql);
793 }
794 }
795
796 }
797 }
798 if (isset($option['is_retention_period_enable']) && $option['is_retention_period_enable']) {
799
800 if ($option['log_retention_period'] == 'custom_in_days') {
801 $custom_in_days = empty($option['log_retention_period_in_days']) ? 1 : intval($option['log_retention_period_in_days']);
802 $time_interval = strtotime('+' . $custom_in_days. ' days');
803 }else{
804 $periods = array( '1_day' =>86400,
805 '1_week' =>604800,
806 '1_month' =>2419200,
807 '6_month' =>15780000,
808 '1_year' =>31560000
809 );
810 $time_interval = $periods[$option['log_retention_period']];
811 }
812 $timestamp = time() - $time_interval;
813
814 $sql = "DELETE FROM " . $table_name . " WHERE Unix_timestamp(sent_date) <= %d";
815 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
816 $sql = $wpdb->prepare($sql, $timestamp);
817 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
818 $wpdb->query($sql);
819 }
820 }
821
822 function ck_mail_cron_schedule() {
823 if (!wp_next_scheduled('check_mail_cron_hook')) {
824 wp_schedule_event(time(), 'daily', 'check_mail_cron_hook');
825 }
826 }
827
828 function ck_mail_cron_execute() {
829 $this->delete_log_older_than();
830 // error_log('Cron job executed at' . gmdate('Y-m-d H:i:s'));
831 }
832
833 public function fetch_error_tracker_items( $request, $per_page, $current_page_no ) {
834 global $wpdb;
835 $table_name = $this->get_error_tracker_table_name();
836
837 $query = 'SELECT * FROM ' . $table_name;
838 $count_query = 'SELECT count(*) FROM ' . $table_name;
839 $query_cond = '';
840
841 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
842 $search_date = trim( esc_sql( $request['d'] ) );
843 if ( '' === $query_cond ) {
844 $query_cond .= " WHERE created_at BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
845 } else {
846 $query_cond .= " AND created_at BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
847 }
848 }
849 if ( isset( $request['status'] ) && $request['status'] !== '' ) {
850 $status = trim( esc_sql( $request['status'] ) );
851 switch( $status ) {
852 case 'failed':
853 $query_cond .= " WHERE `event_type` IS NULL OR `event_type` = ''";
854 break;
855 case 'complete':
856 $query_cond .= " WHERE `event_type` IS NOT NULL AND `event_type` != ''";
857 break;
858 default:
859 break;
860 }
861 }
862
863 // Ordering parameters.
864 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'created_at';
865 if ( isset( $request['order'] ) ) {
866 $order = in_array( strtoupper($request['order']), array( 'DESC', 'ASC' ) ) ? esc_sql( $request['order'] ) : 'DESC';
867 }else{
868 $order = 'DESC';
869 }
870
871
872 if ( ! empty( $orderby ) & ! empty( $order ) ) {
873 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
874 }
875
876 // Find total number of items.
877 $count_query = $count_query . $query_cond;
878 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
879 $total_items = $wpdb->get_var( $count_query );
880
881 // Adjust the query to take pagination into account.
882 if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
883 $offset = ( $current_page_no - 1 ) * $per_page;
884 $query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
885 }
886
887 // Fetch the items.
888 $query = $query . $query_cond;
889 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason: Due to critical query not used prepare $table_name
890 $items = $wpdb->get_results( $query );
891
892 return array( $items, $total_items );
893 }
894
895 public function fetch_error_tracker_items_by_id( $ids = array(), $additional_args = array() ) {
896 global $wpdb;
897 $table_name = $this->get_error_tracker_table_name();
898
899 $query = "SELECT * FROM {$table_name}";
900
901 $date_column_format_key = 'date_column_format';
902 if ( array_key_exists( $date_column_format_key, $additional_args ) && ! empty( $additional_args[ $date_column_format_key ] ) ) {
903 $query = "SELECT DATE_FORMAT(created_at, \"{$additional_args[ $date_column_format_key ]}\") as sent_date_custom, el.* FROM {$table_name} as el";
904 }
905
906 if ( ! empty( $ids ) ) {
907 $ids = array_map( 'absint', $ids );
908
909 // Can't use wpdb->prepare for the below query.
910 $ids_list = esc_sql( implode( ',', $ids ) );
911
912 $query .= " where id IN ( {$ids_list} )";
913 }
914
915 return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine
916 }
917 }
918