Completed
Pull Request — dev/2.3.0 (#163)
by Maria Daniel Deepak
11:06 queued 05:31
created

TableManager   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Test Coverage

Coverage 3.7%

Importance

Changes 0
Metric Value
wmc 44
eloc 130
dl 0
loc 386
ccs 6
cts 162
cp 0.037
rs 8.8798
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A insert_log() 0 5 1
A update_table_if_needed() 0 15 4
A get_logs_count() 0 6 1
A delete_table_from_deleted_blog() 0 4 1
A get_create_table_query() 0 20 1
A create_table_for_new_blog() 0 5 2
A fetch_log_items_by_id() 0 16 2
A delete_logs_older_than() 0 8 1
B fetch_log_items() 0 45 11
B fetch_log_item_by_item_data() 0 40 9
A load() 0 7 1
A get_log_table_name() 0 4 1
A create_table_if_needed() 0 13 2
A set_log_item_fail_status_by_id() 0 14 1
A delete_all_logs() 0 6 1
A delete_logs() 0 9 1
A on_activate() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like TableManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TableManager, and based on these observations, apply Extract Interface, too.

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
	 *
162
	 * @return array Log item(s).
163
	 */
164
	public function fetch_log_items_by_id( $ids = array() ) {
165
		global $wpdb;
166
		$table_name = $this->get_log_table_name();
167
168
		$query = "SELECT * FROM {$table_name}";
169
170
		if ( ! empty( $ids ) ) {
171
			$ids = array_map( 'absint', $ids );
172
173
			// Can't use wpdb->prepare for the below query. If used it results in this bug https://github.com/sudar/email-log/issues/13.
174
			$ids_list = esc_sql( implode( ',', $ids ) );
175
176
			$query .= " where id IN ( {$ids_list} )";
177
		}
178
179
		return $wpdb->get_results( $query, 'ARRAY_A' ); //@codingStandardsIgnoreLine
180
	}
181
182
	/**
183
	 * Fetch log items.
184
	 *
185
	 * @param array $request         Request object.
186
	 * @param int   $per_page        Entries per page.
187
	 * @param int   $current_page_no Current page no.
188
	 *
189
	 * @return array Log entries and total items count.
190
	 */
191
	public function fetch_log_items( $request, $per_page, $current_page_no ) {
192
		global $wpdb;
193
		$table_name = $this->get_log_table_name();
194
195
		$query       = 'SELECT * FROM ' . $table_name;
196
		$count_query = 'SELECT count(*) FROM ' . $table_name;
197
		$query_cond  = '';
198
199
		if ( isset( $request['s'] ) && $request['s'] !== '' ) {
200
			$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

200
			$search_term = trim( /** @scrutinizer ignore-type */ esc_sql( $request['s'] ) );
Loading history...
201
			$query_cond .= " WHERE ( to_email LIKE '%$search_term%' OR subject LIKE '%$search_term%' ) ";
202
		}
203
204
		if ( isset( $request['d'] ) && $request['d'] !== '' ) {
205
			$search_date = trim( esc_sql( $request['d'] ) );
206
			if ( '' === $query_cond ) {
207
				$query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
208
			} else {
209
				$query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
210
			}
211
		}
212
213
		// Ordering parameters.
214
		$orderby = ! empty( $request['orderby'] ) ? esc_sql( $request['orderby'] ) : 'sent_date';
215
		$order   = ! empty( $request['order'] ) ? esc_sql( $request['order'] ) : 'DESC';
216
217
		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...
218
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . $order;
0 ignored issues
show
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

218
			$query_cond .= ' ORDER BY ' . /** @scrutinizer ignore-type */ $orderby . ' ' . $order;
Loading history...
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

218
			$query_cond .= ' ORDER BY ' . $orderby . ' ' . /** @scrutinizer ignore-type */ $order;
Loading history...
219
		}
220
221
		// Find total number of items.
222
		$count_query = $count_query . $query_cond;
223
		$total_items = $wpdb->get_var( $count_query );
224
225
		// Adjust the query to take pagination into account.
226
		if ( ! empty( $current_page_no ) && ! empty( $per_page ) ) {
227
			$offset = ( $current_page_no - 1 ) * $per_page;
228
			$query_cond .= ' LIMIT ' . (int) $offset . ',' . (int) $per_page;
229
		}
230
231
		// Fetch the items.
232
		$query = $query . $query_cond;
233
		$items = $wpdb->get_results( $query );
234
235
		return array( $items, $total_items );
236
	}
237
238
	/**
239
	 * Create email log table.
240
	 *
241
	 * @access private
242
	 *
243
	 * @global object $wpdb
244
	 */
245
	private function create_table_if_needed() {
246
		global $wpdb;
247
248
		$table_name = $this->get_log_table_name();
249
250
		if ( $wpdb->get_var( "show tables like '{$table_name}'" ) != $table_name ) {
251
252
			$sql = $this->get_create_table_query();
253
254
			require_once ABSPATH . 'wp-admin/includes/upgrade.php';
255
			dbDelta( $sql );
256
257
			add_option( self::DB_OPTION_NAME, self::DB_VERSION );
258
		}
259
	}
