Completed
Pull Request — dev/2.3.0 (#167)
by Maria Daniel Deepak
07:40 queued 05:02
created

TableManager::get_create_table_query()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 20
ccs 0
cts 7
cp 0
crap 2
rs 10
c 0
b 0
f 0
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.
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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'] ) );
0 ignored issues
show
Bug introduced by
It seems like esc_sql($request['s']) can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

225
			$search_term = trim( /** @scrutinizer ignore-type */ esc_sql( $request['s'] ) );
Loading history...
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 ) ) {
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise & or did you mean &&?
Loading history...
243
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
0 ignored issues
show
Bug introduced by
Are you sure $order of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . /** @scrutinizer ignore-type */ $order;
Loading history...
Bug introduced by
Are you sure $orderby of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

243
			$query_cond .= ' ORDER BY ' . /** @scrutinizer ignore-type */ $orderby . ' ' . $order;
Loading history...
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'] );
0 ignored issues
show
Bug introduced by
The function join_array_elements_with_delimiter was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

330
			$data['to'] = /** @scrutinizer ignore-call */ Util\join_array_elements_with_delimiter( $data['to'] );
Loading history...
331
332
			$to_email = trim( esc_sql( $data['to'] ) );
0 ignored issues
show
Bug introduced by
It seems like esc_sql($data['to']) can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
			$to_email = trim( /** @scrutinizer ignore-type */ esc_sql( $data['to'] ) );
Loading history...
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