Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/date.php (15 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Class for generating SQL clauses that filter a primary query according to date.
4
 *
5
 * WP_Date_Query is a helper that allows primary query classes, such as WP_Query, to filter
6
 * their results by date columns, by generating `WHERE` subclauses to be attached to the
7
 * primary SQL query string.
8
 *
9
 * Attempting to filter by an invalid date value (eg month=13) will generate SQL that will
10
 * return no results. In these cases, a _doing_it_wrong() error notice is also thrown.
11
 * See WP_Date_Query::validate_date_values().
12
 *
13
 * @link https://codex.wordpress.org/Function_Reference/WP_Query Codex page.
14
 *
15
 * @since 3.7.0
16
 */
17
class WP_Date_Query {
18
	/**
19
	 * Array of date queries.
20
	 *
21
	 * See WP_Date_Query::__construct() for information on date query arguments.
22
	 *
23
	 * @since 3.7.0
24
	 * @access public
25
	 * @var array
26
	 */
27
	public $queries = array();
28
29
	/**
30
	 * The default relation between top-level queries. Can be either 'AND' or 'OR'.
31
	 *
32
	 * @since 3.7.0
33
	 * @access public
34
	 * @var string
35
	 */
36
	public $relation = 'AND';
37
38
	/**
39
	 * The column to query against. Can be changed via the query arguments.
40
	 *
41
	 * @since 3.7.0
42
	 * @access public
43
	 * @var string
44
	 */
45
	public $column = 'post_date';
46
47
	/**
48
	 * The value comparison operator. Can be changed via the query arguments.
49
	 *
50
	 * @since 3.7.0
51
	 * @access public
52
	 * @var array
53
	 */
54
	public $compare = '=';
55
56
	/**
57
	 * Supported time-related parameter keys.
58
	 *
59
	 * @since 4.1.0
60
	 * @access public
61
	 * @var array
62
	 */
63
	public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second' );
64
65
	/**
66
	 * @since 4.7.0
67
	 * @access protected
68
	 * @var wpdb
69
	 */
70
	protected $db;
71
72
	/**
73
	 * Constructor.
74
	 *
75
	 * Time-related parameters that normally require integer values ('year', 'month', 'week', 'dayofyear', 'day',
76
	 * 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second') accept arrays of integers for some values of
77
	 * 'compare'. When 'compare' is 'IN' or 'NOT IN', arrays are accepted; when 'compare' is 'BETWEEN' or 'NOT
78
	 * BETWEEN', arrays of two valid values are required. See individual argument descriptions for accepted values.
79
	 *
80
	 * @since 3.7.0
81
	 * @since 4.0.0 The $inclusive logic was updated to include all times within the date range.
82
	 * @since 4.1.0 Introduced 'dayofweek_iso' time type parameter.
83
	 * @access public
84
	 *
85
	 * @param array $date_query {
86
	 *     Array of date query clauses.
87
	 *
88
	 *     @type array {
89
	 *         @type string $column   Optional. The column to query against. If undefined, inherits the value of
90
	 *                                the `$default_column` parameter. Accepts 'post_date', 'post_date_gmt',
91
	 *                                'post_modified','post_modified_gmt', 'comment_date', 'comment_date_gmt'.
92
	 *                                Default 'post_date'.
93
	 *         @type string $compare  Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=',
94
	 *                                'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='.
95
	 *         @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'.
96
	 *                                Default 'OR'.
97
	 *         @type array {
98
	 *             Optional. An array of first-order clause parameters, or another fully-formed date query.
99
	 *
100
	 *             @type string|array $before {
101
	 *                 Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string,
102
	 *                 or array of 'year', 'month', 'day' values.
103
	 *
104
	 *                 @type string $year  The four-digit year. Default empty. Accepts any four-digit year.
105
	 *                 @type string $month Optional when passing array.The month of the year.
106
	 *                                     Default (string:empty)|(array:1). Accepts numbers 1-12.
107
	 *                 @type string $day   Optional when passing array.The day of the month.
108
	 *                                     Default (string:empty)|(array:1). Accepts numbers 1-31.
109
	 *             }
110
	 *             @type string|array $after {
111
	 *                 Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string,
112
	 *                 or array of 'year', 'month', 'day' values.
113
	 *
114
	 *                 @type string $year  The four-digit year. Accepts any four-digit year. Default empty.
115
	 *                 @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12.
116
	 *                                     Default (string:empty)|(array:12).
117
	 *                 @type string $day   Optional when passing array.The day of the month. Accepts numbers 1-31.
118
	 *                                     Default (string:empty)|(array:last day of month).
119
	 *             }
120
	 *             @type string       $column        Optional. Used to add a clause comparing a column other than the
121
	 *                                               column specified in the top-level `$column` parameter. Accepts
122
	 *                                               'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt',
123
	 *                                               'comment_date', 'comment_date_gmt'. Default is the value of
124
	 *                                               top-level `$column`.
125
	 *             @type string       $compare       Optional. The comparison operator. Accepts '=', '!=', '>', '>=',
126
	 *                                               '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. 'IN',
127
	 *                                               'NOT IN', 'BETWEEN', and 'NOT BETWEEN'. Comparisons support
128
	 *                                               arrays in some time-related parameters. Default '='.
129
	 *             @type bool         $inclusive     Optional. Include results from dates specified in 'before' or
130
	 *                                               'after'. Default false.
131
	 *             @type int|array    $year          Optional. The four-digit year number. Accepts any four-digit year
132
	 *                                               or an array of years if `$compare` supports it. Default empty.
133
	 *             @type int|array    $month         Optional. The two-digit month number. Accepts numbers 1-12 or an
134
	 *                                               array of valid numbers if `$compare` supports it. Default empty.
135
	 *             @type int|array    $week          Optional. The week number of the year. Accepts numbers 0-53 or an
136
	 *                                               array of valid numbers if `$compare` supports it. Default empty.
137
	 *             @type int|array    $dayofyear     Optional. The day number of the year. Accepts numbers 1-366 or an
138
	 *                                               array of valid numbers if `$compare` supports it.
139
	 *             @type int|array    $day           Optional. The day of the month. Accepts numbers 1-31 or an array
140
	 *                                               of valid numbers if `$compare` supports it. Default empty.
141
	 *             @type int|array    $dayofweek     Optional. The day number of the week. Accepts numbers 1-7 (1 is
142
	 *                                               Sunday) or an array of valid numbers if `$compare` supports it.
143
	 *                                               Default empty.
144
	 *             @type int|array    $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7
145
	 *                                               (1 is Monday) or an array of valid numbers if `$compare` supports it.
146
	 *                                               Default empty.
147
	 *             @type int|array    $hour          Optional. The hour of the day. Accepts numbers 0-23 or an array
148
	 *                                               of valid numbers if `$compare` supports it. Default empty.
149
	 *             @type int|array    $minute        Optional. The minute of the hour. Accepts numbers 0-60 or an array
150
	 *                                               of valid numbers if `$compare` supports it. Default empty.
151
	 *             @type int|array    $second        Optional. The second of the minute. Accepts numbers 0-60 or an
152
	 *                                               array of valid numbers if `$compare` supports it. Default empty.
153
	 *         }
154
	 *     }
155
	 * }
156
	 * @param array $default_column Optional. Default column to query against. Default 'post_date'.
157
	 *                              Accepts 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt',
158
	 *                              'comment_date', 'comment_date_gmt'.
159
	 */
160
	public function __construct( $date_query, $default_column = 'post_date' ) {
161
		$this->db = $GLOBALS['wpdb'];
162
163 View Code Duplication
		if ( isset( $date_query['relation'] ) && 'OR' === strtoupper( $date_query['relation'] ) ) {
164
			$this->relation = 'OR';
165
		} else {
166
			$this->relation = 'AND';
167
		}
168
169
		if ( ! is_array( $date_query ) ) {
170
			return;
171
		}
172
173
		// Support for passing time-based keys in the top level of the $date_query array.
174
		if ( ! isset( $date_query[0] ) && ! empty( $date_query ) ) {
175
			$date_query = array( $date_query );
176
		}
177
178
		if ( empty( $date_query ) ) {
179
			return;
180
		}
181
182
		if ( ! empty( $date_query['column'] ) ) {
183
			$date_query['column'] = esc_sql( $date_query['column'] );
184
		} else {
185
			$date_query['column'] = esc_sql( $default_column );
186
		}
187
188
		$this->column = $this->validate_column( $this->column );
189
190
		$this->compare = $this->get_compare( $date_query );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->get_compare($date_query) can also be of type string. However, the property $compare is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
191
192
		$this->queries = $this->sanitize_query( $date_query );
193
	}
194
195
	/**
196
	 * Recursive-friendly query sanitizer.
197
	 *
198
	 * Ensures that each query-level clause has a 'relation' key, and that
199
	 * each first-order clause contains all the necessary keys from
200
	 * `$defaults`.
201
	 *
202
	 * @since 4.1.0
203
	 * @access public
204
	 *
205
	 * @param array $queries
206
	 * @param array $parent_query
207
	 *
208
	 * @return array Sanitized queries.
209
	 */
210
	public function sanitize_query( $queries, $parent_query = null ) {
211
		$cleaned_query = array();
212
213
		$defaults = array(
214
			'column'   => 'post_date',
215
			'compare'  => '=',
216
			'relation' => 'AND',
217
		);
218
219
		// Numeric keys should always have array values.
220
		foreach ( $queries as $qkey => $qvalue ) {
221
			if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) {
222
				unset( $queries[ $qkey ] );
223
			}
224
		}
225
226
		// Each query should have a value for each default key. Inherit from the parent when possible.
227
		foreach ( $defaults as $dkey => $dvalue ) {
228
			if ( isset( $queries[ $dkey ] ) ) {
229
				continue;
230
			}
231
232
			if ( isset( $parent_query[ $dkey ] ) ) {
233
				$queries[ $dkey ] = $parent_query[ $dkey ];
234
			} else {
235
				$queries[ $dkey ] = $dvalue;
236
			}
237
		}
238
239
		// Validate the dates passed in the query.
240
		if ( $this->is_first_order_clause( $queries ) ) {
241
			$this->validate_date_values( $queries );
242
		}
243
244
		foreach ( $queries as $key => $q ) {
245
			if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) {
246
				// This is a first-order query. Trust the values and sanitize when building SQL.
247
				$cleaned_query[ $key ] = $q;
248
			} else {
249
				// Any array without a time key is another query, so we recurse.
250
				$cleaned_query[] = $this->sanitize_query( $q, $queries );
251
			}
252
		}
253
254
		return $cleaned_query;
255
	}