260
261
	/**
262
	 * Get the total number of email logs.
263
	 *
264
	 * @return int Total email log count
265
	 */
266
	public function get_logs_count() {
267
		global $wpdb;
268
269
		$query = 'SELECT count(*) FROM ' . $this->get_log_table_name();
270
271
		return $wpdb->get_var( $query );
272
	}
273
274
	/**
275
	 * @param array $data {
276
	 *      @type string|array to
277
	 *      @type string       subject
278
	 *      @type string       message
279
	 *      @type string|array headers
280
	 *      @type string|array attachments
281
	 * }
282
	 *
283
	 * @return int
284
	 */
285
	public function fetch_log_item_by_item_data( $data ) {
286
		global $wpdb;
287
		$table_name = $this->get_log_table_name();
288
289
		// Since the value is stored as CSV in DB, convert the values from error data to CSV to compare.
290
		$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

290
		$data['to']      = /** @scrutinizer ignore-call */ Util\join_array_elements_with_delimiter( $data['to'], ',' );
Loading history...
291
		$data['headers'] = Util\join_array_elements_with_delimiter( $data['headers'], "\n" );
292
293
		$query = "SELECT ID FROM {$table_name}";
294
		$query_cond  = '';
295
296
		if ( empty( $data ) || ! is_array( $data ) ) {
297
			$query_cond .= " WHERE id = 0";
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal WHERE id = 0 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...
298
		}
299
300
		if ( array_key_exists( 'to', $data ) ) {
301
			$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

301
			$to_email   = trim( /** @scrutinizer ignore-type */ esc_sql( $data['to'] ) );
Loading history...
302
			$query_cond .= " WHERE to_email = '$to_email'";
303
		}
304
305
		if ( array_key_exists( 'subject', $data ) ) {
306
			$subject    = trim( esc_sql( $data['subject'] ) );
307
			$query_cond .= " AND subject = '$subject'";
308
		}
309
310
		if ( array_key_exists( 'attachments', $data ) ) {
311
			if ( is_array( $data['attachments'] ) ) {
312
				$attachments = count( $data['attachments'] ) > 0 ? 'true' : 'false';
313
			} else {
314
				$attachments = empty( $data['attachments'] ) ? 'false' : 'true';
315
			}
316
			$attachments = trim( esc_sql( $attachments ) );
317
			$query_cond  .= " AND attachments = '$attachments'";
318
		}
319
320
		// Get only the latest logged item when multiple rows match.
321
		$query_cond .= ' ORDER BY id DESC LIMIT 1';
322
323
		$query = $query . $query_cond;
324
		return absint( $wpdb->get_var( $query ) );
325
	}
326
327
	/**
328
	 * Sets email sent status as failed for the given log item.
329
	 *
330
	 * @since 2.3.0
331
	 *
332
	 * @param int $id ID of the log item whose email sent status should be set to failed.
333
	 */
334
	public function set_log_item_fail_status_by_id( $id ) {
335
		global $wpdb;
336
		$table_name = $this->get_log_table_name();
337
338
		$wpdb->update(
339
			$table_name,
340
			array(
341
				'result' => '0',
342
			),
343
			array( 'ID' => $id ),
344
			array(
345
				'%d' // VALUE format
346
			),
347
			array( '%d' ) // WHERE format
348
		);
349
	}
350
351
	/**
352
	 * Updates the DB schema.
353
	 *
354
	 * Adds new columns to the Database as of v0.2.
355
	 *
356
	 * @since 2.3.0
357
	 */
358
	private function update_table_if_needed() {
359
		$existing_db_version = get_option( self::DB_OPTION_NAME, false );
360
		$updated_db_version  = self::DB_VERSION;
361
362
		// Bail out when the DB version is `0.1` or equals to self::DB_VERSION
363
		if ( ! $existing_db_version || $existing_db_version !== '0.1' || $existing_db_version === $updated_db_version ) {
364
			return;
365
		}
366
367
		$sql = $this->get_create_table_query();
368
369
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
370
		dbDelta( $sql );
371
372
		update_option( self::DB_OPTION_NAME, self::DB_VERSION );
373
	}
374
375
	/**
376
	 * Gets the Create Table query.
377
	 *
378
	 * @since 2.3.0
379
	 *
380
	 * @return string
381
	 */
382
	private function get_create_table_query() {
383
		global $wpdb;
384
		$table_name      = $this->get_log_table_name();
385
		$charset_collate = $wpdb->get_charset_collate();
386
387
		$sql = 'CREATE TABLE ' . $table_name . ' (
388
				id mediumint(9) NOT NULL AUTO_INCREMENT,
389
				to_email VARCHAR(250) NOT NULL,
390
				subject VARCHAR(250) NOT NULL,
391
				message TEXT NOT NULL,
392
				headers TEXT NOT NULL,
393
				attachments TEXT NOT NULL,
394
				sent_date timestamp NOT NULL,
395
				attachment_name VARCHAR(1000),
396
				ip_address VARCHAR(15),
397
				result TINYINT(1)
398
				PRIMARY KEY  (id)
399
			) ' . $charset_collate . ' ;';
400
401
		return $sql;
402
	}
403
}
404