PluginProbe ʕ •ᴥ•ʔ
Check & Log Email – Easy Email Testing & Mail logging / trunk
Check & Log Email – Easy Email Testing & Mail logging vtrunk
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 1 month ago
Check_Email_Table_Manager.php
931 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
256 if ( isset( $request['s'] ) && is_string( $request['s'] ) && $request['s'] !== '' ) {
257 $search_term = isset($request['s'])
258 ? sanitize_text_field( wp_unslash( $request['s'] ) )
259 : '';
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 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
269 $query_cond .= "id = '$email'";
270 break;
271 case 'to':
272 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
273 $query_cond .= "to_email LIKE '%$email%'";
274 break;
275 case 'email':
276 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
277 $query_cond .= ' ( '; /* Begin 1st */
278 $query_cond .= " ( to_email LIKE '%$email%' OR subject LIKE '%$email%' ) "; /* Begin 2nd & End 2nd */
279 $query_cond .= ' OR ';
280 $query_cond .= ' ( '; /* Begin 3rd */
281 $query_cond .= "headers <> ''";
282 $query_cond .= ' AND ';
283 $query_cond .= ' ( '; /* Begin 4th */
284 $query_cond .= "headers REGEXP '[F|f]rom:.*$email' OR ";
285 $query_cond .= "headers REGEXP '[CC|Cc|cc]:.*$email' OR ";
286 $query_cond .= "headers REGEXP '[BCC|Bcc|bcc]:.*$email' OR ";
287 $query_cond .= "headers REGEXP '[R|r]eply-[T|t]o:.*$email'";
288 $query_cond .= ' ) '; /* End 4th */
289 $query_cond .= ' ) '; /* End 3rd */
290 $query_cond .= ' ) '; /* End 1st */
291 break;
292 case 'cc':
293 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
294 $query_cond .= ' ( '; /* Begin 1st */
295 $query_cond .= "headers <> ''";
296 $query_cond .= ' AND ';
297 $query_cond .= ' ( '; /* Begin 2nd */
298 $query_cond .= "headers REGEXP '[CC|Cc|cc]:.*$email' ";
299 $query_cond .= ' ) '; /* End 2nd */
300 $query_cond .= ' ) '; /* End 1st */
301 break;
302 case 'bcc':
303 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
304 $query_cond .= ' ( '; /* Begin 1st */
305 $query_cond .= "headers <> ''";
306 $query_cond .= ' AND ';
307 $query_cond .= ' ( '; /* Begin 2nd */
308 $query_cond .= "headers REGEXP '[BCC|Bcc|bcc]:.*$email' ";
309 $query_cond .= ' ) '; /* End 2nd */
310 $query_cond .= ' ) '; /* End 1st */
311 break;
312 case 'reply-to':
313 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
314 $query_cond .= ' ( '; /* Begin 1st */
315 $query_cond .= "headers <> ''";
316 $query_cond .= ' AND ';
317 $query_cond .= ' ( '; /* Begin 2nd */
318 $query_cond .= "headers REGEXP '[R|r]eply-to:.*$email' ";
319 $query_cond .= ' ) '; /* End 2nd */
320 $query_cond .= ' ) '; /* End 1st */
321 break;
322 }
323 }
324 } else {
325 $like = '%' . $wpdb->esc_like( $search_term ) . '%';
326
327 $query_cond .= $wpdb->prepare(
328 " WHERE ( to_email LIKE %s OR subject LIKE %s OR message LIKE %s ) ",
329 $like,
330 $like,
331 $like
332 );
333 }
334 }
335
336 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
337 $search_date = sanitize_text_field( wp_unslash( $request['d'] ) );
338 $search_date = trim($search_date);
339 if ( '' === $query_cond ) {
340 $query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
341 } else {
342 $query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
343 }
344 }
345 if ( isset( $request['status'] ) && $request['status'] !== '' ) {
346 $status = trim( esc_sql( $request['status'] ) );
347 switch( $status ) {
348 case 'failed':
349 $query_cond .= " WHERE `result` IS NULL OR `result` = ''";
350 break;
351 case 'complete':
352 $query_cond .= " WHERE `result` IS NOT NULL AND `result` != ''";
353 break;
354 default:
355 break;
356 }
357 }
358
359 // Ordering parameters.
360 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'sent_date';
361 if ( isset( $request['order'] ) ) {
362 $order = in_array( strtoupper($request['order']), array( 'DESC', 'ASC' ) ) ? esc_sql( $request['order'] ) : 'DESC';
363 }else{
364 $order = 'DESC';
365 }
366
367
368 if ( ! empty( $orderby ) & ! empty( $order ) ) {
369 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
370 }
371
372 // Find total number of items.
373 $count_query = $count_query . $query_cond;
374 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
375 $total_items = $wpdb->get_var( $count_query );
376
377 // Adjust the query to take pagination into account.
378 if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
379 $offset = ( $current_page_no - 1 ) * $per_page;
380 $query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
381 }
382
383 // Fetch the items.
384 $query = $query . $query_cond;
385 // 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
386 $items = $wpdb->get_results( $query );
387
388 return array( $items, $total_items );
389 }
390
391 /*public function create_table_if_needed() {
392 global $wpdb;
393
394 $table_name = $this->get_log_table_name();
395 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
396 if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s",$wpdb->esc_like( $table_name ))) != $table_name ) {
397
398 $sql = $this->get_create_table_query();
399
400 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
401 dbDelta( $sql );
402
403 add_option( self::DB_OPTION_NAME, self::DB_VERSION );
404 }
405 }*/
406
407 public function create_table_if_needed() {
408 global $wpdb;
409
410 $table_name = $this->get_log_table_name();
411 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
412 $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
413
414 // If the table does NOT exist...
415 if ( ! $table_exists ) {
416 $sql = $this->get_create_table_query();
417
418 if ( ! function_exists( 'dbDelta' ) ) {
419 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
420 }
421 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared
422 $wpdb->query( $sql );
423 }
424
425 if ( ! function_exists( 'dbDelta' ) ) {
426 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
427 }
428
429 $sql = $this->get_create_table_query();
430 dbDelta( $sql );
431 }
432
433 public function get_logs_count() {
434 global $wpdb;
435 $table_name = $this->get_log_table_name();
436 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
437 // $query = $wpdb->prepare("SELECT count(*) FROM `$table_name`");
438 $query = "SELECT count(*) FROM `$table_name`";
439 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason:already used prepare
440 return $wpdb->get_var( $query );
441 }
442
443 public function fetch_log_id_by_data( $data ) {
444 if ( empty( $data ) || ! is_array( $data ) ) {
445 return 0;
446 }
447
448 global $wpdb;
449 $table_name = $this->get_log_table_name();
450
451 $query = "SELECT ID FROM {$table_name}";
452 $query_cond = '';
453 $where = array();
454
455 // Execute the following `if` conditions only when $data is array.
456 if ( array_key_exists( 'to', $data ) ) {
457 // Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
458 $to_email = Util\wp_chill_check_email_stringify( $data['to'] );
459
460 $to_email = trim( esc_sql( $to_email ) );
461 $where[] = $wpdb->prepare("to_email = %s",$to_email);
462 }
463
464 if ( array_key_exists( 'subject', $data ) ) {
465 $subject = trim( esc_sql( $data['subject'] ) );
466 $where[] = $wpdb->prepare("subject = %s",$subject);
467 }
468
469 if ( array_key_exists( 'attachments', $data ) ) {
470 if ( is_array( $data['attachments'] ) ) {
471 $attachments = count( $data['attachments'] ) > 0 ? 'true' : 'false';
472 } else {
473 $attachments = empty( $data['attachments'] ) ? 'false' : 'true';
474 }
475 $attachments = trim( esc_sql( $attachments ) );
476 $where[] = $wpdb->prepare("attachments = %s",$attachments);
477 }
478
479 foreach ( $where as $index => $value ) {
480 $query_cond .= 0 === $index ? ' WHERE ' : ' AND ';
481 $query_cond .= $value;
482 }
483
484 // Get only the latest logged item when multiple rows match.
485 $query_cond .= ' ORDER BY id DESC LIMIT 1';
486
487 $query = $query . $query_cond;
488 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
489 return absint( $wpdb->get_var( $query ) );
490 }
491
492 public function mark_log_as_failed( $log_item_id, $message ) {
493 global $wpdb;
494 $table_name = $this->get_log_table_name();
495 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
496 $wpdb->update(
497 $table_name,
498 array(
499 'result' => '0',
500 'error_message' => $message,
501 ),
502 array( 'ID' => $log_item_id ),
503 array(
504 '%d', // `result` format.
505 '%s', // `error_message` format.
506 ),
507 array(
508 '%d', // `ID` format.
509 )
510 );
511 }
512
513 private function update_table_if_needed() {
514 if ( get_option( self::DB_OPTION_NAME, false ) === self::DB_VERSION ) {
515 return;
516 }
517
518 $sql = $this->get_create_table_query();
519
520 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
521 dbDelta( $sql );
522
523 update_option( self::DB_OPTION_NAME, self::DB_VERSION );
524 }
525
526 private function get_create_table_query() {
527 global $wpdb;
528 $table_name = $this->get_log_table_name();
529 $charset_collate = $wpdb->get_charset_collate();
530
531 $sql = 'CREATE TABLE ' . $table_name . ' (
532 id mediumint(9) NOT NULL AUTO_INCREMENT,
533 to_email VARCHAR(500) NOT NULL,
534 subject VARCHAR(500) NOT NULL,
535 message TEXT NOT NULL,
536 backtrace_segment TEXT NOT NULL,
537 headers TEXT NOT NULL,
538 attachments TEXT NOT NULL,
539 sent_date timestamp NOT NULL,
540 attachment_name VARCHAR(1000),
541 ip_address VARCHAR(15),
542 result TINYINT(1),
543 error_message VARCHAR(1000),
544 PRIMARY KEY (id)
545 ) ' . $charset_collate . ';';
546
547 return $sql;
548 }
549
550 private function validate_columns( $column ) {
551 return in_array( $column, array( 'to' ), true );
552 }
553
554 public function query_log_items_by_column( $columns ) {
555 if ( ! is_array( $columns ) ) {
556 return;
557 }
558
559 $columns_keys = array_keys( $columns );
560 if ( ! array_filter( $columns_keys, array( $this, 'validate_columns' ) ) ) {
561 return;
562 }
563
564 global $wpdb;
565
566 $table_name = $this->get_log_table_name();
567 $query = "SELECT id, sent_date, to_email, subject FROM {$table_name}";
568 $query_cond = '';
569 $where = array();
570
571 // Execute the following `if` conditions only when $data is array.
572 if ( array_key_exists( 'to', $columns ) ) {
573 // Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
574 $to_email = Util\wp_chill_check_email_stringify( $columns['to'] );
575
576 $to_email = trim( esc_sql( $to_email ) );
577 $where[] = "to_email = '$to_email'";
578
579 foreach ( $where as $index => $value ) {
580 $query_cond .= 0 === $index ? ' WHERE ' : ' AND ';
581 $query_cond .= $value;
582 }
583
584 // Get only the latest logged item when multiple rows match.
585 $query_cond .= ' ORDER BY id DESC';
586
587 $query = $query . $query_cond;
588 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
589 return $wpdb->get_results( $query );
590 }
591 }
592
593 /**
594 * Add new backtrace_segment field to check_email_log table
595 * @since 1.0.12
596 * */
597 public function add_backtrace_segment_field(){
598 global $wpdb;
599 $table_name = $this->get_log_table_name();
600
601 // Field to check
602 $field_name = 'backtrace_segment';
603
604 // Query to check if the field exists in the table
605 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
606 $field_exists = $wpdb->get_results(
607 $wpdb->prepare(
608 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
609 "SHOW COLUMNS FROM $table_name LIKE %s",
610 $field_name
611 )
612 );
613
614 if(empty($field_exists)){
615 $query = "ALTER TABLE $table_name ADD backtrace_segment TEXT NULL DEFAULT NULL AFTER message";
616 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
617 $wpdb->query($query);
618 }
619 }
620 /**
621 * Add new open_count field to check_email_log table = will check email is opened count by user
622 * @since 1.0.12
623 * */
624 public function add_open_count_field(){
625 global $wpdb;
626 $table_name = $this->get_log_table_name();
627
628 // Field to check
629 $field_name = 'open_count';
630
631 // Query to check if the field exists in the table
632 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
633 $field_exists = $wpdb->get_results(
634 $wpdb->prepare(
635 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
636 "SHOW COLUMNS FROM $table_name LIKE %s",
637 $field_name
638 )
639 );
640
641 if(empty($field_exists)){
642 $query = "ALTER TABLE $table_name ADD open_tracking_id TEXT NULL DEFAULT NULL, ADD open_count TEXT NULL DEFAULT NULL AFTER message";
643 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
644 $wpdb->query($query);
645 }
646 }
647
648 public function fetch_log_count_by_status( $request, $per_page, $current_page_no,$status='all' ) {
649 global $wpdb;
650 $table_name = $this->get_log_table_name();
651
652
653 $count_query = 'SELECT count(*) FROM ' . $table_name;
654 $query_cond = '';
655
656 if ( isset( $request['s'] ) && is_string( $request['s'] ) && $request['s'] !== '' ) {
657 $search_term = trim( esc_sql( $request['s'] ) );
658
659 if ( Util\wp_chill_check_email_advanced_search_term( $search_term ) ) {
660 $predicates = Util\wp_chill_check_email_get_advanced_search_term_predicates( $search_term );
661
662 foreach ( $predicates as $column => $email ) {
663 switch ( $column ) {
664 case 'id':
665 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
666 $query_cond .= "id = '$email'";
667 break;
668 case 'to':
669 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
670 $query_cond .= "to_email LIKE '%$email%'";
671 break;
672 case 'email':
673 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
674 $query_cond .= ' ( '; /* Begin 1st */
675 $query_cond .= " ( to_email LIKE '%$email%' OR subject LIKE '%$email%' ) "; /* Begin 2nd & End 2nd */
676 $query_cond .= ' OR ';
677 $query_cond .= ' ( '; /* Begin 3rd */
678 $query_cond .= "headers <> ''";
679 $query_cond .= ' AND ';
680 $query_cond .= ' ( '; /* Begin 4th */
681 $query_cond .= "headers REGEXP '[F|f]rom:.*$email' OR ";
682 $query_cond .= "headers REGEXP '[CC|Cc|cc]:.*$email' OR ";
683 $query_cond .= "headers REGEXP '[BCC|Bcc|bcc]:.*$email' OR ";
684 $query_cond .= "headers REGEXP '[R|r]eply-[T|t]o:.*$email'";
685 $query_cond .= ' ) '; /* End 4th */
686 $query_cond .= ' ) '; /* End 3rd */
687 $query_cond .= ' ) '; /* End 1st */
688 break;
689 case 'cc':
690 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
691 $query_cond .= ' ( '; /* Begin 1st */
692 $query_cond .= "headers <> ''";
693 $query_cond .= ' AND ';
694 $query_cond .= ' ( '; /* Begin 2nd */
695 $query_cond .= "headers REGEXP '[CC|Cc|cc]:.*$email' ";
696 $query_cond .= ' ) '; /* End 2nd */
697 $query_cond .= ' ) '; /* End 1st */
698 break;
699 case 'bcc':
700 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
701 $query_cond .= ' ( '; /* Begin 1st */
702 $query_cond .= "headers <> ''";
703 $query_cond .= ' AND ';
704 $query_cond .= ' ( '; /* Begin 2nd */
705 $query_cond .= "headers REGEXP '[BCC|Bcc|bcc]:.*$email' ";
706 $query_cond .= ' ) '; /* End 2nd */
707 $query_cond .= ' ) '; /* End 1st */
708 break;
709 case 'reply-to':
710 $query_cond .= empty( $query_cond ) ? ' WHERE ' : ' AND ';
711 $query_cond .= ' ( '; /* Begin 1st */
712 $query_cond .= "headers <> ''";
713 $query_cond .= ' AND ';
714 $query_cond .= ' ( '; /* Begin 2nd */
715 $query_cond .= "headers REGEXP '[R|r]eply-to:.*$email' ";
716 $query_cond .= ' ) '; /* End 2nd */
717 $query_cond .= ' ) '; /* End 1st */
718 break;
719 }
720 }
721 } else {
722 $query_cond .= " WHERE ( to_email LIKE '%$search_term%' OR subject LIKE '%$search_term%' ) ";
723 }
724 }
725
726 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
727 $search_date = trim( esc_sql( $request['d'] ) );
728 if ( '' === $query_cond ) {
729 $query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
730 } else {
731 $query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
732 }
733 }
734 if ( !empty($status) ) {
735 $status = trim( esc_sql( $status ) );
736 if ($status != 'all') {
737 if ( empty($request['d']) && empty($request['s']) ) {
738 $query_cond .= " WHERE ";
739 }else{
740 $query_cond .= " AND ";
741 }
742 }
743
744 // print_r($query_cond);die;
745
746 switch( $status ) {
747 case 'failed':
748 $query_cond .= " `result` = 0";
749 break;
750 case 'complete':
751 $query_cond .= " `result` != 0";
752 break;
753 default:
754 break;
755 }
756 }
757
758 // Ordering parameters.
759 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'sent_date';
760 if ( isset( $request['order'] ) ) {
761 $order = in_array( strtoupper($request['order']), array( 'DESC', 'ASC' ) ) ? esc_sql( $request['order'] ) : 'DESC';
762 }else{
763 $order = 'DESC';
764 }
765
766 if ( ! empty( $orderby ) & ! empty( $order ) ) {
767 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
768 }
769
770 // Find total number of items.
771 $count_query = $count_query . $query_cond;
772 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Reason using critical conditions in query
773 $total_items = $wpdb->get_var( $count_query );
774 return $total_items;
775 }
776
777 public function delete_log_older_than($timeInterval = null)
778 {
779 global $wpdb;
780 $table_name = $this->get_log_table_name();
781 $option = get_option( 'check-email-log-core' );
782 if (isset($option['is_retention_amount_enable']) && isset($option['retention_amount']) && $option['is_retention_amount_enable']) {
783 $limit= intval($option['retention_amount']);
784 if(!empty($limit)){
785 $count_query = 'SELECT count(*) FROM ' . $table_name;
786 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
787 $total_items = $wpdb->get_var( $count_query );
788 if ($total_items > $limit) {
789 $data_to_delete = $total_items - $limit;
790 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
791 $old_posts = $wpdb->get_col( $wpdb->prepare(
792 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
793 "SELECT ID FROM $table_name
794 ORDER BY ID ASC
795 LIMIT %d",$data_to_delete) );
796
797 // Delete the logs
798 foreach ($old_posts as $column_value) {
799 $sql = $wpdb->prepare(
800 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
801 "DELETE FROM $table_name WHERE ID = %d",
802 $column_value
803 );
804 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
805 $wpdb->query($sql);
806 }
807 }
808
809 }
810 }
811 if (isset($option['is_retention_period_enable']) && $option['is_retention_period_enable']) {
812
813 if ($option['log_retention_period'] == 'custom_in_days') {
814 $custom_in_days = empty($option['log_retention_period_in_days']) ? 1 : intval($option['log_retention_period_in_days']);
815 $time_interval = strtotime('+' . $custom_in_days. ' days');
816 }else{
817 $periods = array( '1_day' =>86400,
818 '1_week' =>604800,
819 '1_month' =>2419200,
820 '6_month' =>15780000,
821 '1_year' =>31560000
822 );
823 $time_interval = $periods[$option['log_retention_period']];
824 }
825 $timestamp = time() - $time_interval;
826
827 $sql = "DELETE FROM " . $table_name . " WHERE Unix_timestamp(sent_date) <= %d";
828 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
829 $sql = $wpdb->prepare($sql, $timestamp);
830 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
831 $wpdb->query($sql);
832 }
833 }
834
835 function ck_mail_cron_schedule() {
836 if (!wp_next_scheduled('check_mail_cron_hook')) {
837 wp_schedule_event(time(), 'daily', 'check_mail_cron_hook');
838 }
839 }
840
841 function ck_mail_cron_execute() {
842 $this->delete_log_older_than();
843 // error_log('Cron job executed at' . gmdate('Y-m-d H:i:s'));
844 }
845
846 public function fetch_error_tracker_items( $request, $per_page, $current_page_no ) {
847 global $wpdb;
848 $table_name = $this->get_error_tracker_table_name();
849
850 $query = 'SELECT * FROM ' . $table_name;
851 $count_query = 'SELECT count(*) FROM ' . $table_name;
852 $query_cond = '';
853
854 if ( isset( $request['d'] ) && $request['d'] !== '' ) {
855 $search_date = trim( esc_sql( $request['d'] ) );
856 if ( '' === $query_cond ) {
857 $query_cond .= " WHERE created_at BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
858 } else {
859 $query_cond .= " AND created_at BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
860 }
861 }
862 if ( isset( $request['status'] ) && $request['status'] !== '' ) {
863 $status = trim( esc_sql( $request['status'] ) );
864 switch( $status ) {
865 case 'failed':
866 $query_cond .= " WHERE `event_type` IS NULL OR `event_type` = ''";
867 break;
868 case 'complete':
869 $query_cond .= " WHERE `event_type` IS NOT NULL AND `event_type` != ''";
870 break;
871 default:
872 break;
873 }
874 }
875
876 // Ordering parameters.
877 $orderby = ! empty( $request['orderby'] ) ? sanitize_sql_orderby( $request['orderby'] ) : 'created_at';
878 if ( isset( $request['order'] ) ) {
879 $order = in_array( strtoupper($request['order']), array( 'DESC', 'ASC' ) ) ? esc_sql( $request['order'] ) : 'DESC';
880 }else{
881 $order = 'DESC';
882 }
883
884
885 if ( ! empty( $orderby ) & ! empty( $order ) ) {
886 $query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
887 }
888
889 // Find total number of items.
890 $count_query = $count_query . $query_cond;
891 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
892 $total_items = $wpdb->get_var( $count_query );
893
894 // Adjust the query to take pagination into account.
895 if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
896 $offset = ( $current_page_no - 1 ) * $per_page;
897 $query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
898 }
899
900 // Fetch the items.
901 $query = $query . $query_cond;
902 // 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
903 $items = $wpdb->get_results( $query );
904
905 return array( $items, $total_items );
906 }
907
908 public function fetch_error_tracker_items_by_id( $ids = array(), $additional_args = array() ) {
909 global $wpdb;
910 $table_name = $this->get_error_tracker_table_name();
911
912 $query = "SELECT * FROM {$table_name}";
913
914 $date_column_format_key = 'date_column_format';
915 if ( array_key_exists( $date_column_format_key, $additional_args ) && ! empty( $additional_args[ $date_column_format_key ] ) ) {
916 $query = "SELECT DATE_FORMAT(created_at, \"{$additional_args[ $date_column_format_key ]}\") as sent_date_custom, el.* FROM {$table_name} as el";
917 }
918
919 if ( ! empty( $ids ) ) {
920 $ids = array_map( 'absint', $ids );
921
922 // Can't use wpdb->prepare for the below query.
923 $ids_list = esc_sql( implode( ',', $ids ) );
924
925 $query .= " where id IN ( {$ids_list} )";
926 }
927
928 return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine
929 }
930 }
931