Passed
Push — 193-feature/search-permalink ( 77bed7...5759fa )
by Maria Daniel Deepak
03:17
created

TableManager::fetch_log_items()   D

Complexity

Conditions 18
Paths 144

Size

Total Lines 62
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 0
Metric Value
cc 18
eloc 37
nc 144
nop 3
dl 0
loc 62
ccs 0
cts 42
cp 0
crap 342
rs 4.5
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * Setup hooks.
29
	 */
30
	public function load() {
31
		add_action( 'wpmu_new_blog', array( $this, 'create_table_for_new_blog' ) );
32
33
		add_filter( 'wpmu_drop_tables', array( $this, 'delete_table_from_deleted_blog' ) );
34
35
		// Do any DB upgrades.
36
		$this->update_table_if_needed();
37
	}
38
39
	/**
40
	 * On plugin activation, create table if needed.
41
	 *
42
	 * @param bool $network_wide True if the plugin was network activated.
43
	 */
44
	public function on_activate( $network_wide ) {
45
		if ( is_multisite() && $network_wide ) {
46
			// Note: if there are more than 10,000 blogs or
47
			// if `wp_is_large_network` filter is set, then this may fail.
48
			$sites = get_sites();
49
50
			foreach ( $sites as $site ) {
51
				switch_to_blog( $site['blog_id'] );
52
				$this->create_table_if_needed();
53
				restore_current_blog();
54
			}
55
		} else {
56
			$this->create_table_if_needed();
57
		}
58
	}
59
60
	/**
61
	 * Create email log table when a new blog is created.
62
	 *
63
	 * @param int $blog_id Blog Id.
64
	 */
65
	public function create_table_for_new_blog( $blog_id ) {
66
		if ( is_plugin_active_for_network( 'email-log/email-log.php' ) ) {
67
			switch_to_blog( $blog_id );
68
			$this->create_table_if_needed();
69
			restore_current_blog();
70
		}
71
	}
72
73
	/**
74
	 * Add email log table to the list of tables deleted when a blog is deleted.
75
	 *
76
	 * @param array $tables List of tables to be deleted.
77
	 *
78
	 * @return string[] $tables Modified list of tables to be deleted.
79
	 */
80 1
	public function delete_table_from_deleted_blog( $tables ) {
81 1
		$tables[] = $this->get_log_table_name();
82
83 1
		return $tables;
84
	}
85
86
	/**
87
	 * Get email log table name.
88
	 *
89
	 * @return string Email Log Table name.
90
	 */
91 2
	public function get_log_table_name() {
92 2
		global $wpdb;
93
94 2
		return $wpdb->prefix . self::LOG_TABLE_NAME;
95
	}
96
97
	/**
98
	 * Insert log data into DB.
99
	 *
100
	 * @param array $data Data to be inserted.
101
	 */
102
	public function insert_log( $data ) {
103
		global $wpdb;
104
105
		$table_name = $this->get_log_table_name();
106
		$wpdb->insert( $table_name, $data );
107
	}
108
109
	/**
110
	 * Delete log entries by ids.
111
	 *
112
	 * @param string $ids Comma separated list of log ids.
113
	 *
114
	 * @return false|int Number of log entries that got deleted. False on failure.
115
	 */
116
	public function delete_logs( $ids ) {
117
		global $wpdb;
118
119
		$table_name = $this->get_log_table_name();
120
121
		// Can't use wpdb->prepare for the below query. If used it results in this bug // https://github.com/sudar/email-log/issues/13.
122
		$ids = esc_sql( $ids );
123
124
		return $wpdb->query( "DELETE FROM {$table_name} where id IN ( {$ids} )" ); //@codingStandardsIgnoreLine
125
	}
126
127
	/**
128
	 * Delete all log entries.
129
	 *
130
	 * @return false|int Number of log entries that got deleted. False on failure.
131
	 */
132
	public function delete_all_logs() {
133
		global $wpdb;
134
135
		$table_name = $this->get_log_table_name();
136
137
		return $wpdb->query( "DELETE FROM {$table_name}" ); //@codingStandardsIgnoreLine
138
	}
