WordPress.org

Make WordPress Core

Ticket #29738: 29738.2.patch

File 29738.2.patch, 26.2 KB (added by boonebgorges, 5 years ago)
  • src/wp-includes/query.php

    diff --git src/wp-includes/query.php src/wp-includes/query.php
    index a31bf87..54f3087 100644
    class WP_Query { 
    16721672                        $this->parse_tax_query( $qv );
    16731673
    16741674                        foreach ( $this->tax_query->queries as $tax_query ) {
    1675                                 if ( 'NOT IN' != $tax_query['operator'] ) {
     1675                                if ( ! is_array( $tax_query ) ) {
     1676                                        continue;
     1677                                }
     1678
     1679                                if ( isset( $tax_query['operator'] ) && 'NOT IN' != $tax_query['operator'] ) {
    16761680                                        switch ( $tax_query['taxonomy'] ) {
    16771681                                                case 'category':
    16781682                                                        $this->is_category = true;
    class WP_Query { 
    26872691                        if ( empty($post_type) ) {
    26882692                                // Do a fully inclusive search for currently registered post types of queried taxonomies
    26892693                                $post_type = array();
    2690                                 $taxonomies = wp_list_pluck( $this->tax_query->queries, 'taxonomy' );
     2694                                $taxonomies = array_keys( $this->tax_query->queried_terms );
    26912695                                foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) {
    26922696                                        $object_taxonomies = $pt === 'attachment' ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt );
    26932697                                        if ( array_intersect( $taxonomies, $object_taxonomies ) )
    class WP_Query { 
    27042708                        }
    27052709                }
    27062710
    2707                 // Back-compat
    2708                 if ( !empty($this->tax_query->queries) ) {
    2709                         $tax_query_in_and = wp_list_filter( $this->tax_query->queries, array( 'operator' => 'NOT IN' ), 'NOT' );
    2710                         if ( !empty( $tax_query_in_and ) ) {
    2711                                 if ( !isset( $q['taxonomy'] ) ) {
    2712                                         foreach ( $tax_query_in_and as $a_tax_query ) {
    2713                                                 if ( !in_array( $a_tax_query['taxonomy'], array( 'category', 'post_tag' ) ) ) {
    2714                                                         $q['taxonomy'] = $a_tax_query['taxonomy'];
    2715                                                         if ( 'slug' == $a_tax_query['field'] )
    2716                                                                 $q['term'] = $a_tax_query['terms'][0];
    2717                                                         else
    2718                                                                 $q['term_id'] = $a_tax_query['terms'][0];
     2711                /*
     2712                 * Ensure that 'taxonomy', 'term', 'term_id', 'cat', and
     2713                 * 'category_name' vars are set for backward compatibility.
     2714                 */
     2715                if ( ! empty( $this->tax_query->queried_terms ) ) {
    27192716
    2720                                                         break;
    2721                                                 }
     2717                        /*
     2718                         * Set 'taxonomy', 'term', and 'term_id' to the
     2719                         * first taxonomy other than 'post_tag' or 'category'.
     2720                         */
     2721                        if ( ! isset( $q['taxonomy'] ) ) {
     2722                                foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
     2723                                        if ( empty( $queried_items['terms'][0] ) ) {
     2724                                                continue;
    27222725                                        }
    2723                                 }
    27242726
    2725                                 $cat_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'category' ) );
    2726                                 if ( ! empty( $cat_query ) ) {
    2727                                         $cat_query = reset( $cat_query );
     2727                                        if ( ! in_array( $queried_taxonomy, array( 'category', 'post_tag' ) ) ) {
     2728                                                $q['taxonomy'] = $queried_taxonomy;
    27282729
    2729                                         if ( ! empty( $cat_query['terms'][0] ) ) {
    2730                                                 $the_cat = get_term_by( $cat_query['field'], $cat_query['terms'][0], 'category' );
    2731                                                 if ( $the_cat ) {
    2732                                                         $this->set( 'cat', $the_cat->term_id );
    2733                                                         $this->set( 'category_name', $the_cat->slug );
     2730                                                if ( 'slug' === $queried_items['field'] ) {
     2731                                                        $q['term'] = $queried_items['terms'][0];
     2732                                                } else {
     2733                                                        $q['term_id'] = $queried_items['terms'][0];
    27342734                                                }
    2735                                                 unset( $the_cat );
    27362735                                        }
    27372736                                }
    2738                                 unset( $cat_query );
     2737                        }
    27392738
    2740                                 $tag_query = wp_list_filter( $tax_query_in_and, array( 'taxonomy' => 'post_tag' ) );
    2741                                 if ( ! empty( $tag_query ) ) {
    2742                                         $tag_query = reset( $tag_query );
     2739                        // 'cat', 'category_name', 'tag_id'
     2740                        foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
     2741                                if ( empty( $queried_items['terms'][0] ) ) {
     2742                                        continue;
     2743                                }
     2744
     2745                                if ( 'category' === $queried_taxonomy ) {
     2746                                        $the_cat = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'category' );
     2747                                        if ( $the_cat ) {
     2748                                                $this->set( 'cat', $the_cat->term_id );
     2749                                                $this->set( 'category_name', $the_cat->slug );
     2750                                        }
     2751                                        unset( $the_cat );
     2752                                }
    27432753
    2744                                         if ( ! empty( $tag_query['terms'][0] ) ) {
    2745                                                 $the_tag = get_term_by( $tag_query['field'], $tag_query['terms'][0], 'post_tag' );
    2746                                                 if ( $the_tag )
    2747                                                         $this->set( 'tag_id', $the_tag->term_id );
    2748                                                 unset( $the_tag );
     2754                                if ( 'post_tag' === $queried_taxonomy ) {
     2755                                        $the_tag = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'post_tag' );
     2756                                        if ( $the_tag ) {
     2757                                                $this->set( 'tag_id', $the_tag->term_id );
    27492758                                        }
     2759                                        unset( $the_tag );
    27502760                                }
    2751                                 unset( $tag_query );
    27522761                        }
    27532762                }
    27542763
  • src/wp-includes/taxonomy.php

    diff --git src/wp-includes/taxonomy.php src/wp-includes/taxonomy.php
    index 013c3d3..1c01895 100644
    function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) { 
    627627}
    628628
    629629/**
    630  * Container class for a multiple taxonomy query.
     630 * Class for generating SQL clauses that filter a primary query according to object taxonomy terms.
     631 *
     632 * `WP_Tax_Query` is a helper that allows primary query classes, such as {@see WP_Query}, to filter
     633 * their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
     634 * to the primary SQL query string.
    631635 *
    632636 * @since 3.1.0
    633637 */
    634638class WP_Tax_Query {
    635639
    636640        /**
    637          * List of taxonomy queries. A single taxonomy query is an associative array:
    638          * - 'taxonomy' string The taxonomy being queried. Optional when using the term_taxonomy_id field.
    639          * - 'terms' string|array The list of terms
    640          * - 'field' string (optional) Which term field is being used.
    641          *              Possible values: 'term_id', 'slug', 'name', or 'term_taxonomy_id'
    642          *              Default: 'term_id'
    643          * - 'operator' string (optional)
    644          *              Possible values: 'AND', 'IN' or 'NOT IN'.
    645          *              Default: 'IN'
    646          * - 'include_children' bool (optional) Whether to include child terms. Requires that a taxonomy be specified.
    647          *              Default: true
     641         * Array of taxonomy queries.
     642         *
     643         * See {@see WP_Tax_Query::__construct()} for information on tax query arguments.
    648644         *
    649645         * @since 3.1.0
    650646         * @access public
    class WP_Tax_Query { 
    668664         * @access private
    669665         * @var string
    670666         */
    671         private static $no_results = array( 'join' => '', 'where' => ' AND 0 = 1' );
     667        private static $no_results = array( 'join' => array( '' ), 'where' => array( '0 = 1' ) );
    672668
    673669        /**
    674          * Constructor.
     670         * A flat list of table aliases used in the JOIN clauses.
     671         *
     672         * @since 4.1.0
     673         * @access protected
     674         * @var array
     675         */
     676        protected $table_aliases = array();
     677
     678        /**
     679         * Terms and taxonomies fetched by this query.
    675680         *
    676          * Parses a compact tax query and sets defaults.
     681         * We store this data in a flat array because they are referenced in a
     682         * number of places by WP_Query.
     683         *
     684         * @since 4.1.0
     685         * @access public
     686         * @var array
     687         */
     688        public $queried_terms = array();
     689
     690        /**
     691         * Constructor.
    677692         *
    678693         * @since 3.1.0
    679694         * @access public
    680695         *
    681          * @param array $tax_query A compact tax query:
    682          *  array(
    683          *    'relation' => 'OR',
    684          *    array(
    685          *      'taxonomy' => 'tax1',
    686          *      'terms' => array( 'term1', 'term2' ),
    687          *      'field' => 'slug',
    688          *    ),
    689          *    array(
    690          *      'taxonomy' => 'tax2',
    691          *      'terms' => array( 'term-a', 'term-b' ),
    692          *      'field' => 'slug',
    693          *    ),
    694          *  )
     696         * @param array $tax_query {
     697         *     Array of taxonoy query clauses.
     698         *
     699         *     @type string $relation Optional. The MySQL keyword used to join
     700         *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
     701         *     @type array {
     702         *         Optional. An array of first-order clause parameters, or another fully-formed tax query.
     703         *
     704         *         @type string           $taxonomy         Taxonomy being queried. Optional when field=term_taxonomy_id.
     705         *         @type string|int|array $terms            Term or terms to filter by.
     706         *         @type string           $field            Field to match $terms against. Accepts 'term_id', 'slug',
     707         *                                                 'name', or 'term_taxonomy_id'. Default: 'term_id'.
     708         *         @type string           $operator         MySQL operator to be used with $terms in the WHERE clause.
     709         *                                                  Accepts 'AND', 'IN', or 'OR. Default: 'IN'.
     710         *         @type bool             $include_children Optional. Whether to include child terms.
     711         *                                                  Requires a $taxonomy. Default: true.
     712         *     }
     713         * }
    695714         */
    696715        public function __construct( $tax_query ) {
    697716                if ( isset( $tax_query['relation'] ) && strtoupper( $tax_query['relation'] ) == 'OR' ) {
    class WP_Tax_Query { 
    700719                        $this->relation = 'AND';
    701720                }
    702721
     722                $this->queries = $this->sanitize_query( $tax_query );
     723        }
     724
     725        /**
     726         * Ensure the `tax_query` argument passed to the class constructor is well-formed.
     727         *
     728         * Ensures that each query-level clause has a 'relation' key, and that
     729         * each first-order clause contains all the necessary keys from $defaults.
     730         *
     731         * @since 4.1.0
     732         * @access public
     733         *
     734         * @param  array $queries Array of queries clauses.
     735         * @return array Sanitized array of query clauses.
     736         */
     737        public function sanitize_query( $queries ) {
     738                $cleaned_query = array();
     739
    703740                $defaults = array(
    704741                        'taxonomy' => '',
    705742                        'terms' => array(),
    706                         'include_children' => true,
    707743                        'field' => 'term_id',
    708744                        'operator' => 'IN',
     745                        'include_children' => true,
    709746                );
    710747
    711                 foreach ( $tax_query as $query ) {
    712                         if ( ! is_array( $query ) )
    713                                 continue;
     748                foreach ( $queries as $key => $query ) {
     749                        if ( 'relation' === $key ) {
     750                                $cleaned_query['relation'] = $query;
     751
     752                        // First-order clause.
     753                        } else if ( self::is_first_order_clause( $query ) ) {
     754
     755                                $cleaned_clause = array_merge( $defaults, $query );
     756                                $cleaned_clause['terms'] = (array) $cleaned_clause['terms'];
     757                                $cleaned_query[] = $cleaned_clause;
     758
     759                                /*
     760                                 * Keep a copy of the clause in the flate
     761                                 * $queried_terms array, for use in WP_Query.
     762                                 */
     763                                if ( ! empty( $cleaned_clause['taxonomy'] ) && 'NOT IN' !== $cleaned_clause['operator'] ) {
     764                                        $taxonomy = $cleaned_clause['taxonomy'];
     765                                        if ( ! isset( $this->queried_terms[ $taxonomy ] ) ) {
     766                                                $this->queried_terms[ $taxonomy ] = array();
     767                                        }
    714768
    715                         $query = array_merge( $defaults, $query );
     769                                        /*
     770                                         * Backward compatibility: Only store the first
     771                                         * 'terms' and 'field' found for a given taxonomy.
     772                                         */
     773                                        if ( isset( $cleaned_clause['terms'] ) && ! isset( $this->queried_terms[ $taxonomy ]['terms'] ) ) {
     774                                                $this->queried_terms[ $taxonomy ]['terms'] = $cleaned_clause['terms'];
     775                                        }
     776
     777                                        if ( isset( $cleaned_clause['field'] ) && ! isset( $this->queried_terms[ $taxonomy ]['field'] ) ) {
     778                                                $this->queried_terms[ $taxonomy ]['field'] = $cleaned_clause['field'];
     779                                        }
     780                                }
    716781
    717                         $query['terms'] = (array) $query['terms'];
     782                        // Otherwise, it's a nested query, so we recurse.
     783                        } else if ( is_array( $query ) ) {
     784                                $cleaned_subquery = $this->sanitize_query( $query );
    718785
    719                         $this->queries[] = $query;
     786                                if ( ! empty( $cleaned_subquery ) ) {
     787                                        $cleaned_query[] = $cleaned_subquery;
     788                                }
     789                        }
    720790                }
     791
     792                return $cleaned_query;
     793        }
     794
     795        /**
     796         * Determine whether a clause is first-order.
     797         *
     798         * A "first-order" clause is one that contains any of the first-order
     799         * clause keys ('terms', 'taxonomy', 'include_children', 'field',
     800         * 'operator'). An empty clause also counts as a first-order clause,
     801         * for backward compatibility. Any clause that doesn't meet this is
     802         * determined, by process of elimination, to be a higher-order query.
     803         *
     804         * @since 4.1.0
     805         * @access protected
     806         *
     807         * @param  array $query Tax query arguments.
     808         * @return bool  Whether the query clause is a first-order clause.
     809         */
     810        protected static function is_first_order_clause( $query ) {
     811                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 );
    721812        }
    722813
    723814        /**
    class WP_Tax_Query { 
    726817         * @since 3.1.0
    727818         * @access public
    728819         *
    729          * @param string $primary_table
    730          * @param string $primary_id_column
    731          * @return array
     820         * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
     821         * @param string $primary_id_column ID column for the filtered object in $primary_table.
     822         * @return array {
     823         *     Array containing JOIN and WHERE SQL clauses to append to the main query.
     824         *
     825         *     @type string $join  SQL fragment to append to the main JOIN clause.
     826         *     @type string $where SQL fragment to append to the main WHERE clause.
     827         * }
    732828         */
    733829        public function get_sql( $primary_table, $primary_id_column ) {
    734                 global $wpdb;
     830                $this->primary_table = $primary_table;
     831                $this->primary_id_column = $primary_id_column;
    735832
    736                 $join = '';
    737                 $where = array();
    738                 $i = 0;
    739                 $count = count( $this->queries );
     833                return $this->get_sql_clauses();
     834        }
    740835
    741                 foreach ( $this->queries as $index => $query ) {
    742                         $this->clean_query( $query );
     836        /**
     837         * Generate SQL clauses to be appended to a main query.
     838         *
     839         * Called by the public {@see WP_Tax_Query::get_sql()}, this method
     840         * is abstracted out to maintain parity with the other Query classes.
     841         *
     842         * @since 4.1.0
     843         * @access protected
     844         *
     845         * @return array {
     846         *     Array containing JOIN and WHERE SQL clauses to append to the main query.
     847         *
     848         *     @type string $join  SQL fragment to append to the main JOIN clause.
     849         *     @type string $where SQL fragment to append to the main WHERE clause.
     850         * }
     851         */
     852        protected function get_sql_clauses() {
     853                $sql = $this->get_sql_for_query( $this->queries );
    743854
    744                         if ( is_wp_error( $query ) ) {
    745                                 return self::$no_results;
    746                         }
     855                if ( ! empty( $sql['where'] ) ) {
     856                        $sql['where'] = ' AND ' . $sql['where'];
     857                }
    747858
    748                         $terms = $query['terms'];
    749                         $operator = strtoupper( $query['operator'] );
     859                return $sql;
     860        }
    750861
    751                         if ( 'IN' == $operator ) {
     862        /**
     863         * Generate SQL clauses for a single query array.
     864         *
     865         * If nested subqueries are found, this method recurses the tree to
     866         * produce the properly nested SQL.
     867         *
     868         * @since 4.1.0
     869         * @access protected
     870         *
     871         * @param array $query Query to parse.
     872         * @param int   $depth Optional. Number of tree levels deep we currently are.
     873         *              Used to calculate indentation.
     874         * @return array {
     875         *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
     876         *
     877         *     @type string $join  SQL fragment to append to the main JOIN clause.
     878         *     @type string $where SQL fragment to append to the main WHERE clause.
     879         * }
     880         */
     881        protected function get_sql_for_query( $query, $depth = 0 ) {
     882                $sql_chunks = array(
     883                        'join'  => array(),
     884                        'where' => array(),
     885                );
    752886
    753                                 if ( empty( $terms ) ) {
    754                                         if ( 'OR' == $this->relation ) {
    755                                                 if ( ( $index + 1 === $count ) && empty( $where ) ) {
    756                                                         return self::$no_results;
    757                                                 }
    758                                                 continue;
     887                $sql = array(
     888                        'join'  => '',
     889                        'where' => '',
     890                );
     891
     892                $indent = '';
     893                for ( $i = 0; $i < $depth; $i++ ) {
     894                        $indent .= "  ";
     895                }
     896
     897                foreach ( $query as $key => $clause ) {
     898                        if ( 'relation' === $key ) {
     899                                $relation = $query['relation'];
     900                        } else if ( is_array( $clause ) ) {
     901
     902                                // This is a first-order clause.
     903                                if ( $this->is_first_order_clause( $clause ) ) {
     904                                        $clause_sql = $this->get_sql_for_clause( $clause, $query );
     905
     906                                        $where_count = count( $clause_sql['where'] );
     907                                        if ( ! $where_count ) {
     908                                                $sql_chunks['where'][] = '';
     909                                        } else if ( 1 === $where_count ) {
     910                                                $sql_chunks['where'][] = $clause_sql['where'][0];
    759911                                        } else {
    760                                                 return self::$no_results;
     912                                                $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
    761913                                        }
     914
     915                                        $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
     916                                // This is a subquery, so we recurse.
     917                                } else {
     918                                        $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
     919
     920                                        $sql_chunks['where'][] = $clause_sql['where'];
     921                                        $sql_chunks['join'][]  = $clause_sql['join'];
    762922                                }
     923                        }
     924                }
    763925
    764                                 $terms = implode( ',', $terms );
     926                // Filter to remove empties.
     927                $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
     928                $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
    765929
    766                                 $alias = $i ? 'tt' . $i : $wpdb->term_relationships;
     930                if ( empty( $relation ) ) {
     931                        $relation = 'AND';
     932                }
    767933
    768                                 $join .= " INNER JOIN $wpdb->term_relationships";
    769                                 $join .= $i ? " AS $alias" : '';
    770                                 $join .= " ON ($primary_table.$primary_id_column = $alias.object_id)";
     934                // Filter duplicate JOIN clauses and combine into a single string.
     935                if ( ! empty( $sql_chunks['join'] ) ) {
     936                        $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
     937                }
    771938
    772                                 $where[] = "$alias.term_taxonomy_id $operator ($terms)";
    773                         } elseif ( 'NOT IN' == $operator ) {
     939                // Generate a single WHERE clause with proper brackets and indentation.
     940                if ( ! empty( $sql_chunks['where'] ) ) {
     941                        $sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
     942                }
    774943
    775                                 if ( empty( $terms ) ) {
    776                                         continue;
    777                                 }
     944                return $sql;
     945        }
    778946
    779                                 $terms = implode( ',', $terms );
     947        /**
     948         * Generate SQL JOIN and WHERE clauses for a first-order query clause.
    780949
    781                                 $where[] = "$primary_table.$primary_id_column NOT IN (
    782                                         SELECT object_id
    783                                         FROM $wpdb->term_relationships
    784                                         WHERE term_taxonomy_id IN ($terms)
    785                                 )";
    786                         } elseif ( 'AND' == $operator ) {
     950         * @since 4.1.0
     951         * @access public
     952         *
     953         * @param  array $clause       Query clause.
     954         * @param  array $parent_query Parent query array.
     955         * @return array {
     956         *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
     957         *
     958         *     @type string $join  SQL fragment to append to the main JOIN clause.
     959         *     @type string $where SQL fragment to append to the main WHERE clause.
     960         * }
     961         */
     962        public function get_sql_for_clause( $clause, $parent_query ) {
     963                global $wpdb;
    787964
    788                                 if ( empty( $terms ) ) {
    789                                         continue;
    790                                 }
     965                $sql = array(
     966                        'where' => array(),
     967                        'join'  => array(),
     968                );
     969
     970                $join = '';
     971
     972                $this->clean_query( $clause );
    791973
    792                                 $num_terms = count( $terms );
     974                if ( is_wp_error( $clause ) ) {
     975                        return self::$no_results;
     976                }
    793977
    794                                 $terms = implode( ',', $terms );
     978                $terms = $clause['terms'];
     979                $operator = strtoupper( $clause['operator'] );
    795980
    796                                 $where[] = "(
    797                                         SELECT COUNT(1)
    798                                         FROM $wpdb->term_relationships
    799                                         WHERE term_taxonomy_id IN ($terms)
    800                                         AND object_id = $primary_table.$primary_id_column
    801                                 ) = $num_terms";
     981                if ( 'IN' == $operator ) {
     982
     983                        if ( empty( $terms ) ) {
     984                                return self::$no_results;
    802985                        }
    803986
    804                         $i++;
    805                 }
     987                        $terms = implode( ',', $terms );
    806988
    807                 if ( ! empty( $where ) ) {
    808                         $where = ' AND ( ' . implode( " $this->relation ", $where ) . ' )';
    809                 } else {
    810                         $where = '';
     989                        $i = count( $this->table_aliases );
     990                        $alias = $i ? 'tt' . $i : $wpdb->term_relationships;
     991                        $this->table_aliases[] = $alias;
     992
     993                        $join .= " INNER JOIN $wpdb->term_relationships";
     994                        $join .= $i ? " AS $alias" : '';
     995                        $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
     996
     997                        $where = "$alias.term_taxonomy_id $operator ($terms)";
     998
     999                } elseif ( 'NOT IN' == $operator ) {
     1000
     1001                        if ( empty( $terms ) ) {
     1002                                continue;
     1003                        }
     1004
     1005                        $terms = implode( ',', $terms );
     1006
     1007                        $where = "$this->primary_table.$this->primary_id_column NOT IN (
     1008                                SELECT object_id
     1009                                FROM $wpdb->term_relationships
     1010                                WHERE term_taxonomy_id IN ($terms)
     1011                        )";
     1012
     1013                } elseif ( 'AND' == $operator ) {
     1014
     1015                        if ( empty( $terms ) ) {
     1016                                continue;
     1017                        }
     1018
     1019                        $num_terms = count( $terms );
     1020
     1021                        $terms = implode( ',', $terms );
     1022
     1023                        $where = "(
     1024                                SELECT COUNT(1)
     1025                                FROM $wpdb->term_relationships
     1026                                WHERE term_taxonomy_id IN ($terms)
     1027                                AND object_id = $this->primary_table.$this->primary_id_column
     1028                        ) = $num_terms";
    8111029                }
    812                 return compact( 'join', 'where' );
     1030
     1031                $sql['join'][]  = $join;
     1032                $sql['where'][] = $where;
     1033                return $sql;
    8131034        }
    8141035
     1036
    8151037        /**
    8161038         * Validates a single query.
    8171039         *
    8181040         * @since 3.2.0
    8191041         * @access private
    8201042         *
    821          * @param array &$query The single query
     1043         * @param array &$query The single query.
    8221044         */
    8231045        private function clean_query( &$query ) {
    8241046                if ( empty( $query['taxonomy'] ) ) {
    class WP_Tax_Query { 
    8581080         *
    8591081         * @since 3.2.0
    8601082         *
    861          * @param array &$query The single query
    862          * @param string $resulting_field The resulting field
     1083         * @param array  &$query          The single query.
     1084         * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
     1085         *                                or 'term_id'. Default: 'term_id'.
    8631086         */
    8641087        public function transform_query( &$query, $resulting_field ) {
    8651088                global $wpdb;
  • tests/phpunit/tests/post/query.php

    diff --git tests/phpunit/tests/post/query.php tests/phpunit/tests/post/query.php
    index ad36158..bf9a4e6 100644
    class Tests_Post_Query extends WP_UnitTestCase { 
    15061506        }
    15071507
    15081508        /**
     1509         * @ticket 29738
     1510         */
     1511        public function test_two_nested_queries() {
     1512                register_taxonomy( 'foo', 'post' );
     1513                register_taxonomy( 'bar', 'post' );
     1514
     1515                $foo_term_1 = $this->factory->term->create( array(
     1516                        'taxonomy' => 'foo',
     1517                ) );
     1518                $foo_term_2 = $this->factory->term->create( array(
     1519                        'taxonomy' => 'foo',
     1520                ) );
     1521                $bar_term_1 = $this->factory->term->create( array(
     1522                        'taxonomy' => 'bar',
     1523                ) );
     1524                $bar_term_2 = $this->factory->term->create( array(
     1525                        'taxonomy' => 'bar',
     1526                ) );
     1527
     1528                $p1 = $this->factory->post->create();
     1529                $p2 = $this->factory->post->create();
     1530                $p3 = $this->factory->post->create();
     1531
     1532                wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' );
     1533                wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' );
     1534                wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' );
     1535                wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' );
     1536                wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' );
     1537                wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' );
     1538
     1539                $q = new WP_Query( array(
     1540                        'fields' => 'ids',
     1541                        'update_post_meta_cache' => false,
     1542                        'update_post_term_cache' => false,
     1543                        'tax_query' => array(
     1544                                'relation' => 'OR',
     1545                                array(
     1546                                        'relation' => 'AND',
     1547                                        array(
     1548                                                'taxonomy' => 'foo',
     1549                                                'terms' => array( $foo_term_1 ),
     1550                                                'field' => 'term_id',
     1551                                        ),
     1552                                        array(
     1553                                                'taxonomy' => 'bar',
     1554                                                'terms' => array( $bar_term_1 ),
     1555                                                'field' => 'term_id',
     1556                                        ),
     1557                                ),
     1558                                array(
     1559                                        'relation' => 'AND',
     1560                                        array(
     1561                                                'taxonomy' => 'foo',
     1562                                                'terms' => array( $foo_term_2 ),
     1563                                                'field' => 'term_id',
     1564                                        ),
     1565                                        array(
     1566                                                'taxonomy' => 'bar',
     1567                                                'terms' => array( $bar_term_2 ),
     1568                                                'field' => 'term_id',
     1569                                        ),
     1570                                ),
     1571                        ),
     1572                ) );
     1573
     1574                _unregister_taxonomy( 'foo' );
     1575                _unregister_taxonomy( 'bar' );
     1576
     1577                $this->assertEqualSets( array( $p1, $p2 ), $q->posts );
     1578        }
     1579
     1580        /**
     1581         * @ticket 29738
     1582         */
     1583        public function test_one_nested_query_one_first_order_query() {
     1584                register_taxonomy( 'foo', 'post' );
     1585                register_taxonomy( 'bar', 'post' );
     1586
     1587                $foo_term_1 = $this->factory->term->create( array(
     1588                        'taxonomy' => 'foo',
     1589                ) );
     1590                $foo_term_2 = $this->factory->term->create( array(
     1591                        'taxonomy' => 'foo',
     1592                ) );
     1593                $bar_term_1 = $this->factory->term->create( array(
     1594                        'taxonomy' => 'bar',
     1595                ) );
     1596                $bar_term_2 = $this->factory->term->create( array(
     1597                        'taxonomy' => 'bar',
     1598                ) );
     1599
     1600                $p1 = $this->factory->post->create();
     1601                $p2 = $this->factory->post->create();
     1602                $p3 = $this->factory->post->create();
     1603
     1604                wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' );
     1605                wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' );
     1606                wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' );
     1607                wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' );
     1608                wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' );
     1609                wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' );
     1610
     1611                $q = new WP_Query( array(
     1612                        'fields' => 'ids',
     1613                        'update_post_meta_cache' => false,
     1614                        'update_post_term_cache' => false,
     1615                        'tax_query' => array(
     1616                                'relation' => 'OR',
     1617                                array(
     1618                                        'taxonomy' => 'foo',
     1619                                        'terms' => array( $foo_term_2 ),
     1620                                        'field' => 'term_id',
     1621                                ),
     1622                                array(
     1623                                        'relation' => 'AND',
     1624                                        array(
     1625                                                'taxonomy' => 'foo',
     1626                                                'terms' => array( $foo_term_1 ),
     1627                                                'field' => 'term_id',
     1628                                        ),
     1629                                        array(
     1630                                                'taxonomy' => 'bar',
     1631                                                'terms' => array( $bar_term_1 ),
     1632                                                'field' => 'term_id',
     1633                                        ),
     1634                                ),
     1635                        ),
     1636                ) );
     1637
     1638                _unregister_taxonomy( 'foo' );
     1639                _unregister_taxonomy( 'bar' );
     1640
     1641                $this->assertEqualSets( array( $p1, $p2 ), $q->posts );
     1642        }
     1643
     1644        /**
     1645         * @ticket 29738
     1646         */
     1647        public function test_one_double_nested_query_one_first_order_query() {
     1648                register_taxonomy( 'foo', 'post' );
     1649                register_taxonomy( 'bar', 'post' );
     1650
     1651                $foo_term_1 = $this->factory->term->create( array(
     1652                        'taxonomy' => 'foo',
     1653                ) );
     1654                $foo_term_2 = $this->factory->term->create( array(
     1655                        'taxonomy' => 'foo',
     1656                ) );
     1657                $bar_term_1 = $this->factory->term->create( array(
     1658                        'taxonomy' => 'bar',
     1659                ) );
     1660                $bar_term_2 = $this->factory->term->create( array(
     1661                        'taxonomy' => 'bar',
     1662                ) );
     1663
     1664                $p1 = $this->factory->post->create();
     1665                $p2 = $this->factory->post->create();
     1666                $p3 = $this->factory->post->create();
     1667                $p4 = $this->factory->post->create();
     1668
     1669                wp_set_object_terms( $p1, array( $foo_term_1 ), 'foo' );
     1670                wp_set_object_terms( $p1, array( $bar_term_1 ), 'bar' );
     1671                wp_set_object_terms( $p2, array( $foo_term_2 ), 'foo' );
     1672                wp_set_object_terms( $p2, array( $bar_term_2 ), 'bar' );
     1673                wp_set_object_terms( $p3, array( $foo_term_1 ), 'foo' );
     1674                wp_set_object_terms( $p3, array( $bar_term_2 ), 'bar' );
     1675
     1676                $q = new WP_Query( array(
     1677                        'fields' => 'ids',
     1678                        'update_post_meta_cache' => false,
     1679                        'update_post_term_cache' => false,
     1680                        'tax_query' => array(
     1681                                'relation' => 'OR',
     1682                                array(
     1683                                        'taxonomy' => 'foo',
     1684                                        'terms' => array( $foo_term_2 ),
     1685                                        'field' => 'term_id',
     1686                                ),
     1687                                array(
     1688                                        'relation' => 'AND',
     1689                                        array(
     1690                                                'taxonomy' => 'foo',
     1691                                                'terms' => array( $foo_term_1 ),
     1692                                                'field' => 'term_id',
     1693                                        ),
     1694                                        array(
     1695                                                'relation' => 'OR',
     1696                                                array(
     1697                                                        'taxonomy' => 'bar',
     1698                                                        'terms' => array( $bar_term_1 ),
     1699                                                        'field' => 'term_id',
     1700                                                ),
     1701                                                array(
     1702                                                        'taxonomy' => 'bar',
     1703                                                        'terms' => array( $bar_term_2 ),
     1704                                                        'field' => 'term_id',
     1705                                                ),
     1706                                        ),
     1707                                ),
     1708                        ),
     1709                ) );
     1710
     1711                _unregister_taxonomy( 'foo' );
     1712                _unregister_taxonomy( 'bar' );
     1713
     1714                $this->assertEqualSets( array( $p1, $p2, $p3 ), $q->posts );
     1715        }
     1716
     1717        /**
    15091718         * @ticket 20604
    15101719         * @group taxonomy
    15111720         */