256
257
	/**
258
	 * Determine whether this is a first-order clause.
259
	 *
260
	 * Checks to see if the current clause has any time-related keys.
261
	 * If so, it's first-order.
262
	 *
263
	 * @param  array $query Query clause.
264
	 * @return bool True if this is a first-order clause.
265
	 */
266
	protected function is_first_order_clause( $query ) {
267
		$time_keys = array_intersect( $this->time_keys, array_keys( $query ) );
268
		return ! empty( $time_keys );
269
	}
270
271
	/**
272
	 * Determines and validates what comparison operator to use.
273
	 *
274
	 * @since 3.7.0
275
	 * @access public
276
	 *
277
	 * @param array $query A date query or a date subquery.
278
	 * @return string The comparison operator.
279
	 */
280
	public function get_compare( $query ) {
281
		if ( ! empty( $query['compare'] ) && in_array( $query['compare'], array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) )
282
			return strtoupper( $query['compare'] );
283
284
		return $this->compare;
285
	}
286
287
	/**
288
	 * Validates the given date_query values and triggers errors if something is not valid.
289
	 *
290
	 * Note that date queries with invalid date ranges are allowed to
291
	 * continue (though of course no items will be found for impossible dates).
292
	 * This method only generates debug notices for these cases.
293
	 *
294
	 * @since  4.1.0
295
	 * @access public
296
	 *
297
	 * @param  array $date_query The date_query array.
298
	 * @return bool  True if all values in the query are valid, false if one or more fail.
299
	 */
