Introduce support for nested queries in WP_Tax_Query.
Previously, tax query arguments could be joined by a single AND or OR relation. Now, these queries can be arbitrarily nested, allowing clauses to be linked together with multiple relations. In a few places, WP_Query runs through a list of clauses in a tax_query in order to set certain query vars for backward compatibility. The necessary changes have been made to WP_Query to support this feature with the new complex structure of tax_query. Unit tests are included for these backward compatibility fixes. Unit tests for the new nesting syntax are included. Props boonebgorges. Fixes #29718. See #29738. Built from https://develop.svn.wordpress.org/trunk@29891 git-svn-id: http://core.svn.wordpress.org/trunk@29647 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
d64b1ed955
commit
0143196338
|
@ -1672,7 +1672,11 @@ class WP_Query {
|
||||||
$this->parse_tax_query( $qv );
|
$this->parse_tax_query( $qv );
|
||||||
|
|
||||||
foreach ( $this->tax_query->queries as $tax_query ) {
|
foreach ( $this->tax_query->queries as $tax_query ) {
|
||||||
if ( 'NOT IN' != $tax_query['operator'] ) {
|
if ( ! is_array( $tax_query ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $tax_query['operator'] ) && 'NOT IN' != $tax_query['operator'] ) {
|
||||||
switch ( $tax_query['taxonomy'] ) {
|
switch ( $tax_query['taxonomy'] ) {
|
||||||
case 'category':
|
case 'category':
|
||||||
$this->is_category = true;
|
$this->is_category = true;
|
||||||
|
@ -2687,7 +2691,7 @@ class WP_Query {
|
||||||
if ( empty($post_type) ) {
|
if ( empty($post_type) ) {
|
||||||
// Do a fully inclusive search for currently registered post types of queried taxonomies
|
// Do a fully inclusive search for currently registered post types of queried taxonomies
|
||||||
$post_type = array();
|
$post_type = array();
|
||||||
$taxonomies = wp_list_pluck( $this->tax_query->queries, 'taxonomy' );
|
$taxonomies = array_keys( $this->tax_query->queried_terms );
|
||||||
foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) {
|
foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) {
|
||||||
$object_taxonomies = $pt === 'attachment' ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt );
|
$object_taxonomies = $pt === 'attachment' ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt );
|
||||||
if ( array_intersect( $taxonomies, $object_taxonomies ) )
|
if ( array_intersect( $taxonomies, $object_taxonomies ) )
|
||||||
|
@ -2704,52 +2708,57 @@ class WP_Query {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back-compat
|
/*
|
||||||
if ( !empty($this->tax_query->queries) ) {
|
* Ensure that 'taxonomy', 'term', 'term_id', 'cat', and
|
||||||
$tax_query_in_and = wp_list_filter( $this->tax_query->queries, array( 'operator' => 'NOT IN' ), 'NOT' );
|
* 'category_name' vars are set for backward compatibility.
|
||||||
if ( !empty( $tax_query_in_and ) ) {
|
*/
|
||||||
if ( !isset( $q['taxonomy'] ) ) {
|
if ( ! empty( $this->tax_query->queried_terms ) ) {
|
||||||
foreach ( $tax_query_in_and as $a_tax_query ) {
|
|
||||||
if ( !in_array( $a_tax_query['taxonomy'], array( 'category', 'post_tag' ) ) ) {
|
|
||||||
$q['taxonomy'] = $a_tax_query['taxonomy'];
|
|
||||||
if ( 'slug' == $a_tax_query['field'] )
|
|
||||||
$q['term'] = $a_tax_query['terms'][0];
|
|
||||||
else
|
|
||||||
$q['term_id'] = $a_tax_query['terms'][0];
|
|
||||||
|
|
||||||
break;
|
/*
|
||||||
|
* Set 'taxonomy', 'term', and 'term_id' to the
|
||||||
|
* first taxonomy other than 'post_tag' or 'category'.
|
||||||
|
*/
|
||||||
|
if ( ! isset( $q['taxonomy'] ) ) {
|
||||||
|
foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
|
||||||
|
if ( empty( $queried_items['terms'][0] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! in_array( $queried_taxonomy, array( 'category', 'post_tag' ) ) ) {
|
||||||
|
$q['taxonomy'] = $queried_taxonomy;
|
||||||
|
|
||||||
|
if ( 'slug' === $queried_items['field'] ) {
|
||||||
|
$q['term'] = $queried_items['terms'][0];
|
||||||
|
} else {
|
||||||
|
$q['term_id'] = $queried_items['terms'][0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$cat_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'category' ) );
|
// 'cat', 'category_name', 'tag_id'
|
||||||
if ( ! empty( $cat_query ) ) {
|
foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
|
||||||
$cat_query = reset( $cat_query );
|
if ( empty( $queried_items['terms'][0] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! empty( $cat_query['terms'][0] ) ) {
|
if ( 'category' === $queried_taxonomy ) {
|
||||||
$the_cat = get_term_by( $cat_query['field'], $cat_query['terms'][0], 'category' );
|
$the_cat = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'category' );
|
||||||
if ( $the_cat ) {
|
if ( $the_cat ) {
|
||||||
$this->set( 'cat', $the_cat->term_id );
|
$this->set( 'cat', $the_cat->term_id );
|
||||||
$this->set( 'category_name', $the_cat->slug );
|
$this->set( 'category_name', $the_cat->slug );
|
||||||
}
|
}
|
||||||
unset( $the_cat );
|
unset( $the_cat );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
unset( $cat_query );
|
|
||||||
|
|
||||||
$tag_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'post_tag' ) );
|
if ( 'post_tag' === $queried_taxonomy ) {
|
||||||
if ( ! empty( $tag_query ) ) {
|
$the_tag = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'post_tag' );
|
||||||
$tag_query = reset( $tag_query );
|
if ( $the_tag ) {
|
||||||
|
|
||||||
if ( ! empty( $tag_query['terms'][0] ) ) {
|
|
||||||
$the_tag = get_term_by( $tag_query['field'], $tag_query['terms'][0], 'post_tag' );
|
|
||||||
if ( $the_tag )
|
|
||||||
$this->set( 'tag_id', $the_tag->term_id );
|
$this->set( 'tag_id', $the_tag->term_id );
|
||||||
|
}
|
||||||
unset( $the_tag );
|
unset( $the_tag );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset( $tag_query );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !empty( $this->tax_query->queries ) || !empty( $this->meta_query->queries ) ) {
|
if ( !empty( $this->tax_query->queries ) || !empty( $this->meta_query->queries ) ) {
|
||||||
|
|
|
@ -627,24 +627,20 @@ function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container class for a multiple taxonomy query.
|
* Class for generating SQL clauses that filter a primary query according to object taxonomy terms.
|
||||||
|
*
|
||||||
|
* `WP_Tax_Query` is a helper that allows primary query classes, such as {@see WP_Query}, to filter
|
||||||
|
* their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
|
||||||
|
* to the primary SQL query string.
|
||||||
*
|
*
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
*/
|
*/
|
||||||
class WP_Tax_Query {
|
class WP_Tax_Query {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of taxonomy queries. A single taxonomy query is an associative array:
|
* Array of taxonomy queries.
|
||||||
* - 'taxonomy' string The taxonomy being queried. Optional when using the term_taxonomy_id field.
|
*
|
||||||
* - 'terms' string|array The list of terms
|
* See {@see WP_Tax_Query::__construct()} for information on tax query arguments.
|
||||||
* - 'field' string (optional) Which term field is being used.
|
|
||||||
* Possible values: 'term_id', 'slug', 'name', or 'term_taxonomy_id'
|
|
||||||
* Default: 'term_id'
|
|
||||||
* - 'operator' string (optional)
|
|
||||||
* Possible values: 'AND', 'IN' or 'NOT IN'.
|
|
||||||
* Default: 'IN'
|
|
||||||
* - 'include_children' bool (optional) Whether to include child terms. Requires that a taxonomy be specified.
|
|
||||||
* Default: true
|
|
||||||
*
|
*
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
* @access public
|
* @access public
|
||||||
|
@ -668,30 +664,53 @@ class WP_Tax_Query {
|
||||||
* @access private
|
* @access private
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private static $no_results = array( 'join' => '', 'where' => ' AND 0 = 1' );
|
private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flat list of table aliases used in the JOIN clauses.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access protected
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $table_aliases = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terms and taxonomies fetched by this query.
|
||||||
|
*
|
||||||
|
* We store this data in a flat array because they are referenced in a
|
||||||
|
* number of places by WP_Query.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access public
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $queried_terms = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* Parses a compact tax query and sets defaults.
|
|
||||||
*
|
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
* @access public
|
* @access public
|
||||||
*
|
*
|
||||||
* @param array $tax_query A compact tax query:
|
* @param array $tax_query {
|
||||||
* array(
|
* Array of taxonoy query clauses.
|
||||||
* 'relation' => 'OR',
|
*
|
||||||
* array(
|
* @type string $relation Optional. The MySQL keyword used to join
|
||||||
* 'taxonomy' => 'tax1',
|
* the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
|
||||||
* 'terms' => array( 'term1', 'term2' ),
|
* @type array {
|
||||||
* 'field' => 'slug',
|
* Optional. An array of first-order clause parameters, or another fully-formed tax query.
|
||||||
* ),
|
*
|
||||||
* array(
|
* @type string $taxonomy Taxonomy being queried. Optional when field=term_taxonomy_id.
|
||||||
* 'taxonomy' => 'tax2',
|
* @type string|int|array $terms Term or terms to filter by.
|
||||||
* 'terms' => array( 'term-a', 'term-b' ),
|
* @type string $field Field to match $terms against. Accepts 'term_id', 'slug',
|
||||||
* 'field' => 'slug',
|
* 'name', or 'term_taxonomy_id'. Default: 'term_id'.
|
||||||
* ),
|
* @type string $operator MySQL operator to be used with $terms in the WHERE clause.
|
||||||
* )
|
* Accepts 'AND', 'IN', or 'OR. Default: 'IN'.
|
||||||
|
* @type bool $include_children Optional. Whether to include child terms.
|
||||||
|
* Requires a $taxonomy. Default: true.
|
||||||
|
* }
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
public function __construct( $tax_query ) {
|
public function __construct( $tax_query ) {
|
||||||
if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR' ) {
|
if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR' ) {
|
||||||
|
@ -700,24 +719,96 @@ class WP_Tax_Query {
|
||||||
$this->relation = 'AND';
|
$this->relation = 'AND';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->queries = $this->sanitize_query( $tax_query );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the `tax_query` argument passed to the class constructor is well-formed.
|
||||||
|
*
|
||||||
|
* Ensures that each query-level clause has a 'relation' key, and that
|
||||||
|
* each first-order clause contains all the necessary keys from $defaults.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @param array $queries Array of queries clauses.
|
||||||
|
* @return array Sanitized array of query clauses.
|
||||||
|
*/
|
||||||
|
public function sanitize_query( $queries ) {
|
||||||
|
$cleaned_query = array();
|
||||||
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'taxonomy' => '',
|
'taxonomy' => '',
|
||||||
'terms' => array(),
|
'terms' => array(),
|
||||||
'include_children' => true,
|
|
||||||
'field' => 'term_id',
|
'field' => 'term_id',
|
||||||
'operator' => 'IN',
|
'operator' => 'IN',
|
||||||
|
'include_children' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ( $tax_query as $query ) {
|
foreach ( $queries as $key => $query ) {
|
||||||
if ( ! is_array( $query ) )
|
if ( 'relation' === $key ) {
|
||||||
continue;
|
$cleaned_query['relation'] = $query;
|
||||||
|
|
||||||
$query = array_merge( $defaults, $query );
|
// First-order clause.
|
||||||
|
} else if ( self::is_first_order_clause( $query ) ) {
|
||||||
|
|
||||||
$query['terms'] = (array) $query['terms'];
|
$cleaned_clause = array_merge( $defaults, $query );
|
||||||
|
$cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
|
||||||
|
$cleaned_query[] = $cleaned_clause;
|
||||||
|
|
||||||
$this->queries[] = $query;
|
/*
|
||||||
|
* Keep a copy of the clause in the flate
|
||||||
|
* $queried_terms array, for use in WP_Query.
|
||||||
|
*/
|
||||||
|
if ( ! empty( $cleaned_clause['taxonomy'] ) && 'NOT IN' !== $cleaned_clause['operator'] ) {
|
||||||
|
$taxonomy = $cleaned_clause['taxonomy'];
|
||||||
|
if ( ! isset( $this->queried_terms[ $taxonomy ] ) ) {
|
||||||
|
$this->queried_terms[ $taxonomy ] = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Backward compatibility: Only store the first
|
||||||
|
* 'terms' and 'field' found for a given taxonomy.
|
||||||
|
*/
|
||||||
|
if ( ! empty( $cleaned_clause['terms'] ) && ! isset( $this->queried_terms[ $taxonomy ]['terms'] ) ) {
|
||||||
|
$this->queried_terms[ $taxonomy ]['terms'] = $cleaned_clause['terms'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
|
||||||
|
$this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, it's a nested query, so we recurse.
|
||||||
|
} else if ( is_array( $query ) ) {
|
||||||
|
$cleaned_subquery = $this->sanitize_query( $query );
|
||||||
|
|
||||||
|
if ( ! empty( $cleaned_subquery ) ) {
|
||||||
|
$cleaned_query[] = $cleaned_subquery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cleaned_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a clause is first-order.
|
||||||
|
*
|
||||||
|
* A "first-order" clause is one that contains any of the first-order
|
||||||
|
* clause keys ('terms', 'taxonomy', 'include_children', 'field',
|
||||||
|
* 'operator'). An empty clause also counts as a first-order clause,
|
||||||
|
* for backward compatibility. Any clause that doesn't meet this is
|
||||||
|
* determined, by process of elimination, to be a higher-order query.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access protected
|
||||||
|
*
|
||||||
|
* @param array $query Tax query arguments.
|
||||||
|
* @return bool Whether the query clause is a first-order clause.
|
||||||
|
*/
|
||||||
|
protected static function is_first_order_clause( $query ) {
|
||||||
|
return 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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -726,50 +817,185 @@ class WP_Tax_Query {
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
* @access public
|
* @access public
|
||||||
*
|
*
|
||||||
* @param string $primary_table
|
* @param string $primary_table Database table where the object being filtered is stored (eg wp_users).
|
||||||
* @param string $primary_id_column
|
* @param string $primary_id_column ID column for the filtered object in $primary_table.
|
||||||
* @return array
|
* @return array {
|
||||||
|
* Array containing JOIN and WHERE SQL clauses to append to the main query.
|
||||||
|
*
|
||||||
|
* @type string $join SQL fragment to append to the main JOIN clause.
|
||||||
|
* @type string $where SQL fragment to append to the main WHERE clause.
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
public function get_sql( $primary_table, $primary_id_column ) {
|
public function get_sql( $primary_table, $primary_id_column ) {
|
||||||
|
$this->primary_table = $primary_table;
|
||||||
|
$this->primary_id_column = $primary_id_column;
|
||||||
|
|
||||||
|
return $this->get_sql_clauses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate SQL clauses to be appended to a main query.
|
||||||
|
*
|
||||||
|
* Called by the public {@see WP_Tax_Query::get_sql()}, this method
|
||||||
|
* is abstracted out to maintain parity with the other Query classes.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access protected
|
||||||
|
*
|
||||||
|
* @return array {
|
||||||
|
* Array containing JOIN and WHERE SQL clauses to append to the main query.
|
||||||
|
*
|
||||||
|
* @type string $join SQL fragment to append to the main JOIN clause.
|
||||||
|
* @type string $where SQL fragment to append to the main WHERE clause.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
protected function get_sql_clauses() {
|
||||||
|
$sql = $this->get_sql_for_query( $this->queries );
|
||||||
|
|
||||||
|
if ( ! empty( $sql['where'] ) ) {
|
||||||
|
$sql['where'] = ' AND ' . $sql['where'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate SQL clauses for a single query array.
|
||||||
|
*
|
||||||
|
* If nested subqueries are found, this method recurses the tree to
|
||||||
|
* produce the properly nested SQL.
|
||||||
|
*
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access protected
|
||||||
|
*
|
||||||
|
* @param array $query Query to parse.
|
||||||
|
* @param int $depth Optional. Number of tree levels deep we currently are.
|
||||||
|
* Used to calculate indentation.
|
||||||
|
* @return array {
|
||||||
|
* Array containing JOIN and WHERE SQL clauses to append to a single query array.
|
||||||
|
*
|
||||||
|
* @type string $join SQL fragment to append to the main JOIN clause.
|
||||||
|
* @type string $where SQL fragment to append to the main WHERE clause.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
protected function get_sql_for_query( $query, $depth = 0 ) {
|
||||||
|
$sql_chunks = array(
|
||||||
|
'join' => array(),
|
||||||
|
'where' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$sql = array(
|
||||||
|
'join' => '',
|
||||||
|
'where' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
$indent = '';
|
||||||
|
for ( $i = 0; $i < $depth; $i++ ) {
|
||||||
|
$indent .= " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $query as $key => $clause ) {
|
||||||
|
if ( 'relation' === $key ) {
|
||||||
|
$relation = $query['relation'];
|
||||||
|
} else if ( is_array( $clause ) ) {
|
||||||
|
|
||||||
|
// This is a first-order clause.
|
||||||
|
if ( $this->is_first_order_clause( $clause ) ) {
|
||||||
|
$clause_sql = $this->get_sql_for_clause( $clause, $query );
|
||||||
|
|
||||||
|
$where_count = count( $clause_sql['where'] );
|
||||||
|
if ( ! $where_count ) {
|
||||||
|
$sql_chunks['where'][] = '';
|
||||||
|
} else if ( 1 === $where_count ) {
|
||||||
|
$sql_chunks['where'][] = $clause_sql['where'][0];
|
||||||
|
} else {
|
||||||
|
$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
|
||||||
|
// This is a subquery, so we recurse.
|
||||||
|
} else {
|
||||||
|
$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
|
||||||
|
|
||||||
|
$sql_chunks['where'][] = $clause_sql['where'];
|
||||||
|
$sql_chunks['join'][] = $clause_sql['join'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to remove empties.
|
||||||
|
$sql_chunks['join'] = array_filter( $sql_chunks['join'] );
|
||||||
|
$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
|
||||||
|
|
||||||
|
if ( empty( $relation ) ) {
|
||||||
|
$relation = 'AND';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter duplicate JOIN clauses and combine into a single string.
|
||||||
|
if ( ! empty( $sql_chunks['join'] ) ) {
|
||||||
|
$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a single WHERE clause with proper brackets and indentation.
|
||||||
|
if ( ! empty( $sql_chunks['where'] ) ) {
|
||||||
|
$sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate SQL JOIN and WHERE clauses for a first-order query clause.
|
||||||
|
|
||||||
|
* @since 4.1.0
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @param array $clause Query clause.
|
||||||
|
* @param array $parent_query Parent query array.
|
||||||
|
* @return array {
|
||||||
|
* Array containing JOIN and WHERE SQL clauses to append to a first-order query.
|
||||||
|
*
|
||||||
|
* @type string $join SQL fragment to append to the main JOIN clause.
|
||||||
|
* @type string $where SQL fragment to append to the main WHERE clause.
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public function get_sql_for_clause( $clause, $parent_query ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = array(
|
||||||
|
'where' => array(),
|
||||||
|
'join' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
$join = '';
|
$join = '';
|
||||||
$where = array();
|
|
||||||
$i = 0;
|
|
||||||
$count = count( $this->queries );
|
|
||||||
|
|
||||||
foreach ( $this->queries as $index => $query ) {
|
$this->clean_query( $clause );
|
||||||
$this->clean_query( $query );
|
|
||||||
|
|
||||||
if ( is_wp_error( $query ) ) {
|
if ( is_wp_error( $clause ) ) {
|
||||||
return self::$no_results;
|
return self::$no_results;
|
||||||
}
|
}
|
||||||
|
|
||||||
$terms = $query['terms'];
|
$terms = $clause['terms'];
|
||||||
$operator = strtoupper( $query['operator'] );
|
$operator = strtoupper( $clause['operator'] );
|
||||||
|
|
||||||
if ( 'IN' == $operator ) {
|
if ( 'IN' == $operator ) {
|
||||||
|
|
||||||
if ( empty( $terms ) ) {
|
if ( empty( $terms ) ) {
|
||||||
if ( 'OR' == $this->relation ) {
|
|
||||||
if ( ( $index + 1 === $count ) && empty( $where ) ) {
|
|
||||||
return self::$no_results;
|
return self::$no_results;
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
return self::$no_results;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$terms = implode( ',', $terms );
|
$terms = implode( ',', $terms );
|
||||||
|
|
||||||
|
$i = count( $this->table_aliases );
|
||||||
$alias = $i ? 'tt' . $i : $wpdb->term_relationships;
|
$alias = $i ? 'tt' . $i : $wpdb->term_relationships;
|
||||||
|
$this->table_aliases[] = $alias;
|
||||||
|
|
||||||
$join .= " INNER JOIN $wpdb->term_relationships";
|
$join .= " INNER JOIN $wpdb->term_relationships";
|
||||||
$join .= $i ? " AS $alias" : '';
|
$join .= $i ? " AS $alias" : '';
|
||||||
$join .= " ON ($primary_table.$primary_id_column = $alias.object_id)";
|
$join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
|
||||||
|
|
||||||
|
$where = "$alias.term_taxonomy_id $operator ($terms)";
|
||||||
|
|
||||||
$where[] = "$alias.term_taxonomy_id $operator ($terms)";
|
|
||||||
} elseif ( 'NOT IN' == $operator ) {
|
} elseif ( 'NOT IN' == $operator ) {
|
||||||
|
|
||||||
if ( empty( $terms ) ) {
|
if ( empty( $terms ) ) {
|
||||||
|
@ -778,11 +1004,12 @@ class WP_Tax_Query {
|
||||||
|
|
||||||
$terms = implode( ',', $terms );
|
$terms = implode( ',', $terms );
|
||||||
|
|
||||||
$where[] = "$primary_table.$primary_id_column NOT IN (
|
$where = "$this->primary_table.$this->primary_id_column NOT IN (
|
||||||
SELECT object_id
|
SELECT object_id
|
||||||
FROM $wpdb->term_relationships
|
FROM $wpdb->term_relationships
|
||||||
WHERE term_taxonomy_id IN ($terms)
|
WHERE term_taxonomy_id IN ($terms)
|
||||||
)";
|
)";
|
||||||
|
|
||||||
} elseif ( 'AND' == $operator ) {
|
} elseif ( 'AND' == $operator ) {
|
||||||
|
|
||||||
if ( empty( $terms ) ) {
|
if ( empty( $terms ) ) {
|
||||||
|
@ -793,24 +1020,19 @@ class WP_Tax_Query {
|
||||||
|
|
||||||
$terms = implode( ',', $terms );
|
$terms = implode( ',', $terms );
|
||||||
|
|
||||||
$where[] = "(
|
$where = "(
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM $wpdb->term_relationships
|
FROM $wpdb->term_relationships
|
||||||
WHERE term_taxonomy_id IN ($terms)
|
WHERE term_taxonomy_id IN ($terms)
|
||||||
AND object_id = $primary_table.$primary_id_column
|
AND object_id = $this->primary_table.$this->primary_id_column
|
||||||
) = $num_terms";
|
) = $num_terms";
|
||||||
}
|
}
|
||||||
|
|
||||||
$i++;
|
$sql['join'][] = $join;
|
||||||
|
$sql['where'][] = $where;
|
||||||
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty( $where ) ) {
|
|
||||||
$where = ' AND ( ' . implode( " $this->relation ", $where ) . ' )';
|
|
||||||
} else {
|
|
||||||
$where = '';
|
|
||||||
}
|
|
||||||
return compact( 'join', 'where' );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a single query.
|
* Validates a single query.
|
||||||
|
@ -818,7 +1040,7 @@ class WP_Tax_Query {
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
* @access private
|
* @access private
|
||||||
*
|
*
|
||||||
* @param array &$query The single query
|
* @param array &$query The single query.
|
||||||
*/
|
*/
|
||||||
private function clean_query( &$query ) {
|
private function clean_query( &$query ) {
|
||||||
if ( empty( $query['taxonomy'] ) ) {
|
if ( empty( $query['taxonomy'] ) ) {
|
||||||
|
@ -858,8 +1080,9 @@ class WP_Tax_Query {
|
||||||
*
|
*
|
||||||
* @since 3.2.0
|
* @since 3.2.0
|
||||||
*
|
*
|
||||||
* @param array &$query The single query
|
* @param array &$query The single query.
|
||||||
* @param string $resulting_field The resulting field
|
* @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
|
||||||
|
* or 'term_id'. Default: 'term_id'.
|
||||||
*/
|
*/
|
||||||
public function transform_query( &$query, $resulting_field ) {
|
public function transform_query( &$query, $resulting_field ) {
|
||||||
global $wpdb;
|
global $wpdb;
|
||||||
|
|
Loading…
Reference in New Issue