Changeset 29923 for trunk/src/wp-includes/date.php
- Timestamp:
- 10/16/2014 07:33:24 PM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/date.php
r29797 r29923 1 1 <?php 2 2 /** 3 * WP_Date_Query will generate a MySQL WHERE clause for the specified date-based parameters.3 * Class for generating SQL clauses that filter a primary query according to date. 4 4 * 5 * Initialize the class by passing an array of arrays of parameters. 5 * `WP_Date_Query` is a helper that allows primary query classes, such as {@see WP_Query}, 6 * to filter their results by date columns, by generating `WHERE` subclauses to be attached 7 * to the primary SQL query string. 6 8 * 7 9 * @link http://codex.wordpress.org/Function_Reference/WP_Query Codex page. … … 11 13 class WP_Date_Query { 12 14 /** 13 * List of date queries. 15 * Array of date queries. 16 * 17 * See {@see WP_Date_Query::__construct()} for information on date query arguments. 14 18 * 15 19 * @since 3.7.0 … … 20 24 21 25 /** 22 * The relation between the queries. Can be either 'AND' or 'OR' and can be changed via the query arguments.26 * The default relation between top-level queries. Can be either 'AND' or 'OR'. 23 27 * 24 28 * @since 3.7.0 … … 47 51 48 52 /** 53 * Supported time-related parameter keys. 54 * 55 * @since 4.1.0 56 * @access public 57 * @var array 58 */ 59 public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'hour', 'minute', 'second' ); 60 61 /** 49 62 * Constructor. 50 63 * 51 64 * @since 3.7.0 52 65 * @since 4.0.0 The $inclusive logic was updated to include all times within the date range. 66 * @access public 53 67 * 54 68 * @param array $date_query { 55 * One or more associative arrays of date query parameters.69 * Array of date query clauses. 56 70 * 57 71 * @type array { … … 61 75 * 'comment_date_gmt'. 62 76 * @type string $compare Optional. The comparison operator. 63 * Default '='. Accepts '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN',77 * Accepts '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN'. Default '='. 64 78 * 'BETWEEN', 'NOT BETWEEN'. 65 * @type string $relation Optional. The boolean relationship between the date quer yies.66 * Default 'OR'. Accepts 'OR', 'AND'.79 * @type string $relation Optional. The boolean relationship between the date queries. 80 * Accepts 'OR', 'AND'. Default 'OR'. 67 81 * @type array { 82 Optional. An array of first-order clause parameters, or another fully-formed date query. 68 83 * @type string|array $before Optional. Date to retrieve posts before. Accepts strtotime()-compatible 69 84 * string, or array of 'year', 'month', 'day' values. { … … 110 125 */ 111 126 public function __construct( $date_query, $default_column = 'post_date' ) { 112 if ( empty( $date_query ) || ! is_array( $date_query ) ) 127 128 if ( isset( $date_query['relation'] ) && 'OR' === strtoupper( $date_query['relation'] ) ) { 129 $this->relation = 'OR'; 130 } else { 131 $this->relation = 'AND'; 132 } 133 134 if ( ! is_array( $date_query ) ) { 113 135 return; 114 115 if ( isset( $date_query['relation'] ) && strtoupper( $date_query['relation'] ) == 'OR' ) 116 $this->relation = 'OR'; 117 else 118 $this->relation = 'AND'; 119 120 if ( ! empty( $date_query['column'] ) ) 121 $this->column = esc_sql( $date_query['column'] ); 122 else 123 $this->column = esc_sql( $default_column ); 136 } 137 138 // Support for passing time-based keys in the top level of the $date_query array. 139 if ( ! isset( $date_query[0] ) && ! empty( $date_query ) ) { 140 $date_query = array( $date_query ); 141 } 142 143 if ( empty( $date_query ) ) { 144 return; 145 } 146 147 if ( ! empty( $date_query['column'] ) ) { 148 $date_query['column'] = esc_sql( $date_query['column'] ); 149 } else { 150 $date_query['column'] = esc_sql( $default_column ); 151 } 124 152 125 153 $this->column = $this->validate_column( $this->column ); … … 127 155 $this->compare = $this->get_compare( $date_query ); 128 156 129 // If an array of arrays wasn't passed, fix it 130 if ( ! isset( $date_query[0] ) ) 131 $date_query = array( $date_query ); 132 133 $this->queries = array(); 134 foreach ( $date_query as $key => $query ) { 135 if ( ! is_array( $query ) ) 157 $this->queries = $this->sanitize_query( $date_query ); 158 159 return; 160 } 161 162 /** 163 * Recursive-friendly query sanitizer. 164 * 165 * Ensures that each query-level clause has a 'relation' key, and that 166 * each first-order clause contains all the necessary keys from 167 * $defaults. 168 * 169 * @since 4.1.0 170 * @access public 171 * 172 * @param array $query A tax_query query clause. 173 * @return array Sanitized queries. 174 */ 175 public function sanitize_query( $queries, $parent_query = null ) { 176 $cleaned_query = array(); 177 178 $defaults = array( 179 'column' => 'post_date', 180 'compare' => '=', 181 'relation' => 'AND', 182 ); 183 184 // Numeric keys should always have array values. 185 foreach ( $queries as $qkey => $qvalue ) { 186 if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) { 187 unset( $queries[ $qkey ] ); 188 } 189 } 190 191 // Each query should have a value for each default key. Inherit from the parent when possible. 192 foreach ( $defaults as $dkey => $dvalue ) { 193 if ( isset( $queries[ $dkey ] ) ) { 136 194 continue; 137 138 $this->queries[$key] = $query; 139 } 195 } 196 197 if ( isset( $parent_query[ $dkey ] ) ) { 198 $queries[ $dkey ] = $parent_query[ $dkey ]; 199 } else { 200 $queries[ $dkey ] = $dvalue; 201 } 202 } 203 204 foreach ( $queries as $key => $q ) { 205 if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { 206 // This is a first-order query. Trust the values and sanitize when building SQL. 207 $cleaned_query[ $key ] = $q; 208 } else { 209 // Any array without a time key is another query, so we recurse. 210 $cleaned_query[] = $this->sanitize_query( $q, $queries ); 211 } 212 } 213 214 return $cleaned_query; 215 } 216 217 /** 218 * Determine whether this is a first-order clause. 219 * 220 * Checks to see if the current clause has any time-related keys. 221 * If so, it's first-order. 222 * 223 * @param array $query Query clause. 224 * @return bool True if this is a first-order clause. 225 */ 226 protected function is_first_order_clause( $query ) { 227 $time_keys = array_intersect( $this->time_keys, array_keys( $query ) ); 228 return ! empty( $time_keys ); 140 229 } 141 230 … … 146 235 * @access public 147 236 * 148 * @param array $query A date query or a date subquery 149 * @return string The comparison operator 237 * @param array $query A date query or a date subquery. 238 * @return string The comparison operator. 150 239 */ 151 240 public function get_compare( $query ) { … … 185 274 186 275 /** 187 * Turns an array of date query parameters into a MySQL string.188 * 189 * @since 3.7.0 190 * @access public 191 * 192 * @return string MySQL WHERE parameters276 * Generate WHERE clause to be appended to a main query. 277 * 278 * @since 3.7.0 279 * @access public 280 * 281 * @return string MySQL WHERE clause. 193 282 */ 194 283 public function get_sql() { 195 // The parts of the final query 196 $where = array(); 197 198 foreach ( $this->queries as $key => $query ) { 199 $where_parts = $this->get_sql_for_subquery( $query ); 200 if ( $where_parts ) { 201 // Combine the parts of this subquery into a single string 202 $where[ $key ] = '( ' . implode( ' AND ', $where_parts ) . ' )'; 203 } 204 } 205 206 // Combine the subquery strings into a single string 207 if ( $where ) 208 $where = ' AND ( ' . implode( " {$this->relation} ", $where ) . ' )'; 209 else 210 $where = ''; 284 $sql = $this->get_sql_clauses(); 285 286 $where = $sql['where']; 211 287 212 288 /** … … 222 298 223 299 /** 224 * Turns a single date subquery into pieces for a WHERE clause. 225 * 226 * @since 3.7.0 227 * return array 300 * Generate SQL clauses to be appended to a main query. 301 * 302 * Called by the public {@see WP_Date_Query::get_sql()}, this method 303 * is abstracted out to maintain parity with the other Query classes. 304 * 305 * @since 4.1.0 306 * @access protected 307 * 308 * @return array { 309 * Array containing JOIN and WHERE SQL clauses to append to the main query. 310 * 311 * @type string $join SQL fragment to append to the main JOIN clause. 312 * @type string $where SQL fragment to append to the main WHERE clause. 313 * } 314 */ 315 protected function get_sql_clauses() { 316 $sql = $this->get_sql_for_query( $this->queries ); 317 318 if ( ! empty( $sql['where'] ) ) { 319 $sql['where'] = ' AND ' . $sql['where']; 320 } 321 322 return $sql; 323 } 324 325 /** 326 * Generate SQL clauses for a single query array. 327 * 328 * If nested subqueries are found, this method recurses the tree to 329 * produce the properly nested SQL. 330 * 331 * @since 4.1.0 332 * @access protected 333 * 334 * @param array $query Query to parse. 335 * @param int $depth Optional. Number of tree levels deep we currently are. 336 * Used to calculate indentation. 337 * @return array { 338 * Array containing JOIN and WHERE SQL clauses to append to a single query array. 339 * 340 * @type string $join SQL fragment to append to the main JOIN clause. 341 * @type string $where SQL fragment to append to the main WHERE clause. 342 * } 343 */ 344 protected function get_sql_for_query( $query, $depth = 0 ) { 345 $sql_chunks = array( 346 'join' => array(), 347 'where' => array(), 348 ); 349 350 $sql = array( 351 'join' => '', 352 'where' => '', 353 ); 354 355 $indent = ''; 356 for ( $i = 0; $i < $depth; $i++ ) { 357 $indent .= " "; 358 } 359 360 foreach ( $query as $key => $clause ) { 361 if ( 'relation' === $key ) { 362 $relation = $query['relation']; 363 } else if ( is_array( $clause ) ) { 364 365 // This is a first-order clause. 366 if ( $this->is_first_order_clause( $clause ) ) { 367 $clause_sql = $this->get_sql_for_clause( $clause, $query ); 368 369 $where_count = count( $clause_sql['where'] ); 370 if ( ! $where_count ) { 371 $sql_chunks['where'][] = ''; 372 } else if ( 1 === $where_count ) { 373 $sql_chunks['where'][] = $clause_sql['where'][0]; 374 } else { 375 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; 376 } 377 378 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); 379 // This is a subquery, so we recurse. 380 } else { 381 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); 382 383 $sql_chunks['where'][] = $clause_sql['where']; 384 $sql_chunks['join'][] = $clause_sql['join']; 385 } 386 } 387 } 388 389 // Filter to remove empties. 390 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); 391 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); 392 393 if ( empty( $relation ) ) { 394 $relation = 'AND'; 395 } 396 397 // Filter duplicate JOIN clauses and combine into a single string. 398 if ( ! empty( $sql_chunks['join'] ) ) { 399 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); 400 } 401 402 // Generate a single WHERE clause with proper brackets and indentation. 403 if ( ! empty( $sql_chunks['where'] ) ) { 404 $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; 405 } 406 407 return $sql; 408 } 409 410 /** 411 * Turns a single date clause into pieces for a WHERE clause. 412 * 413 * A wrapper for get_sql_for_clause(), included here for backward 414 * compatibility while retaining the naming convention across Query classes. 415 * 416 * @since 3.7.0 417 * @access protected 418 * 419 * @param array $query Date query arguments. 420 * @return array { 421 * Array containing JOIN and WHERE SQL clauses to append to the main query. 422 * 423 * @type string $join SQL fragment to append to the main JOIN clause. 424 * @type string $where SQL fragment to append to the main WHERE clause. 425 * } 228 426 */ 229 427 protected function get_sql_for_subquery( $query ) { 428 return $this->get_sql_for_clause( $query, '' ); 429 } 430 431 /** 432 * Turns a first-order date query into SQL for a WHERE clause. 433 * 434 * @since 4.1.0 435 * @access protected 436 * 437 * @param array $query Date query clause. 438 * @param array $parent_query Parent query of the current date query. 439 * @return array { 440 * Array containing JOIN and WHERE SQL clauses to append to the main query. 441 * 442 * @type string $join SQL fragment to append to the main JOIN clause. 443 * @type string $where SQL fragment to append to the main WHERE clause. 444 * } 445 */ 446 protected function get_sql_for_clause( $query, $parent_query ) { 230 447 global $wpdb; 231 448 232 // The sub-parts of a $where part 449 // The sub-parts of a $where part. 233 450 $where_parts = array(); 234 451 … … 250 467 } 251 468 252 // Range queries 469 // Range queries. 253 470 if ( ! empty( $query['after'] ) ) 254 471 $where_parts[] = $wpdb->prepare( "$column $gt %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) ); … … 257 474 $where_parts[] = $wpdb->prepare( "$column $lt %s", $this->build_mysql_datetime( $query['before'], $inclusive ) ); 258 475 259 // Specific value queries 476 // Specific value queries. 260 477 261 478 if ( isset( $query['year'] ) && $value = $this->build_value( $compare, $query['year'] ) ) … … 282 499 283 500 if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) { 284 // Avoid notices 501 // Avoid notices. 285 502 foreach ( array( 'hour', 'minute', 'second' ) as $unit ) { 286 if ( ! isset( $query[ $unit] ) ) {287 $query[ $unit] = null;503 if ( ! isset( $query[ $unit ] ) ) { 504 $query[ $unit ] = null; 288 505 } 289 506 } … … 294 511 } 295 512 296 return $where_parts; 513 /* 514 * Return an array of 'join' and 'where' for compatibility 515 * with other query classes. 516 */ 517 return array( 518 'where' => $where_parts, 519 'join' => array(), 520 ); 297 521 } 298 522
Note: See TracChangeset
for help on using the changeset viewer.