300
	public function validate_date_values( $date_query = array() ) {
301
		if ( empty( $date_query ) ) {
302
			return false;
303
		}
304
305
		$valid = true;
306
307
		/*
308
		 * Validate 'before' and 'after' up front, then let the
309
		 * validation routine continue to be sure that all invalid
310
		 * values generate errors too.
311
		 */
312 View Code Duplication
		if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ){
313
			$valid = $this->validate_date_values( $date_query['before'] );
314
		}
315
316 View Code Duplication
		if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ){
317
			$valid = $this->validate_date_values( $date_query['after'] );
318
		}
319
320
		// Array containing all min-max checks.
321
		$min_max_checks = array();
322
323
		// Days per year.
324
		if ( array_key_exists( 'year', $date_query ) ) {
325
			/*
326
			 * If a year exists in the date query, we can use it to get the days.
327
			 * If multiple years are provided (as in a BETWEEN), use the first one.
328
			 */
329
			if ( is_array( $date_query['year'] ) ) {
330
				$_year = reset( $date_query['year'] );
331
			} else {
332
				$_year = $date_query['year'];
333
			}
334
335
			$max_days_of_year = date( 'z', mktime( 0, 0, 0, 12, 31, $_year ) ) + 1;
336
		} else {
337
			// otherwise we use the max of 366 (leap-year)
338
			$max_days_of_year = 366;
339
		}
340
341
		$min_max_checks['dayofyear'] = array(
342
			'min' => 1,
343
			'max' => $max_days_of_year
344
		);
345
346
		// Days per week.
347
		$min_max_checks['dayofweek'] = array(
348
			'min' => 1,
349
			'max' => 7
350
		);
351
352
		// Days per week.
353
		$min_max_checks['dayofweek_iso'] = array(
354
			'min' => 1,
355
			'max' => 7
356
		);
357
358
		// Months per year.
359
		$min_max_checks['month'] = array(
360
			'min' => 1,
361
			'max' => 12
362
		);
363
364
		// Weeks per year.
365
		if ( isset( $_year ) ) {
366
			/*
367
			 * If we have a specific year, use it to calculate number of weeks.
368
			 * Note: the number of weeks in a year is the date in which Dec 28 appears.
369
			 */
370
			$week_count = date( 'W', mktime( 0, 0, 0, 12, 28, $_year ) );
371
372
		} else {
373
			// Otherwise set the week-count to a maximum of 53.
374
			$week_count = 53;
375
		}
376
377
		$min_max_checks['week'] = array(
378
			'min' => 1,
379
			'max' => $week_count
380
		);
381
382
		// Days per month.
