wp/wp-includes/class-wp-tax-query.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    41 
    41 
    42 	/**
    42 	/**
    43 	 * Standard response when the query should not return any rows.
    43 	 * Standard response when the query should not return any rows.
    44 	 *
    44 	 *
    45 	 * @since 3.2.0
    45 	 * @since 3.2.0
    46 	 *
       
    47 	 * @static
       
    48 	 * @var string
    46 	 * @var string
    49 	 */
    47 	 */
    50 	private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) );
    48 	private static $no_results = array(
       
    49 		'join'  => array( '' ),
       
    50 		'where' => array( '0 = 1' ),
       
    51 	);
    51 
    52 
    52 	/**
    53 	/**
    53 	 * A flat list of table aliases used in the JOIN clauses.
    54 	 * A flat list of table aliases used in the JOIN clauses.
    54 	 *
    55 	 *
    55 	 * @since 4.1.0
    56 	 * @since 4.1.0
   133 	 */
   134 	 */
   134 	public function sanitize_query( $queries ) {
   135 	public function sanitize_query( $queries ) {
   135 		$cleaned_query = array();
   136 		$cleaned_query = array();
   136 
   137 
   137 		$defaults = array(
   138 		$defaults = array(
   138 			'taxonomy' => '',
   139 			'taxonomy'         => '',
   139 			'terms' => array(),
   140 			'terms'            => array(),
   140 			'field' => 'term_id',
   141 			'field'            => 'term_id',
   141 			'operator' => 'IN',
   142 			'operator'         => 'IN',
   142 			'include_children' => true,
   143 			'include_children' => true,
   143 		);
   144 		);
   144 
   145 
   145 		foreach ( $queries as $key => $query ) {
   146 		foreach ( $queries as $key => $query ) {
   146 			if ( 'relation' === $key ) {
   147 			if ( 'relation' === $key ) {
   147 				$cleaned_query['relation'] = $this->sanitize_relation( $query );
   148 				$cleaned_query['relation'] = $this->sanitize_relation( $query );
   148 
   149 
   149 			// First-order clause.
   150 				// First-order clause.
   150 			} elseif ( self::is_first_order_clause( $query ) ) {
   151 			} elseif ( self::is_first_order_clause( $query ) ) {
   151 
   152 
   152 				$cleaned_clause = array_merge( $defaults, $query );
   153 				$cleaned_clause          = array_merge( $defaults, $query );
   153 				$cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
   154 				$cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
   154 				$cleaned_query[] = $cleaned_clause;
   155 				$cleaned_query[]         = $cleaned_clause;
   155 
   156 
   156 				/*
   157 				/*
   157 				 * Keep a copy of the clause in the flate
   158 				 * Keep a copy of the clause in the flate
   158 				 * $queried_terms array, for use in WP_Query.
   159 				 * $queried_terms array, for use in WP_Query.
   159 				 */
   160 				 */
   174 					if ( ! empty( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
   175 					if ( ! empty( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
   175 						$this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
   176 						$this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
   176 					}
   177 					}
   177 				}
   178 				}
   178 
   179 
   179 			// Otherwise, it's a nested query, so we recurse.
   180 				// Otherwise, it's a nested query, so we recurse.
   180 			} elseif ( is_array( $query ) ) {
   181 			} elseif ( is_array( $query ) ) {
   181 				$cleaned_subquery = $this->sanitize_query( $query );
   182 				$cleaned_subquery = $this->sanitize_query( $query );
   182 
   183 
   183 				if ( ! empty( $cleaned_subquery ) ) {
   184 				if ( ! empty( $cleaned_subquery ) ) {
   184 					// All queries with children must have a relation.
   185 					// All queries with children must have a relation.
   219 	 * for backward compatibility. Any clause that doesn't meet this is
   220 	 * for backward compatibility. Any clause that doesn't meet this is
   220 	 * determined, by process of elimination, to be a higher-order query.
   221 	 * determined, by process of elimination, to be a higher-order query.
   221 	 *
   222 	 *
   222 	 * @since 4.1.0
   223 	 * @since 4.1.0
   223 	 *
   224 	 *
   224 	 * @static
       
   225 	 *
       
   226 	 * @param array $query Tax query arguments.
   225 	 * @param array $query Tax query arguments.
   227 	 * @return bool Whether the query clause is a first-order clause.
   226 	 * @return bool Whether the query clause is a first-order clause.
   228 	 */
   227 	 */
   229 	protected static function is_first_order_clause( $query ) {
   228 	protected static function is_first_order_clause( $query ) {
   230 		return is_array( $query ) && ( empty( $query ) || array_key_exists( 'terms', $query ) || array_key_exists( 'taxonomy', $query ) || array_key_exists( 'include_children', $query ) || array_key_exists( 'field', $query ) || array_key_exists( 'operator', $query ) );
   229 		return is_array( $query ) && ( empty( $query ) || array_key_exists( 'terms', $query ) || array_key_exists( 'taxonomy', $query ) || array_key_exists( 'include_children', $query ) || array_key_exists( 'field', $query ) || array_key_exists( 'operator', $query ) );
   232 
   231 
   233 	/**
   232 	/**
   234 	 * Generates SQL clauses to be appended to a main query.
   233 	 * Generates SQL clauses to be appended to a main query.
   235 	 *
   234 	 *
   236 	 * @since 3.1.0
   235 	 * @since 3.1.0
   237 	 *
       
   238 	 * @static
       
   239 	 *
   236 	 *
   240 	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
   237 	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
   241 	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
   238 	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
   242 	 * @return array {
   239 	 * @return array {
   243 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
   240 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
   245 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
   242 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
   246 	 *     @type string $where SQL fragment to append to the main WHERE clause.
   243 	 *     @type string $where SQL fragment to append to the main WHERE clause.
   247 	 * }
   244 	 * }
   248 	 */
   245 	 */
   249 	public function get_sql( $primary_table, $primary_id_column ) {
   246 	public function get_sql( $primary_table, $primary_id_column ) {
   250 		$this->primary_table = $primary_table;
   247 		$this->primary_table     = $primary_table;
   251 		$this->primary_id_column = $primary_id_column;
   248 		$this->primary_id_column = $primary_id_column;
   252 
   249 
   253 		return $this->get_sql_clauses();
   250 		return $this->get_sql_clauses();
   254 	}
   251 	}
   255 
   252 
   272 		/*
   269 		/*
   273 		 * $queries are passed by reference to get_sql_for_query() for recursion.
   270 		 * $queries are passed by reference to get_sql_for_query() for recursion.
   274 		 * To keep $this->queries unaltered, pass a copy.
   271 		 * To keep $this->queries unaltered, pass a copy.
   275 		 */
   272 		 */
   276 		$queries = $this->queries;
   273 		$queries = $this->queries;
   277 		$sql = $this->get_sql_for_query( $queries );
   274 		$sql     = $this->get_sql_for_query( $queries );
   278 
   275 
   279 		if ( ! empty( $sql['where'] ) ) {
   276 		if ( ! empty( $sql['where'] ) ) {
   280 			$sql['where'] = ' AND ' . $sql['where'];
   277 			$sql['where'] = ' AND ' . $sql['where'];
   281 		}
   278 		}
   282 
   279 
   312 			'where' => '',
   309 			'where' => '',
   313 		);
   310 		);
   314 
   311 
   315 		$indent = '';
   312 		$indent = '';
   316 		for ( $i = 0; $i < $depth; $i++ ) {
   313 		for ( $i = 0; $i < $depth; $i++ ) {
   317 			$indent .= "  ";
   314 			$indent .= '  ';
   318 		}
   315 		}
   319 
   316 
   320 		foreach ( $query as $key => &$clause ) {
   317 		foreach ( $query as $key => &$clause ) {
   321 			if ( 'relation' === $key ) {
   318 			if ( 'relation' === $key ) {
   322 				$relation = $query['relation'];
   319 				$relation = $query['relation'];
   334 					} else {
   331 					} else {
   335 						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
   332 						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
   336 					}
   333 					}
   337 
   334 
   338 					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
   335 					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
   339 				// This is a subquery, so we recurse.
   336 					// This is a subquery, so we recurse.
   340 				} else {
   337 				} else {
   341 					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
   338 					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
   342 
   339 
   343 					$sql_chunks['where'][] = $clause_sql['where'];
   340 					$sql_chunks['where'][] = $clause_sql['where'];
   344 					$sql_chunks['join'][]  = $clause_sql['join'];
   341 					$sql_chunks['join'][]  = $clause_sql['join'];
   397 
   394 
   398 		if ( is_wp_error( $clause ) ) {
   395 		if ( is_wp_error( $clause ) ) {
   399 			return self::$no_results;
   396 			return self::$no_results;
   400 		}
   397 		}
   401 
   398 
   402 		$terms = $clause['terms'];
   399 		$terms    = $clause['terms'];
   403 		$operator = strtoupper( $clause['operator'] );
   400 		$operator = strtoupper( $clause['operator'] );
   404 
   401 
   405 		if ( 'IN' == $operator ) {
   402 		if ( 'IN' == $operator ) {
   406 
   403 
   407 			if ( empty( $terms ) ) {
   404 			if ( empty( $terms ) ) {
   414 			 * Before creating another table join, see if this clause has a
   411 			 * Before creating another table join, see if this clause has a
   415 			 * sibling with an existing join that can be shared.
   412 			 * sibling with an existing join that can be shared.
   416 			 */
   413 			 */
   417 			$alias = $this->find_compatible_table_alias( $clause, $parent_query );
   414 			$alias = $this->find_compatible_table_alias( $clause, $parent_query );
   418 			if ( false === $alias ) {
   415 			if ( false === $alias ) {
   419 				$i = count( $this->table_aliases );
   416 				$i     = count( $this->table_aliases );
   420 				$alias = $i ? 'tt' . $i : $wpdb->term_relationships;
   417 				$alias = $i ? 'tt' . $i : $wpdb->term_relationships;
   421 
   418 
   422 				// Store the alias as part of a flat array to build future iterators.
   419 				// Store the alias as part of a flat array to build future iterators.
   423 				$this->table_aliases[] = $alias;
   420 				$this->table_aliases[] = $alias;
   424 
   421 
   427 
   424 
   428 				$join .= " LEFT JOIN $wpdb->term_relationships";
   425 				$join .= " LEFT JOIN $wpdb->term_relationships";
   429 				$join .= $i ? " AS $alias" : '';
   426 				$join .= $i ? " AS $alias" : '';
   430 				$join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
   427 				$join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
   431 			}
   428 			}
   432 
       
   433 
   429 
   434 			$where = "$alias.term_taxonomy_id $operator ($terms)";
   430 			$where = "$alias.term_taxonomy_id $operator ($terms)";
   435 
   431 
   436 		} elseif ( 'NOT IN' == $operator ) {
   432 		} elseif ( 'NOT IN' == $operator ) {
   437 
   433 
   464 				AND object_id = $this->primary_table.$this->primary_id_column
   460 				AND object_id = $this->primary_table.$this->primary_id_column
   465 			) = $num_terms";
   461 			) = $num_terms";
   466 
   462 
   467 		} elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
   463 		} elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
   468 
   464 
   469 			$where = $wpdb->prepare( "$operator (
   465 			$where = $wpdb->prepare(
       
   466 				"$operator (
   470 				SELECT 1
   467 				SELECT 1
   471 				FROM $wpdb->term_relationships
   468 				FROM $wpdb->term_relationships
   472 				INNER JOIN $wpdb->term_taxonomy
   469 				INNER JOIN $wpdb->term_taxonomy
   473 				ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id
   470 				ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id
   474 				WHERE $wpdb->term_taxonomy.taxonomy = %s
   471 				WHERE $wpdb->term_taxonomy.taxonomy = %s
   475 				AND $wpdb->term_relationships.object_id = $this->primary_table.$this->primary_id_column
   472 				AND $wpdb->term_relationships.object_id = $this->primary_table.$this->primary_id_column
   476 			)", $clause['taxonomy'] );
   473 			)",
       
   474 				$clause['taxonomy']
       
   475 			);
   477 
   476 
   478 		}
   477 		}
   479 
   478 
   480 		$sql['join'][]  = $join;
   479 		$sql['join'][]  = $join;
   481 		$sql['where'][] = $where;
   480 		$sql['where'][] = $where;
   559 		$query['terms'] = array_unique( (array) $query['terms'] );
   558 		$query['terms'] = array_unique( (array) $query['terms'] );
   560 
   559 
   561 		if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
   560 		if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
   562 			$this->transform_query( $query, 'term_id' );
   561 			$this->transform_query( $query, 'term_id' );
   563 
   562 
   564 			if ( is_wp_error( $query ) )
   563 			if ( is_wp_error( $query ) ) {
   565 				return;
   564 				return;
       
   565 			}
   566 
   566 
   567 			$children = array();
   567 			$children = array();
   568 			foreach ( $query['terms'] as $term ) {
   568 			foreach ( $query['terms'] as $term ) {
   569 				$children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
   569 				$children   = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
   570 				$children[] = $term;
   570 				$children[] = $term;
   571 			}
   571 			}
   572 			$query['terms'] = $children;
   572 			$query['terms'] = $children;
   573 		}
   573 		}
   574 
   574 
   588 	 * @param array  $query           The single query. Passed by reference.
   588 	 * @param array  $query           The single query. Passed by reference.
   589 	 * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
   589 	 * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
   590 	 *                                or 'term_id'. Default 'term_id'.
   590 	 *                                or 'term_id'. Default 'term_id'.
   591 	 */
   591 	 */
   592 	public function transform_query( &$query, $resulting_field ) {
   592 	public function transform_query( &$query, $resulting_field ) {
   593 		if ( empty( $query['terms'] ) )
   593 		if ( empty( $query['terms'] ) ) {
   594 			return;
   594 			return;
   595 
   595 		}
   596 		if ( $query['field'] == $resulting_field )
   596 
       
   597 		if ( $query['field'] == $resulting_field ) {
   597 			return;
   598 			return;
       
   599 		}
   598 
   600 
   599 		$resulting_field = sanitize_key( $resulting_field );
   601 		$resulting_field = sanitize_key( $resulting_field );
   600 
   602 
   601 		// Empty 'terms' always results in a null transformation.
   603 		// Empty 'terms' always results in a null transformation.
   602 		$terms = array_filter( $query['terms'] );
   604 		$terms = array_filter( $query['terms'] );