139
140
	/**
141
	 * Deletes Email Logs older than the specified interval.
142
	 *
143
	 * @param int $interval_in_days No. of days beyond which logs are to be deleted.
144
	 *
145
	 * @return int $deleted_rows_count  Count of rows deleted.
146
	 */
147
	public function delete_logs_older_than( $interval_in_days ) {
148
		global $wpdb;
149
		$table_name = $this->get_log_table_name();
150
151
		$query              = $wpdb->prepare( "DELETE FROM {$table_name} WHERE sent_date < DATE_SUB( CURDATE(), INTERVAL %d DAY )", $interval_in_days );
152
		$deleted_rows_count = $wpdb->query( $query );
153
154
		return $deleted_rows_count;
155
	}
156
157
	/**
158
	 * Fetch log item by ID.
159
	 *
160
	 * @param array $ids             Optional. Array of IDs of the log items to be retrieved.
161
	 * @param array $additional_args {
162
	 *                               Optional. Array of additional args.
163
	 *
164
	 * @type string $date_column_format MySQL date column format. Refer
165
	 *
166
	 * @link  https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format
167
	 * }
168
	 *
169
	 * @return array Log item(s).
170
	 */
171
	public function fetch_log_items_by_id( $ids = array(), $additional_args = array() ) {
172
		global $wpdb;
173
		$table_name = $this->get_log_table_name();
174
175
		$query = "SELECT * FROM {$table_name}";
176
177
		// When `date_column_format` exists, should replace the `$query` var.
178
		$date_column_format_key = 'date_column_format';
179
		if ( array_key_exists( $date_column_format_key, $additional_args ) && ! empty( $additional_args[ $date_column_format_key ] ) ) {
180
			$query = "SELECT DATE_FORMAT(sent_date, \"{$additional_args[ $date_column_format_key ]}\") as sent_date_custom, el.* FROM {$table_name} as el";
181
		}
182
183
		if ( ! empty( $ids ) ) {
184
			$ids = array_map( 'absint', $ids );
185
186
			// Can't use wpdb->prepare for the below query. If used it results in this bug https://github.com/sudar/email-log/issues/13.
187
			$ids_list = esc_sql( implode( ',', $ids ) );
188
189
			$query .= " where id IN ( {$ids_list} )";
190
		}
191
192
		return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine
193
	}
194
195
	/**
196
	 * Fetch log items.
197
	 *
198
	 * @param array $request         Request object.
199
	 * @param int   $per_page        Entries per page.
200
	 * @param int   $current_page_no Current page no.
201
	 *
202
	 * @return array Log entries and total items count.
203
	 */