383
		$min_max_checks['day'] = array(
384
			'min' => 1,
385
			'max' => 31
386
		);
387
388
		// Hours per day.
389
		$min_max_checks['hour'] = array(
390
			'min' => 0,
391
			'max' => 23
392
		);
393
394
		// Minutes per hour.
395
		$min_max_checks['minute'] = array(
396
			'min' => 0,
397
			'max' => 59
398
		);
399
400
		// Seconds per minute.
401
		$min_max_checks['second'] = array(
402
			'min' => 0,
403
			'max' => 59
404
		);
405
406
		// Concatenate and throw a notice for each invalid value.
407
		foreach ( $min_max_checks as $key => $check ) {
408
			if ( ! array_key_exists( $key, $date_query ) ) {
409
				continue;
410
			}
411
412
			// Throw a notice for each failing value.
413
			foreach ( (array) $date_query[ $key ] as $_value ) {
414
				$is_between = $_value >= $check['min'] && $_value <= $check['max'];
415
416
				if ( ! is_numeric( $_value ) || ! $is_between ) {
417
					$error = sprintf(
418
						/* translators: Date query invalid date message: 1: invalid value, 2: type of value, 3: minimum valid value, 4: maximum valid value */
419
						__( 'Invalid value %1$s for %2$s. Expected value should be between %3$s and %4$s.' ),
420
						'<code>' . esc_html( $_value ) . '</code>',
421
						'<code>' . esc_html( $key ) . '</code>',
422
						'<code>' . esc_html( $check['min'] ) . '</code>',
423
						'<code>' . esc_html( $check['max'] ) . '</code>'
424
					);
425
426
					_doing_it_wrong( __CLASS__, $error, '4.1.0' );
427
428
					$valid = false;
429
				}
430
			}
431
		}
432
433
		// If we already have invalid date messages, don't bother running through checkdate().
434
		if ( ! $valid ) {
435
			return $valid;
436
		}
437
438
		$day_month_year_error_msg = '';
439
440
		$day_exists   = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] );
441
		$month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] );
442
		$year_exists  = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] );
443
444
		if ( $day_exists && $month_exists && $year_exists ) {
445
			// 1. Checking day, month, year combination.
446
			if ( ! wp_checkdate( $date_query['month'], $date_query['day'], $date_query['year'], sprintf( '%s-%s-%s', $date_query['year'], $date_query['month'], $date_query['day'] ) ) ) {
447
				/* translators: 1: year, 2: month, 3: day of month */
448
				$day_month_year_error_msg = sprintf(
449
					__( 'The following values do not describe a valid date: year %1$s, month %2$s, day %3$s.' ),
450
					'<code>' . esc_html( $date_query['year'] ) . '</code>',
451
					'<code>' . esc_html( $date_query['month'] ) . '</code>',
452
					'<code>' . esc_html( $date_query['day'] ) . '</code>'
453
				);
454
455
				$valid = false;
456
			}
457
458
		} elseif ( $day_exists && $month_exists ) {
459
			/*
460
			 * 2. checking day, month combination
461
			 * We use 2012 because, as a leap year, it's the most permissive.
462
			 */
463
			if ( ! wp_checkdate( $date_query['month'], $date_query['day'], 2012, sprintf( '2012-%s-%s', $date_query['month'], $date_query['day'] ) ) ) {
464
				/* translators: 1: month, 2: day of month */
465
				$day_month_year_error_msg = sprintf(
466
					__( 'The following values do not describe a valid date: month %1$s, day %2$s.' ),
467
					'<code>' . esc_html( $date_query['month'] ) . '</code>',
468
					'<code>' . esc_html( $date_query['day'] ) . '</code>'
469
				);
470
471
				$valid = false;
472
			}
473
		}
474
475
		if ( ! empty( $day_month_year_error_msg ) ) {
476
			_doing_it_wrong( __CLASS__, $day_month_year_error_msg, '4.1.0' );
477
		}
478
479
		return $valid;
480
	}
481
482
	/**
483
	 * Validates a column name parameter.
484
	 *
485
	 * Column names without a table prefix (like 'post_date') are checked against a whitelist of
486
	 * known tables, and then, if found, have a table prefix (such as 'wp_posts.') prepended.
487
	 * Prefixed column names (such as 'wp_posts.post_date') bypass this whitelist check,
488
	 * and are only sanitized to remove illegal characters.
489
	 *
490
	 * @since 3.7.0
491
	 * @access public
492
	 *
493
	 * @param string $column The user-supplied column name.
494
	 * @return string A validated column name value.
495
	 */
