Passed
Pull Request — dev/2.3.0 (#167)
by Maria Daniel Deepak
04:37 queued 02:16
created

TableManager::get_log_table_name()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

218
			$search_term = trim( /** @scrutinizer ignore-type */ esc_sql( $request['s'] ) );
Loading history...
219
			$query_cond .= " WHERE ( to_email LIKE '%$search_term%' OR subject LIKE '%$search_term%' ) ";
220
		}
221
222
		if ( isset( $request['d'] ) && $request['d'] !== '' ) {
223
			$search_date = trim( esc_sql( $request['d'] ) );
224
			if ( '' === $query_cond ) {
225
				$query_cond .= " WHERE sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
226
			} else {
227
				$query_cond .= " AND sent_date BETWEEN '$search_date 00:00:00' AND '$search_date 23:59:59' ";
228
			}
229
		}
230
231
		// Ordering parameters.
232
		$orderby = ! empty( $request['orderby'] ) ? esc_sql( $request['orderby'] ) : 'sent_date';
233
		$order   = ! empty( $request['order'] ) ? esc_sql( $request['order'] ) : 'DESC';
234
235
		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...
236
			$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

236
			$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

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

323
			$data['to'] = /** @scrutinizer ignore-call */ Util\join_array_elements_with_delimiter( $data['to'] );
Loading history...
324
325
			$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

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