wp/wp-includes/class-wp-meta-query.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    17  * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
    17  * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
    18  * to the primary SQL query string.
    18  * to the primary SQL query string.
    19  *
    19  *
    20  * @since 3.2.0
    20  * @since 3.2.0
    21  */
    21  */
       
    22 #[AllowDynamicProperties]
    22 class WP_Meta_Query {
    23 class WP_Meta_Query {
    23 	/**
    24 	/**
    24 	 * Array of metadata queries.
    25 	 * Array of metadata queries.
    25 	 *
    26 	 *
    26 	 * See WP_Meta_Query::__construct() for information on meta query arguments.
    27 	 * See WP_Meta_Query::__construct() for information on meta query arguments.
   120 	 *                                            - 'NOT LIKE'
   121 	 *                                            - 'NOT LIKE'
   121 	 *                                            - 'IN'
   122 	 *                                            - 'IN'
   122 	 *                                            - 'NOT IN'
   123 	 *                                            - 'NOT IN'
   123 	 *                                            - 'REGEXP'
   124 	 *                                            - 'REGEXP'
   124 	 *                                            - 'NOT REGEXP'
   125 	 *                                            - 'NOT REGEXP'
   125 	 *                                            - 'RLIKE',
   126 	 *                                            - 'RLIKE'
   126 	 *                                            - 'EXISTS' (alias of '=')
   127 	 *                                            - 'EXISTS' (alias of '=')
   127 	 *                                            - 'NOT EXISTS' (alias of '!=')
   128 	 *                                            - 'NOT EXISTS' (alias of '!=')
   128 	 *                                            Default is 'IN' when `$key` is an array, '=' otherwise.
   129 	 *                                            Default is 'IN' when `$key` is an array, '=' otherwise.
   129 	 *         @type string          $type_key    MySQL data type that the meta_key column will be CAST to for
   130 	 *         @type string          $type_key    MySQL data type that the meta_key column will be CAST to for
   130 	 *                                            comparisons. Accepts 'BINARY' for case-sensitive regular expression
   131 	 *                                            comparisons. Accepts 'BINARY' for case-sensitive regular expression
   131 	 *                                            comparisons. Default is ''.
   132 	 *                                            comparisons. Default is ''.
   132 	 *         @type string|string[] $value       Meta value or values to filter by.
   133 	 *         @type string|string[] $value       Meta value or values to filter by.
   133 	 *         @type string          $compare     MySQL operator used for comparing the $value. Accepts:
   134 	 *         @type string          $compare     MySQL operator used for comparing the $value. Accepts:
   134 	 *                                            - '=',
   135 	 *                                            - '='
   135 	 *                                            - '!='
   136 	 *                                            - '!='
   136 	 *                                            - '>'
   137 	 *                                            - '>'
   137 	 *                                            - '>='
   138 	 *                                            - '>='
   138 	 *                                            - '<'
   139 	 *                                            - '<'
   139 	 *                                            - '<='
   140 	 *                                            - '<='
   177 
   178 
   178 		$this->queries = $this->sanitize_query( $meta_query );
   179 		$this->queries = $this->sanitize_query( $meta_query );
   179 	}
   180 	}
   180 
   181 
   181 	/**
   182 	/**
   182 	 * Ensure the 'meta_query' argument passed to the class constructor is well-formed.
   183 	 * Ensures the 'meta_query' argument passed to the class constructor is well-formed.
   183 	 *
   184 	 *
   184 	 * Eliminates empty items and ensures that a 'relation' is set.
   185 	 * Eliminates empty items and ensures that a 'relation' is set.
   185 	 *
   186 	 *
   186 	 * @since 4.1.0
   187 	 * @since 4.1.0
   187 	 *
   188 	 *
   244 
   245 
   245 		return $clean_queries;
   246 		return $clean_queries;
   246 	}
   247 	}
   247 
   248 
   248 	/**
   249 	/**
   249 	 * Determine whether a query clause is first-order.
   250 	 * Determines whether a query clause is first-order.
   250 	 *
   251 	 *
   251 	 * A first-order meta query clause is one that has either a 'key' or
   252 	 * A first-order meta query clause is one that has either a 'key' or
   252 	 * a 'value' array key.
   253 	 * a 'value' array key.
   253 	 *
   254 	 *
   254 	 * @since 4.1.0
   255 	 * @since 4.1.0
   263 	/**
   264 	/**
   264 	 * Constructs a meta query based on 'meta_*' query vars
   265 	 * Constructs a meta query based on 'meta_*' query vars
   265 	 *
   266 	 *
   266 	 * @since 3.2.0
   267 	 * @since 3.2.0
   267 	 *
   268 	 *
   268 	 * @param array $qv The query variables
   269 	 * @param array $qv The query variables.
   269 	 */
   270 	 */
   270 	public function parse_query_vars( $qv ) {
   271 	public function parse_query_vars( $qv ) {
   271 		$meta_query = array();
   272 		$meta_query = array();
   272 
   273 
   273 		/*
   274 		/*
   306 
   307 
   307 		$this->__construct( $meta_query );
   308 		$this->__construct( $meta_query );
   308 	}
   309 	}
   309 
   310 
   310 	/**
   311 	/**
   311 	 * Return the appropriate alias for the given meta type if applicable.
   312 	 * Returns the appropriate alias for the given meta type if applicable.
   312 	 *
   313 	 *
   313 	 * @since 3.7.0
   314 	 * @since 3.7.0
   314 	 *
   315 	 *
   315 	 * @param string $type MySQL type to cast meta_value.
   316 	 * @param string $type MySQL type to cast meta_value.
   316 	 * @return string MySQL type.
   317 	 * @return string MySQL type.
   342 	 *                                  to 'post', 'comment', 'blog', 'term', and 'user'.
   343 	 *                                  to 'post', 'comment', 'blog', 'term', and 'user'.
   343 	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
   344 	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
   344 	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
   345 	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
   345 	 * @param object $context           Optional. The main query object that corresponds to the type, for
   346 	 * @param object $context           Optional. The main query object that corresponds to the type, for
   346 	 *                                  example a `WP_Query`, `WP_User_Query`, or `WP_Site_Query`.
   347 	 *                                  example a `WP_Query`, `WP_User_Query`, or `WP_Site_Query`.
       
   348 	 *                                  Default null.
   347 	 * @return string[]|false {
   349 	 * @return string[]|false {
   348 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query,
   350 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query,
   349 	 *     or false if no table exists for the requested meta type.
   351 	 *     or false if no table exists for the requested meta type.
   350 	 *
   352 	 *
   351 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
   353 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
   370 
   372 
   371 		/*
   373 		/*
   372 		 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
   374 		 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
   373 		 * be LEFT. Otherwise posts with no metadata will be excluded from results.
   375 		 * be LEFT. Otherwise posts with no metadata will be excluded from results.
   374 		 */
   376 		 */
   375 		if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
   377 		if ( str_contains( $sql['join'], 'LEFT JOIN' ) ) {
   376 			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
   378 			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
   377 		}
   379 		}
   378 
   380 
   379 		/**
   381 		/**
   380 		 * Filters the meta query's generated SQL.
   382 		 * Filters the meta query's generated SQL.
   392 		 */
   394 		 */
   393 		return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
   395 		return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
   394 	}
   396 	}
   395 
   397 
   396 	/**
   398 	/**
   397 	 * Generate SQL clauses to be appended to a main query.
   399 	 * Generates SQL clauses to be appended to a main query.
   398 	 *
   400 	 *
   399 	 * Called by the public WP_Meta_Query::get_sql(), this method is abstracted
   401 	 * Called by the public WP_Meta_Query::get_sql(), this method is abstracted
   400 	 * out to maintain parity with the other Query classes.
   402 	 * out to maintain parity with the other Query classes.
   401 	 *
   403 	 *
   402 	 * @since 4.1.0
   404 	 * @since 4.1.0
   422 
   424 
   423 		return $sql;
   425 		return $sql;
   424 	}
   426 	}
   425 
   427 
   426 	/**
   428 	/**
   427 	 * Generate SQL clauses for a single query array.
   429 	 * Generates SQL clauses for a single query array.
   428 	 *
   430 	 *
   429 	 * If nested subqueries are found, this method recurses the tree to
   431 	 * If nested subqueries are found, this method recurses the tree to
   430 	 * produce the properly nested SQL.
   432 	 * produce the properly nested SQL.
   431 	 *
   433 	 *
   432 	 * @since 4.1.0
   434 	 * @since 4.1.0
   506 
   508 
   507 		return $sql;
   509 		return $sql;
   508 	}
   510 	}
   509 
   511 
   510 	/**
   512 	/**
   511 	 * Generate SQL JOIN and WHERE clauses for a first-order query clause.
   513 	 * Generates SQL JOIN and WHERE clauses for a first-order query clause.
   512 	 *
   514 	 *
   513 	 * "First-order" means that it's an array with a 'key' or 'value'.
   515 	 * "First-order" means that it's an array with a 'key' or 'value'.
   514 	 *
   516 	 *
   515 	 * @since 4.1.0
   517 	 * @since 4.1.0
   516 	 *
   518 	 *
   518 	 *
   520 	 *
   519 	 * @param array  $clause       Query clause (passed by reference).
   521 	 * @param array  $clause       Query clause (passed by reference).
   520 	 * @param array  $parent_query Parent query array.
   522 	 * @param array  $parent_query Parent query array.
   521 	 * @param string $clause_key   Optional. The array key used to name the clause in the original `$meta_query`
   523 	 * @param string $clause_key   Optional. The array key used to name the clause in the original `$meta_query`
   522 	 *                             parameters. If not provided, a key will be generated automatically.
   524 	 *                             parameters. If not provided, a key will be generated automatically.
   523 	 * @return string[] {
   525 	 *                             Default empty string.
       
   526 	 * @return array {
   524 	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
   527 	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
   525 	 *
   528 	 *
   526 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
   529 	 *     @type string[] $join  Array of SQL fragments to append to the main JOIN clause.
   527 	 *     @type string $where SQL fragment to append to the main WHERE clause.
   530 	 *     @type string[] $where Array of SQL fragments to append to the main WHERE clause.
   528 	 * }
   531 	 * }
   529 	 */
   532 	 */
   530 	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
   533 	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
   531 		global $wpdb;
   534 		global $wpdb;
   532 
   535 
   628 		// Ensure unique clause keys, so none are overwritten.
   631 		// Ensure unique clause keys, so none are overwritten.
   629 		$iterator        = 1;
   632 		$iterator        = 1;
   630 		$clause_key_base = $clause_key;
   633 		$clause_key_base = $clause_key;
   631 		while ( isset( $this->clauses[ $clause_key ] ) ) {
   634 		while ( isset( $this->clauses[ $clause_key ] ) ) {
   632 			$clause_key = $clause_key_base . '-' . $iterator;
   635 			$clause_key = $clause_key_base . '-' . $iterator;
   633 			$iterator++;
   636 			++$iterator;
   634 		}
   637 		}
   635 
   638 
   636 		// Store the clause in our flat array.
   639 		// Store the clause in our flat array.
   637 		$this->clauses[ $clause_key ] =& $clause;
   640 		$this->clauses[ $clause_key ] =& $clause;
   638 
   641 
   677 						break;
   680 						break;
   678 					case 'RLIKE':
   681 					case 'RLIKE':
   679 					case 'REGEXP':
   682 					case 'REGEXP':
   680 						$operator = $meta_compare_key;
   683 						$operator = $meta_compare_key;
   681 						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
   684 						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
   682 							$cast = 'BINARY';
   685 							$cast     = 'BINARY';
       
   686 							$meta_key = "CAST($alias.meta_key AS BINARY)";
   683 						} else {
   687 						} else {
   684 							$cast = '';
   688 							$cast     = '';
       
   689 							$meta_key = "$alias.meta_key";
   685 						}
   690 						}
   686 						$where = $wpdb->prepare( "$alias.meta_key $operator $cast %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
   691 						$where = $wpdb->prepare( "$meta_key $operator $cast %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
   687 						break;
   692 						break;
   688 
   693 
   689 					case '!=':
   694 					case '!=':
   690 					case 'NOT EXISTS':
   695 					case 'NOT EXISTS':
   691 						$meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key = %s " . $meta_compare_string_end;
   696 						$meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key = %s " . $meta_compare_string_end;
   703 						$where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
   708 						$where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
   704 						break;
   709 						break;
   705 					case 'NOT REGEXP':
   710 					case 'NOT REGEXP':
   706 						$operator = $meta_compare_key;
   711 						$operator = $meta_compare_key;
   707 						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
   712 						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
   708 							$cast = 'BINARY';
   713 							$cast     = 'BINARY';
       
   714 							$meta_key = "CAST($subquery_alias.meta_key AS BINARY)";
   709 						} else {
   715 						} else {
   710 							$cast = '';
   716 							$cast     = '';
       
   717 							$meta_key = "$subquery_alias.meta_key";
   711 						}
   718 						}
   712 
   719 
   713 						$meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key REGEXP $cast %s " . $meta_compare_string_end;
   720 						$meta_compare_string = $meta_compare_string_start . "AND $meta_key REGEXP $cast %s " . $meta_compare_string_end;
   714 						$where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
   721 						$where               = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
   715 						break;
   722 						break;
   716 				}
   723 				}
   717 
   724 
   718 				$sql_chunks['where'][] = $where;
   725 				$sql_chunks['where'][] = $where;
   785 
   792 
   786 		return $sql_chunks;
   793 		return $sql_chunks;
   787 	}
   794 	}
   788 
   795 
   789 	/**
   796 	/**
   790 	 * Get a flattened list of sanitized meta clauses.
   797 	 * Gets a flattened list of sanitized meta clauses.
   791 	 *
   798 	 *
   792 	 * This array should be used for clause lookup, as when the table alias and CAST type must be determined for
   799 	 * This array should be used for clause lookup, as when the table alias and CAST type must be determined for
   793 	 * a value of 'orderby' corresponding to a meta clause.
   800 	 * a value of 'orderby' corresponding to a meta clause.
   794 	 *
   801 	 *
   795 	 * @since 4.2.0
   802 	 * @since 4.2.0
   799 	public function get_clauses() {
   806 	public function get_clauses() {
   800 		return $this->clauses;
   807 		return $this->clauses;
   801 	}
   808 	}
   802 
   809 
   803 	/**
   810 	/**
   804 	 * Identify an existing table alias that is compatible with the current
   811 	 * Identifies an existing table alias that is compatible with the current
   805 	 * query clause.
   812 	 * query clause.
   806 	 *
   813 	 *
   807 	 * We avoid unnecessary table joins by allowing each clause to look for
   814 	 * We avoid unnecessary table joins by allowing each clause to look for
   808 	 * an existing table alias that is compatible with the query that it
   815 	 * an existing table alias that is compatible with the query that it
   809 	 * needs to perform.
   816 	 * needs to perform.