496
	public function validate_column( $column ) {
497
		$valid_columns = array(
498
			'post_date', 'post_date_gmt', 'post_modified',
499
			'post_modified_gmt', 'comment_date', 'comment_date_gmt',
500
			'user_registered', 'registered', 'last_updated',
501
		);
502
503
		// Attempt to detect a table prefix.
504
		if ( false === strpos( $column, '.' ) ) {
505
			/**
506
			 * Filters the list of valid date query columns.
507
			 *
508
			 * @since 3.7.0
509
			 * @since 4.1.0 Added 'user_registered' to the default recognized columns.
510
			 *
511
			 * @param array $valid_columns An array of valid date query columns. Defaults
512
			 *                             are 'post_date', 'post_date_gmt', 'post_modified',
513
			 *                             'post_modified_gmt', 'comment_date', 'comment_date_gmt',
514
			 *	                           'user_registered'
515
			 */
516
			if ( ! in_array( $column, apply_filters( 'date_query_valid_columns', $valid_columns ) ) ) {
517
				$column = 'post_date';
518
			}
519
520
			$known_columns = array(
521
				$this->db->posts => array(
522
					'post_date',
523
					'post_date_gmt',
524
					'post_modified',
525
					'post_modified_gmt',
526
				),
527
				$this->db->comments => array(
528
					'comment_date',
529
					'comment_date_gmt',
530
				),
531
				$this->db->users => array(
532
					'user_registered',
533
				),
534
				$this->db->blogs => array(
535
					'registered',
536
					'last_updated',
537
				),
538
			);
539
540
			// If it's a known column name, add the appropriate table prefix.
541
			foreach ( $known_columns as $table_name => $table_columns ) {
542
				if ( in_array( $column, $table_columns ) ) {
543
					$column = $table_name . '.' . $column;
544
					break;
545
				}
546
			}
547
548
		}
549
550
		// Remove unsafe characters.
551
		return preg_replace( '/[^a-zA-Z0-9_$\.]/', '', $column );
552
	}
553
554
	/**
555
	 * Generate WHERE clause to be appended to a main query.
556
	 *
557
	 * @since 3.7.0
558
	 * @access public
559
	 *
560
	 * @return string MySQL WHERE clause.
561
	 */
562
	public function get_sql() {
563
		$sql = $this->get_sql_clauses();
564
565
		$where = $sql['where'];
566
567
		/**
568
		 * Filters the date query WHERE clause.
569
		 *
570
		 * @since 3.7.0
571
		 *
572
		 * @param string        $where WHERE clause of the date query.
573
		 * @param WP_Date_Query $this  The WP_Date_Query instance.
574
		 */
575
		return apply_filters( 'get_date_sql', $where, $this );
576
	}
577
578
	/**
579
	 * Generate SQL clauses to be appended to a main query.
580
	 *
581
	 * Called by the public WP_Date_Query::get_sql(), this method is abstracted
582
	 * out to maintain parity with the other Query classes.
583
	 *
584
	 * @since 4.1.0
585
	 * @access protected
586
	 *
587
	 * @return array {
588
	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
589
	 *
590
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
591
	 *     @type string $where SQL fragment to append to the main WHERE clause.
592
	 * }
593
	 */
594 View Code Duplication
	protected function get_sql_clauses() {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
595
		$sql = $this->get_sql_for_query( $this->queries );
596
597
		if ( ! empty( $sql['where'] ) ) {
598
			$sql['where'] = ' AND ' . $sql['where'];
599
		}
600
601
		return $sql;
602
	}
603
604
	/**
605
	 * Generate SQL clauses for a single query array.
606
	 *
607
	 * If nested subqueries are found, this method recurses the tree to
608
	 * produce the properly nested SQL.
609
	 *
610
	 * @since 4.1.0
611
	 * @access protected
612
	 *
613
	 * @param array $query Query to parse.
614
	 * @param int   $depth Optional. Number of tree levels deep we currently are.
615
	 *                     Used to calculate indentation. Default 0.
616
	 * @return array {
617
	 *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
618
	 *
619
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
620
	 *     @type string $where SQL fragment to append to the main WHERE clause.
621
	 * }
622
	 */
623 View Code Duplication
	protected function get_sql_for_query( $query, $depth = 0 ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
624
		$sql_chunks = array(
625
			'join'  => array(),
626
			'where' => array(),
627
		);
628
629
		$sql = array(
630
			'join'  => '',
631
			'where' => '',
632
		);
633
634
		$indent = '';
635
		for ( $i = 0; $i < $depth; $i++ ) {
636
			$indent .= "  ";
637
		}
638
639
		foreach ( $query as $key => $clause ) {
640
			if ( 'relation' === $key ) {
641
				$relation = $query['relation'];
642
			} elseif ( is_array( $clause ) ) {
643
644
				// This is a first-order clause.
645
				if ( $this->is_first_order_clause( $clause ) ) {
646
					$clause_sql = $this->get_sql_for_clause( $clause, $query );
647
648
					$where_count = count( $clause_sql['where'] );
649
					if ( ! $where_count ) {
650
						$sql_chunks['where'][] = '';
651
					} elseif ( 1 === $where_count ) {
652
						$sql_chunks['where'][] = $clause_sql['where'][0];
653
					} else {
654
						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
655
					}
656
657
					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
658
				// This is a subquery, so we recurse.
659
				} else {
660
					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
661
662
					$sql_chunks['where'][] = $clause_sql['where'];
663
					$sql_chunks['join'][]  = $clause_sql['join'];
664
				}
665
			}
666
		}
667
668
		// Filter to remove empties.
669
		$sql_chunks['join']  = array_filter( $sql_chunks['join'] );
670
		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
671
672
		if ( empty( $relation ) ) {
673
			$relation = 'AND';
674
		}
675
676
		// Filter duplicate JOIN clauses and combine into a single string.
677
		if ( ! empty( $sql_chunks['join'] ) ) {
678
			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
679
		}
680
681
		// Generate a single WHERE clause with proper brackets and indentation.
682
		if ( ! empty( $sql_chunks['where'] ) ) {
683
			$sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
684
		}
685
686
		return $sql;
687
	}