204
	public function fetch_log_items( $request, $per_page, $current_page_no ) {
205
		global $wpdb;
206
		$table_name = $this->get_log_table_name();
207
208
		$query       = 'SELECT * FROM ' . $table_name;
209
		$count_query = 'SELECT count(*) FROM ' . $table_name;
210
		$query_cond  = '';
211
212
		if ( isset( $request['s'] ) && is_string( $request['s'] ) && $request['s'] !== '' ) {
213
			$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

213
			$search_term = trim( /** @scrutinizer ignore-type */ esc_sql( $request['s'] ) );
Loading history...
214
215
			if ( Util\is_advanced_search_term( $search_term ) ) {
0 ignored issues
show
Bug introduced by
The function is_advanced_search_term 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

215
			if ( /** @scrutinizer ignore-call */ Util\is_advanced_search_term( $search_term ) ) {
Loading history...
216
				$predicates = Util\get_advanced_search_term_predicates( $search_term );
0 ignored issues
show
Bug introduced by
The function get_advanced_search_term_predicates 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

216
				$predicates = /** @scrutinizer ignore-call */ Util\get_advanced_search_term_predicates( $search_term );
Loading history...
217
218
				foreach ( $predicates as $column => $email ) {
219
					switch ( $column ) {
220
						case 'to':
221
							$query_cond .= ( empty( $query_cond ) ? " WHERE " : " AND " ) . "to_email LIKE '%$email%'";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal WHERE does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal AND does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
222
							break;
223
						case 'email':
224
							$query_cond .= ( empty( $query_cond ) ? " WHERE " : " AND " ) . "( to_email LIKE '%$email%' OR subject LIKE '%$email%' ) ";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal WHERE does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal AND does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
225
							break;
226
					}
227
				}
228
229
			} else {
230
				$query_cond .= " WHERE ( to_email LIKE '%$search_term%' OR subject LIKE '%$search_term%' ) ";
231
			}
232
		}
233
234
		if ( isset( $request['d'] ) && $request['d'] !== '' ) {
235
			$search_date = trim( esc_sql( $request['d'] ) );
236
			if ( '' === $query_cond ) {
237
				$query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
238
			} else {
239
				$query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
240
			}
241
		}
242
243
		// Ordering parameters.
244
		$orderby = ! empty( $request['orderby'] ) ? esc_sql( $request['orderby'] ) : 'sent_date';
245
		$order   = ! empty( $request['order'] ) ? esc_sql( $request['order'] ) : 'DESC';
246
247
		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...
248
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
0 ignored issues
show
Bug introduced by
Are you sure $order of type array|string 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

248
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . /** @scrutinizer ignore-type */ $order;
Loading history...
Bug introduced by
Are you sure $orderby of type array|string 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

248
			$query_cond .= ' ORDER BY ' . /** @scrutinizer ignore-type */ $orderby . ' ' . $order;
Loading history...
249
		}
250
251
		// Find total number of items.
252
		$count_query = $count_query . $query_cond;
253
		$total_items = $wpdb->get_var( $count_query );
254
255
		// Adjust the query to take pagination into account.
256
		if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
257
			$offset = ( $current_page_no - 1 ) * $per_page;
258
			$query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
259
		}
260
261
		// Fetch the items.
262
		$query = $query . $query_cond;
263
		$items = $wpdb->get_results( $query );
264
265
		return array( $items, $total_items );
266
	}
267
268
	/**
269
	 * Create email log table.
270
	 *
271
	 * @access private
272
	 *
273
	 * @global object $wpdb
274
	 */
275
	private function create_table_if_needed() {
276
		global $wpdb;
277
278
		$table_name = $this->get_log_table_name();
279
280
		if ( $wpdb->get_var( "show tables like '{$table_name}'" ) != $table_name ) {
281
282
			$sql = $this->get_create_table_query();
283
284
			require_once ABSPATH . 'wp-admin/includes/upgrade.php';
285
			dbDelta( $sql );
286
287
			add_option( self::DB_OPTION_NAME, self::DB_VERSION );
288
		}
289
	}
290
291
	/**
292
	 * Get the total number of email logs.
293
	 *
294
	 * @return int Total email log count
295
	 */
296
	public function get_logs_count() {
297
		global $wpdb;
298
299
		$query = 'SELECT count(*) FROM ' . $this->get_log_table_name();
300
301
		return $wpdb->get_var( $query );
302
	}
303
304
	/**
305
	 * Fetches the log item by the item data.
306
	 *
307
	 * Use this method to get the log item when the error instance only returns the log item data.
308
	 *
309
	 * @param array $data Array of Email information. {
310
	 *
311
	 * @type array|string to
312
	 * @type string       subject
313
	 * @type string       message
314
	 * @type array|string headers
315
	 * @type array|string attachments
316
	 *                    }
317
	 *
318
	 * @return int
319
	 */
