1
|
|
|
<?php namespace EmailLog\Core\DB; |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Handle installation and db table creation. |
5
|
|
|
*/ |
6
|
|
|
use EmailLog\Core\Loadie; |
7
|
|
|
use EmailLog\Util; |
8
|
|
|
|
9
|
|
|
defined( 'ABSPATH' ) || exit; // Exit if accessed directly. |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Helper class to create table. |
13
|
|
|
* |
14
|
|
|
* @since 2.0.0 |
15
|
|
|
*/ |
16
|
|
|
class TableManager implements Loadie { |
17
|
|
|
|
18
|
|
|
/* Database table name */ |
19
|
|
|
const LOG_TABLE_NAME = 'email_log'; |
20
|
|
|
|
21
|
|
|
/* Database option name */ |
22
|
|
|
const DB_OPTION_NAME = 'email-log-db'; |
23
|
|
|
|
24
|
|
|
/* Database version */ |
25
|
|
|
const DB_VERSION = '0.2'; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The format of the Date column used when fetching the log items. |
29
|
|
|
* |
30
|
|
|
* @since 2.3.0 |
31
|
|
|
* |
32
|
|
|
* @var string |
33
|
|
|
*/ |
34
|
|
|
private $date_column_format; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Setter for `$date_column_format`. |
38
|
|
|
* |
39
|
|
|
* @since 2.3.0 |
40
|
|
|
* |
41
|
|
|
* @param string $format |
42
|
|
|
*/ |
43
|
|
|
public function set_date_column_format( $format ) { |
44
|
|
|
$this->date_column_format = $format; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Setup hooks. |
49
|
|
|
*/ |
50
|
|
|
public function load() { |
51
|
|
|
add_action( 'wpmu_new_blog', array( $this, 'create_table_for_new_blog' ) ); |
52
|
|
|
|
53
|
|
|
add_filter( 'wpmu_drop_tables', array( $this, 'delete_table_from_deleted_blog' ) ); |
54
|
|
|
|
55
|
|
|
// Do any DB upgrades. |
56
|
|
|
$this->update_table_if_needed(); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* On plugin activation, create table if needed. |
61
|
|
|
* |
62
|
|
|
* @param bool $network_wide True if the plugin was network activated. |
63
|
|
|
*/ |
64
|
|
|
public function on_activate( $network_wide ) { |
65
|
|
|
if ( is_multisite() && $network_wide ) { |
66
|
|
|
// Note: if there are more than 10,000 blogs or |
67
|
|
|
// if `wp_is_large_network` filter is set, then this may fail. |
68
|
|
|
$sites = get_sites(); |
69
|
|
|
|
70
|
|
|
foreach ( $sites as $site ) { |
71
|
|
|
switch_to_blog( $site['blog_id'] ); |
72
|
|
|
$this->create_table_if_needed(); |
73
|
|
|
restore_current_blog(); |
74
|
|
|
} |
75
|
|
|
} else { |
76
|
|
|
$this->create_table_if_needed(); |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Create email log table when a new blog is created. |
82
|
|
|
* |
83
|
|
|
* @param int $blog_id Blog Id. |
84
|
|
|
*/ |
85
|
|
|
public function create_table_for_new_blog( $blog_id ) { |
86
|
|
|
if ( is_plugin_active_for_network( 'email-log/email-log.php' ) ) { |
87
|
|
|
switch_to_blog( $blog_id ); |
88
|
|
|
$this->create_table_if_needed(); |
89
|
|
|
restore_current_blog(); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Add email log table to the list of tables deleted when a blog is deleted. |
95
|
|
|
* |
96
|
|
|
* @param array $tables List of tables to be deleted. |
97
|
|
|
* |
98
|
|
|
* @return string[] $tables Modified list of tables to be deleted. |
99
|
|
|
*/ |
100
|
1 |
|
public function delete_table_from_deleted_blog( $tables ) { |
101
|
1 |
|
$tables[] = $this->get_log_table_name(); |
102
|
|
|
|
103
|
1 |
|
return $tables; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Get email log table name. |
108
|
|
|
* |
109
|
|
|
* @return string Email Log Table name. |
110
|
|
|
*/ |
111
|
2 |
|
public function get_log_table_name() { |
112
|
2 |
|
global $wpdb; |
113
|
|
|
|
114
|
2 |
|
return $wpdb->prefix . self::LOG_TABLE_NAME; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Insert log data into DB. |
119
|
|
|
* |
120
|
|
|
* @param array $data Data to be inserted. |
121
|
|
|
*/ |
122
|
|
|
public function insert_log( $data ) { |
123
|
|
|
global $wpdb; |
124
|
|
|
|
125
|
|
|
$table_name = $this->get_log_table_name(); |
126
|
|
|
$wpdb->insert( $table_name, $data ); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Delete log entries by ids. |
131
|
|
|
* |
132
|
|
|
* @param string $ids Comma separated list of log ids. |
133
|
|
|
* |
134
|
|
|
* @return false|int Number of log entries that got deleted. False on failure. |
135
|
|
|
*/ |
136
|
|
|
public function delete_logs( $ids ) { |
137
|
|
|
global $wpdb; |
138
|
|
|
|
139
|
|
|
$table_name = $this->get_log_table_name(); |
140
|
|
|
|
141
|
|
|
// Can't use wpdb->prepare for the below query. If used it results in this bug // https://github.com/sudar/email-log/issues/13. |
142
|
|
|
$ids = esc_sql( $ids ); |
143
|
|
|
|
144
|
|
|
return $wpdb->query( "DELETE FROM {$table_name} where id IN ( {$ids} )" ); //@codingStandardsIgnoreLine |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Delete all log entries. |
149
|
|
|
* |
150
|
|
|
* @return false|int Number of log entries that got deleted. False on failure. |
151
|
|
|
*/ |
152
|
|
|
public function delete_all_logs() { |
153
|
|
|
global $wpdb; |
154
|
|
|
|
155
|
|
|
$table_name = $this->get_log_table_name(); |
156
|
|
|
|
157
|
|
|
return $wpdb->query( "DELETE FROM {$table_name}" ); //@codingStandardsIgnoreLine |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Deletes Email Logs older than the specified interval. |
162
|
|
|
* |
163
|
|
|
* @param int $interval_in_days No. of days beyond which logs are to be deleted. |
164
|
|
|
* |
165
|
|
|
* @return int $deleted_rows_count Count of rows deleted. |
166
|
|
|
*/ |
167
|
|
|
public function delete_logs_older_than( $interval_in_days ) { |
168
|
|
|
global $wpdb; |
169
|
|
|
$table_name = $this->get_log_table_name(); |
170
|
|
|
|
171
|
|
|
$query = $wpdb->prepare( "DELETE FROM {$table_name} WHERE sent_date < DATE_SUB( CURDATE(), INTERVAL %d DAY )", $interval_in_days ); |
172
|
|
|
$deleted_rows_count = $wpdb->query( $query ); |
173
|
|
|
|
174
|
|
|
return $deleted_rows_count; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Fetch log item by ID. |
179
|
|
|
* |
180
|
|
|
* @param array $ids Optional. Array of IDs of the log items to be retrieved. |
181
|
|
|
* |
182
|
|
|
* @return array Log item(s). |
183
|
|
|
*/ |
184
|
|
|
public function fetch_log_items_by_id( $ids = array() ) { |
185
|
|
|
global $wpdb; |
186
|
|
|
$table_name = $this->get_log_table_name(); |
187
|
|
|
|
188
|
|
|
$query = "SELECT * FROM {$table_name}"; |
189
|
|
|
|
190
|
|
|
// When `$this->date_column_format` exists, should replace the `$query` var. |
|
|
|
|
191
|
|
|
if ( ! empty( $this->date_column_format ) ) { |
192
|
|
|
$query = "SELECT DATE_FORMAT(sent_date, \"{$this->date_column_format}\") as sent_date_custom, el.* FROM {$table_name} as el"; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if ( ! empty( $ids ) ) { |
196
|
|
|
$ids = array_map( 'absint', $ids ); |
197
|
|
|
|
198
|
|
|
// Can't use wpdb->prepare for the below query. If used it results in this bug https://github.com/sudar/email-log/issues/13. |
199
|
|
|
$ids_list = esc_sql( implode( ',', $ids ) ); |
200
|
|
|
|
201
|
|
|
$query .= " where id IN ( {$ids_list} )"; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Fetch log items. |
209
|
|
|
* |
210
|
|
|
* @param array $request Request object. |
211
|
|
|
* @param int $per_page Entries per page. |
212
|
|
|
* @param int $current_page_no Current page no. |
213
|
|
|
* |
214
|
|
|
* @return array Log entries and total items count. |
215
|
|
|
*/ |
216
|
|
|
public function fetch_log_items( $request, $per_page, $current_page_no ) { |
217
|
|
|
global $wpdb; |
218
|
|
|
$table_name = $this->get_log_table_name(); |
219
|
|
|
|
220
|
|
|
$query = 'SELECT * FROM ' . $table_name; |
221
|
|
|
$count_query = 'SELECT count(*) FROM ' . $table_name; |
222
|
|
|
$query_cond = ''; |
223
|
|
|
|
224
|
|
|
if ( isset( $request['s'] ) && $request['s'] !== '' ) { |
225
|
|
|
$search_term = trim( esc_sql( $request['s'] ) ); |
|
|
|
|
226
|
|
|
$query_cond .= " WHERE ( to_email LIKE '%$search_term%' OR subject LIKE '%$search_term%' ) "; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ( isset( $request['d'] ) && $request['d'] !== '' ) { |
230
|
|
|
$search_date = trim( esc_sql( $request['d'] ) ); |
231
|
|
|
if ( '' === $query_cond ) { |
232
|
|
|
$query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' "; |
233
|
|
|
} else { |
234
|
|
|
$query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' "; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// Ordering parameters. |
239
|
|
|
$orderby = ! empty( $request['orderby'] ) ? esc_sql( $request['orderby'] ) : 'sent_date'; |
240
|
|
|
$order = ! empty( $request['order'] ) ? esc_sql( $request['order'] ) : 'DESC'; |
241
|
|
|
|
242
|
|
|
if ( ! empty( $orderby ) & ! empty( $order ) ) { |
|
|
|
|
243
|
|
|
$query_cond .= ' ORDER BY ' . $orderby . ' ' . $order; |
|
|
|
|
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
// Find total number of items. |
247
|
|
|
$count_query = $count_query . $query_cond; |
248
|
|
|
$total_items = $wpdb->get_var( $count_query ); |
249
|
|
|
|
250
|
|
|
// Adjust the query to take pagination into account. |
251
|
|
|
if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) { |
252
|
|
|
$offset = ( $current_page_no - 1 ) * $per_page; |
253
|
|
|
$query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// Fetch the items. |
257
|
|
|
$query = $query . $query_cond; |
258
|
|
|
$items = $wpdb->get_results( $query ); |
259
|
|
|
|
260
|
|
|
return array( $items, $total_items ); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Create email log table. |
265
|
|
|
* |
266
|
|
|
* @access private |
267
|
|
|
* |
268
|
|
|
* @global object $wpdb |
269
|
|
|
*/ |
270
|
|
|
private function create_table_if_needed() { |
271
|
|
|
global $wpdb; |
272
|
|
|
|
273
|
|
|
$table_name = $this->get_log_table_name(); |
274
|
|
|
|
275
|
|
|
if ( $wpdb->get_var( "show tables like '{$table_name}'" ) != $table_name ) { |
276
|
|
|
|
277
|
|
|
$sql = $this->get_create_table_query(); |
278
|
|
|
|
279
|
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; |
280
|
|
|
dbDelta( $sql ); |
281
|
|
|
|
282
|
|
|
add_option( self::DB_OPTION_NAME, self::DB_VERSION ); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Get the total number of email logs. |
288
|
|
|
* |
289
|
|
|
* @return int Total email log count |
290
|
|
|
*/ |
291
|
|
|
public function get_logs_count() { |
292
|
|
|
global $wpdb; |
293
|
|
|
|
294
|
|
|
$query = 'SELECT count(*) FROM ' . $this->get_log_table_name(); |
295
|
|
|
|
296
|
|
|
return $wpdb->get_var( $query ); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Fetches the log item by the item data. |
301
|
|
|
* |
302
|
|
|
* Use this method to get the log item when the error instance only returns the log item data. |
303
|
|
|
* |
304
|
|
|
* @param array $data Array of Email information. { |
305
|
|
|
* |
306
|
|
|
* @type array|string to |
307
|
|
|
* @type string subject |
308
|
|
|
* @type string message |
309
|
|
|
* @type array|string headers |
310
|
|
|
* @type array|string attachments |
311
|
|
|
* } |
312
|
|
|
* |
313
|
|
|
* @return int |
314
|
|
|
*/ |
315
|
|
|
public function fetch_log_item_by_item_data( $data ) { |
316
|
|
|
if ( empty( $data ) || ! is_array( $data ) ) { |
317
|
|
|
return 0; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
global $wpdb; |
321
|
|
|
$table_name = $this->get_log_table_name(); |
322
|
|
|
|
323
|
|
|
$query = "SELECT ID FROM {$table_name}"; |
324
|
|
|
$query_cond = ''; |
325
|
|
|
$where = array(); |
326
|
|
|
|
327
|
|
|
// Execute the following `if` conditions only when $data is array. |
328
|
|
|
if ( array_key_exists( 'to', $data ) ) { |
329
|
|
|
// Since the value is stored as CSV in DB, convert the values from error data to CSV to compare. |
330
|
|
|
$data['to'] = Util\join_array_elements_with_delimiter( $data['to'] ); |
|
|
|
|
331
|
|
|
|
332
|
|
|
$to_email = trim( esc_sql( $data['to'] ) ); |
|
|
|
|
333
|
|
|
$where[] = "to_email = '$to_email'"; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
if ( array_key_exists( 'subject', $data ) ) { |
337
|
|
|
$subject = trim( esc_sql( $data['subject'] ) ); |
338
|
|
|
$where[] = "subject = '$subject'"; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
if ( array_key_exists( 'attachments', $data ) ) { |
342
|
|
|
if ( is_array( $data['attachments'] ) ) { |
343
|
|
|
$attachments = count( $data['attachments'] ) > 0 ? 'true' : 'false'; |
344
|
|
|
} else { |
345
|
|
|
$attachments = empty( $data['attachments'] ) ? 'false' : 'true'; |
346
|
|
|
} |
347
|
|
|
$attachments = trim( esc_sql( $attachments ) ); |
348
|
|
|
$where[] = "attachments = '$attachments'"; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
foreach ( $where as $index => $value ) { |
352
|
|
|
$query_cond .= 0 === $index ? ' WHERE ' : ' AND '; |
353
|
|
|
$query_cond .= $value; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// Get only the latest logged item when multiple rows match. |
357
|
|
|
$query_cond .= ' ORDER BY id DESC LIMIT 1'; |
358
|
|
|
|
359
|
|
|
$query = $query . $query_cond; |
360
|
|
|
|
361
|
|
|
return absint( $wpdb->get_var( $query ) ); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Sets email sent status as failed for the given log item. |
366
|
|
|
* |
367
|
|
|
* @since 2.3.0 |
368
|
|
|
* |
369
|
|
|
* @param int $log_item_id ID of the log item whose email sent status should be set to failed. |
370
|
|
|
*/ |
371
|
|
|
public function set_log_item_fail_status_by_id( $log_item_id ) { |
372
|
|
|
global $wpdb; |
373
|
|
|
$table_name = $this->get_log_table_name(); |
374
|
|
|
|
375
|
|
|
$wpdb->update( |
376
|
|
|
$table_name, |
377
|
|
|
array( 'result' => '0' ), |
378
|
|
|
array( 'ID' => $log_item_id ), |
379
|
|
|
array( '%d' ), |
380
|
|
|
array( '%d' ) |
381
|
|
|
); |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Updates the DB schema. |
386
|
|
|
* |
387
|
|
|
* Adds new columns to the Database as of v0.2. |
388
|
|
|
* |
389
|
|
|
* @since 2.3.0 |
390
|
|
|
*/ |
391
|
|
|
private function update_table_if_needed() { |
392
|
|
|
$existing_db_version = get_option( self::DB_OPTION_NAME, false ); |
393
|
|
|
$updated_db_version = self::DB_VERSION; |
394
|
|
|
|
395
|
|
|
// Bail out when the DB version is `0.1` or equals to self::DB_VERSION |
396
|
|
|
if ( ! $existing_db_version || $existing_db_version !== '0.1' || $existing_db_version === $updated_db_version ) { |
397
|
|
|
return; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
$sql = $this->get_create_table_query(); |
401
|
|
|
|
402
|
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; |
403
|
|
|
dbDelta( $sql ); |
404
|
|
|
|
405
|
|
|
update_option( self::DB_OPTION_NAME, self::DB_VERSION ); |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Gets the Create Table query. |
410
|
|
|
* |
411
|
|
|
* @since 2.3.0 |
412
|
|
|
* |
413
|
|
|
* @return string |
414
|
|
|
*/ |
415
|
|
|
private function get_create_table_query() { |
416
|
|
|
global $wpdb; |
417
|
|
|
$table_name = $this->get_log_table_name(); |
418
|
|
|
$charset_collate = $wpdb->get_charset_collate(); |
419
|
|
|
|
420
|
|
|
$sql = 'CREATE TABLE ' . $table_name . ' ( |
421
|
|
|
id mediumint(9) NOT NULL AUTO_INCREMENT, |
422
|
|
|
to_email VARCHAR(250) NOT NULL, |
423
|
|
|
subject VARCHAR(250) NOT NULL, |
424
|
|
|
message TEXT NOT NULL, |
425
|
|
|
headers TEXT NOT NULL, |
426
|
|
|
attachments TEXT NOT NULL, |
427
|
|
|
sent_date timestamp NOT NULL, |
428
|
|
|
attachment_name VARCHAR(1000), |
429
|
|
|
ip_address VARCHAR(15), |
430
|
|
|
result TINYINT(1), |
431
|
|
|
PRIMARY KEY (id) |
432
|
|
|
) ' . $charset_collate . ';'; |
433
|
|
|
|
434
|
|
|
return $sql; |
435
|
|
|
} |
436
|
|
|
} |
437
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.