688
689
	/**
690
	 * Turns a single date clause into pieces for a WHERE clause.
691
	 *
692
	 * A wrapper for get_sql_for_clause(), included here for backward
693
	 * compatibility while retaining the naming convention across Query classes.
694
	 *
695
	 * @since  3.7.0
696
	 * @access protected
697
	 *
698
	 * @param  array $query Date query arguments.
699
	 * @return array {
700
	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
701
	 *
702
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
703
	 *     @type string $where SQL fragment to append to the main WHERE clause.
704
	 * }
705
	 */
706
	protected function get_sql_for_subquery( $query ) {
707
		return $this->get_sql_for_clause( $query, '' );
0 ignored issues
show
'' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
708
	}
709
710
	/**
711
	 * Turns a first-order date query into SQL for a WHERE clause.
712
	 *
713
	 * @since  4.1.0
714
	 * @access protected
715
	 *
716
	 * @param  array $query        Date query clause.
717
	 * @param  array $parent_query Parent query of the current date query.
718
	 * @return array {
719
	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
720
	 *
721
	 *     @type string $join  SQL fragment to append to the main JOIN clause.
722
	 *     @type string $where SQL fragment to append to the main WHERE clause.
723
	 * }
724
	 */
725
	protected function get_sql_for_clause( $query, $parent_query ) {
726
		// The sub-parts of a $where part.
727
		$where_parts = array();
728
729
		$column = ( ! empty( $query['column'] ) ) ? esc_sql( $query['column'] ) : $this->column;
730
731
		$column = $this->validate_column( $column );
0 ignored issues
show
It seems like $column can also be of type array; however, WP_Date_Query::validate_column() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
732
733
		$compare = $this->get_compare( $query );
734
735
		$inclusive = ! empty( $query['inclusive'] );
736
737
		// Assign greater- and less-than values.
738
		$lt = '<';
739
		$gt = '>';
740
741
		if ( $inclusive ) {
742
			$lt .= '=';
743
			$gt .= '=';
744
		}
745
746
		// Range queries.
747 View Code Duplication
		if ( ! empty( $query['after'] ) ) {
748
			$where_parts[] = $this->db->prepare( "$column $gt %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) );
749
		}
750 View Code Duplication
		if ( ! empty( $query['before'] ) ) {
751
			$where_parts[] = $this->db->prepare( "$column $lt %s", $this->build_mysql_datetime( $query['before'], $inclusive ) );
752
		}
753
		// Specific value queries.
754
755
		if ( isset( $query['year'] ) && $value = $this->build_value( $compare, $query['year'] ) )
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
756
			$where_parts[] = "YEAR( $column ) $compare $value";
757
758
		if ( isset( $query['month'] ) && $value = $this->build_value( $compare, $query['month'] ) ) {
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
759
			$where_parts[] = "MONTH( $column ) $compare $value";
760 View Code Duplication
		} elseif ( isset( $query['monthnum'] ) && $value = $this->build_value( $compare, $query['monthnum'] ) ) {
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
761
			$where_parts[] = "MONTH( $column ) $compare $value";
762
		}
763
		if ( isset( $query['week'] ) && false !== ( $value = $this->build_value( $compare, $query['week'] ) ) ) {
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
764
			$where_parts[] = _wp_mysql_week( $column ) . " $compare $value";
765 View Code Duplication
		} elseif ( isset( $query['w'] ) && false !== ( $value = $this->build_value( $compare, $query['w'] ) ) ) {
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
766
			$where_parts[] = _wp_mysql_week( $column ) . " $compare $value";
767
		}
768
		if ( isset( $query['dayofyear'] ) && $value = $this->build_value( $compare, $query['dayofyear'] ) )
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
769
			$where_parts[] = "DAYOFYEAR( $column ) $compare $value";
770
771
		if ( isset( $query['day'] ) && $value = $this->build_value( $compare, $query['day'] ) )
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
772
			$where_parts[] = "DAYOFMONTH( $column ) $compare $value";
773
774
		if ( isset( $query['dayofweek'] ) && $value = $this->build_value( $compare, $query['dayofweek'] ) )
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
775
			$where_parts[] = "DAYOFWEEK( $column ) $compare $value";
776
777
		if ( isset( $query['dayofweek_iso'] ) && $value = $this->build_value( $compare, $query['dayofweek_iso'] ) )
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_value() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
778
			$where_parts[] = "WEEKDAY( $column ) + 1 $compare $value";
779
780
		if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) {
781
			// Avoid notices.
782
			foreach ( array( 'hour', 'minute', 'second' ) as $unit ) {
783
				if ( ! isset( $query[ $unit ] ) ) {
784
					$query[ $unit ] = null;
785
				}
786
			}
787
788
			if ( $time_query = $this->build_time_query( $column, $compare, $query['hour'], $query['minute'], $query['second'] ) ) {
0 ignored issues
show
It seems like $compare defined by $this->get_compare($query) on line 733 can also be of type array; however, WP_Date_Query::build_time_query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
789
				$where_parts[] = $time_query;
790
			}
791
		}
792
793
		/*
794
		 * Return an array of 'join' and 'where' for compatibility
795
		 * with other query classes.
796
		 */
797
		return array(
798
			'where' => $where_parts,
799
			'join'  => array(),
800
		);
801
	}