320
	public function fetch_log_item_by_item_data( $data ) {
321
		if ( empty( $data ) || ! is_array( $data ) ) {
322
			return 0;
323
		}
324
325
		global $wpdb;
326
		$table_name = $this->get_log_table_name();
327
328
		$query      = "SELECT ID FROM {$table_name}";
329
		$query_cond = '';
330
		$where      = array();
331
332
		// Execute the following `if` conditions only when $data is array.
333
		if ( array_key_exists( 'to', $data ) ) {
334
			// Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
335
			$to_email = 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

335
			$to_email = /** @scrutinizer ignore-call */ Util\join_array_elements_with_delimiter( $data['to'] );
Loading history...
336
337
			$to_email = trim( esc_sql( $to_email ) );
0 ignored issues
show
Bug introduced by
It seems like esc_sql($to_email) 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

337
			$to_email = trim( /** @scrutinizer ignore-type */ esc_sql( $to_email ) );
Loading history...
338
			$where[]  = "to_email = '$to_email'";
339
		}
340
341
		if ( array_key_exists( 'subject', $data ) ) {
342
			$subject = trim( esc_sql( $data['subject'] ) );
343
			$where[] = "subject = '$subject'";
344
		}
345
346
		if ( array_key_exists( 'attachments', $data ) ) {
347
			if ( is_array( $data['attachments'] ) ) {
348
				$attachments = count( $data['attachments'] ) > 0 ? 'true' : 'false';
349
			} else {
350
				$attachments = empty( $data['attachments'] ) ? 'false' : 'true';
351
			}
352
			$attachments = trim( esc_sql( $attachments ) );
353
			$where[]     = "attachments = '$attachments'";
354
		}
355
356
		foreach ( $where as $index => $value ) {
357
			$query_cond .= 0 === $index ? ' WHERE ' : ' AND ';
358
			$query_cond .= $value;
359
		}
360
361
		// Get only the latest logged item when multiple rows match.
362
		$query_cond .= ' ORDER BY id DESC LIMIT 1';
363
364
		$query = $query . $query_cond;
365
366
		return absint( $wpdb->get_var( $query ) );
367
	}
368
369
	/**
370
	 * Sets email sent status as failed for the given log item.
371
	 *
372
	 * @since 2.3.0
373
	 *
374
	 * @param int $log_item_id ID of the log item whose email sent status should be set to failed.
375
	 */
376
	public function set_log_item_fail_status_by_id( $log_item_id ) {
377
		global $wpdb;
378
		$table_name = $this->get_log_table_name();
379
380
		$wpdb->update(
381
			$table_name,
382
			array( 'result' => '0' ),
383
			array( 'ID'     => $log_item_id ),
384
			array( '%d' ),
385
			array( '%d' )
386
		);
387
	}
388
389
	/**
390
	 * Updates the DB schema.
391
	 *
392
	 * Adds new columns to the Database as of v0.2.
393
	 *
394
	 * @since 2.3.0
395
	 */
396
	private function update_table_if_needed() {
397
		$existing_db_version = get_option( self::DB_OPTION_NAME, false );
398
		$updated_db_version  = self::DB_VERSION;
399
400
		// Bail out when the DB version is `0.1` or equals to self::DB_VERSION
401
		if ( ! $existing_db_version || $existing_db_version !== '0.1' || $existing_db_version === $updated_db_version ) {
402
			return;
403
		}
404
405
		$sql = $this->get_create_table_query();
406
407
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
408
		dbDelta( $sql );
409
410
		update_option( self::DB_OPTION_NAME, self::DB_VERSION );
411
	}
412
413
	/**
414
	 * Gets the Create Table query.
415
	 *
416
	 * @since 2.3.0
417
	 *
418
	 * @return string
419
	 */
420
	private function get_create_table_query() {
421
		global $wpdb;
422
		$table_name      = $this->get_log_table_name();
423
		$charset_collate = $wpdb->get_charset_collate();
424
425
		$sql = 'CREATE TABLE ' . $table_name . ' (
426
				id mediumint(9) NOT NULL AUTO_INCREMENT,
427
				to_email VARCHAR(500) NOT NULL,
428
				subject VARCHAR(500) NOT NULL,
429
				message TEXT NOT NULL,
430
				headers TEXT NOT NULL,
431
				attachments TEXT NOT NULL,
432
				sent_date timestamp NOT NULL,
433
				attachment_name VARCHAR(1000),
434
				ip_address VARCHAR(15),
435
				result TINYINT(1),
436
				PRIMARY KEY  (id)
437
			) ' . $charset_collate . ';';
438
439
		return $sql;
440
	}
441
}
442