802
803
	/**
804
	 * Builds and validates a value string based on the comparison operator.
805
	 *
806
	 * @since 3.7.0
807
	 * @access public
808
	 *
809
	 * @param string $compare The compare operator to use
810
	 * @param string|array $value The value
811
	 * @return string|false|int The value to be used in SQL or false on error.
812
	 */
813
	public function build_value( $compare, $value ) {
814
		if ( ! isset( $value ) )
815
			return false;
816
817
		switch ( $compare ) {
818
			case 'IN':
819
			case 'NOT IN':
820
				$value = (array) $value;
821
822
				// Remove non-numeric values.
823
				$value = array_filter( $value, 'is_numeric' );
824
825
				if ( empty( $value ) ) {
826
					return false;
827
				}
828
829
				return '(' . implode( ',', array_map( 'intval', $value ) ) . ')';
830
831
			case 'BETWEEN':
832
			case 'NOT BETWEEN':
833
				if ( ! is_array( $value ) || 2 != count( $value ) ) {
834
					$value = array( $value, $value );
835
				} else {
836
					$value = array_values( $value );
837
				}
838
839
				// If either value is non-numeric, bail.
840
				foreach ( $value as $v ) {
841
					if ( ! is_numeric( $v ) ) {
842
						return false;
843
					}
844
				}
845
846
				$value = array_map( 'intval', $value );
847
848
				return $value[0] . ' AND ' . $value[1];
849
850
			default;
851
				if ( ! is_numeric( $value ) ) {
852
					return false;
853
				}
854
855
				return (int) $value;
856
		}
857
	}
858
859
	/**
860
	 * Builds a MySQL format date/time based on some query parameters.
861
	 *
862
	 * You can pass an array of values (year, month, etc.) with missing parameter values being defaulted to
863
	 * either the maximum or minimum values (controlled by the $default_to parameter). Alternatively you can
864
	 * pass a string that will be run through strtotime().
865
	 *
866
	 * @since 3.7.0
867
	 * @access public
868
	 *
869
	 * @param string|array $datetime       An array of parameters or a strotime() string
870
	 * @param bool         $default_to_max Whether to round up incomplete dates. Supported by values
871
	 *                                     of $datetime that are arrays, or string values that are a
872
	 *                                     subset of MySQL date format ('Y', 'Y-m', 'Y-m-d', 'Y-m-d H:i').
873
	 *                                     Default: false.
874
	 * @return string|false A MySQL format date/time or false on failure
875
	 */
876
	public function build_mysql_datetime( $datetime, $default_to_max = false ) {
877
		$now = current_time( 'timestamp' );
878
879
		if ( ! is_array( $datetime ) ) {
880
881
			/*
882
			 * Try to parse some common date formats, so we can detect
883
			 * the level of precision and support the 'inclusive' parameter.
884
			 */
885
			if ( preg_match( '/^(\d{4})$/', $datetime, $matches ) ) {
886
				// Y
887
				$datetime = array(
888
					'year' => intval( $matches[1] ),
889
				);
890
891
			} elseif ( preg_match( '/^(\d{4})\-(\d{2})$/', $datetime, $matches ) ) {
892
				// Y-m
893
				$datetime = array(
894
					'year'  => intval( $matches[1] ),
895
					'month' => intval( $matches[2] ),
896
				);
897
898
			} elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2})$/', $datetime, $matches ) ) {
899
				// Y-m-d
900
				$datetime = array(
901
					'year'  => intval( $matches[1] ),
902
					'month' => intval( $matches[2] ),
903
					'day'   => intval( $matches[3] ),
904
				);
905
906
			} elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2}) (\d{2}):(\d{2})$/', $datetime, $matches ) ) {
907
				// Y-m-d H:i
908
				$datetime = array(
909
					'year'   => intval( $matches[1] ),
910
					'month'  => intval( $matches[2] ),
911
					'day'    => intval( $matches[3] ),
912
					'hour'   => intval( $matches[4] ),
913
					'minute' => intval( $matches[5] ),
914
				);
915
			}
916
917
			// If no match is found, we don't support default_to_max.
918
			if ( ! is_array( $datetime ) ) {
919
				// @todo Timezone issues here possibly
920
				return gmdate( 'Y-m-d H:i:s', strtotime( $datetime, $now ) );
921
			}
922
		}
923
924
		$datetime = array_map( 'absint', $datetime );
925
926
		if ( ! isset( $datetime['year'] ) )
927
			$datetime['year'] = gmdate( 'Y', $now );
928
929
		if ( ! isset( $datetime['month'] ) )
930
			$datetime['month'] = ( $default_to_max ) ? 12 : 1;
931
932
		if ( ! isset( $datetime['day'] ) )
933
			$datetime['day'] = ( $default_to_max ) ? (int) date( 't', mktime( 0, 0, 0, $datetime['month'], 1, $datetime['year'] ) ) : 1;
934
935
		if ( ! isset( $datetime['hour'] ) )
936
			$datetime['hour'] = ( $default_to_max ) ? 23 : 0;
937
938
		if ( ! isset( $datetime['minute'] ) )
939
			$datetime['minute'] = ( $default_to_max ) ? 59 : 0;
940
941
		if ( ! isset( $datetime['second'] ) )
942
			$datetime['second'] = ( $default_to_max ) ? 59 : 0;
943
944
		return sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['minute'], $datetime['second'] );
945
	}
946
947
	/**
948
	 * Builds a query string for comparing time values (hour, minute, second).
949
	 *
950
	 * If just hour, minute, or second is set than a normal comparison will be done.
951
	 * However if multiple values are passed, a pseudo-decimal time will be created
952
	 * in order to be able to accurately compare against.
953
	 *
954
	 * @since 3.7.0
955
	 * @access public
956
	 *
957
	 * @param string $column The column to query against. Needs to be pre-validated!
958
	 * @param string $compare The comparison operator. Needs to be pre-validated!
959
	 * @param int|null $hour Optional. An hour value (0-23).
960
	 * @param int|null $minute Optional. A minute value (0-59).
961
	 * @param int|null $second Optional. A second value (0-59).
962
	 * @return string|false A query part or false on failure.
963
	 */
964
	public function build_time_query( $column, $compare, $hour = null, $minute = null, $second = null ) {
965
		// Have to have at least one
966
		if ( ! isset( $hour ) && ! isset( $minute ) && ! isset( $second ) )
967
			return false;
968
969
		// Complex combined queries aren't supported for multi-value queries
970
		if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
971
			$return = array();
972
973 View Code Duplication
			if ( isset( $hour ) && false !== ( $value = $this->build_value( $compare, $hour ) ) )
974
				$return[] = "HOUR( $column ) $compare $value";
975
976 View Code Duplication
			if ( isset( $minute ) && false !== ( $value = $this->build_value( $compare, $minute ) ) )
977
				$return[] = "MINUTE( $column ) $compare $value";
978
979 View Code Duplication
			if ( isset( $second ) && false !== ( $value = $this->build_value( $compare, $second ) ) )
980
				$return[] = "SECOND( $column ) $compare $value";
981
982
			return implode( ' AND ', $return );
983
		}
984
985
		// Cases where just one unit is set
986
		if ( isset( $hour ) && ! isset( $minute ) && ! isset( $second ) && false !== ( $value = $this->build_value( $compare, $hour ) ) ) {
987
			return "HOUR( $column ) $compare $value";
988
		} elseif ( ! isset( $hour ) && isset( $minute ) && ! isset( $second ) && false !== ( $value = $this->build_value( $compare, $minute ) ) ) {
989
			return "MINUTE( $column ) $compare $value";
990
		} elseif ( ! isset( $hour ) && ! isset( $minute ) && isset( $second ) && false !== ( $value = $this->build_value( $compare, $second ) ) ) {
991
			return "SECOND( $column ) $compare $value";
992
		}
993
994
		// Single units were already handled. Since hour & second isn't allowed, minute must to be set.
995
		if ( ! isset( $minute ) )
996
			return false;
997
998
		$format = $time = '';
999
1000
		// Hour
1001
		if ( null !== $hour ) {
1002
			$format .= '%H.';
1003
			$time   .= sprintf( '%02d', $hour ) . '.';
1004
		} else {
1005
			$format .= '0.';
1006
			$time   .= '0.';
1007
		}
1008
1009
		// Minute
1010
		$format .= '%i';
1011
		$time   .= sprintf( '%02d', $minute );
1012
1013
		if ( isset( $second ) ) {
1014
			$format .= '%s';
1015
			$time   .= sprintf( '%02d', $second );
1016
		}
1017
1018
		return $this->db->prepare( "DATE_FORMAT( $column, %s ) $compare %f", $format, $time );
1019
	}
1020
}
1021