<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><style type="text/css"><!--
#msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; }
#msg ul, pre { overflow: auto; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<title>[15731] trunk/wp-includes: Generalize taxonomy queries:</title>
</head>
<body>

<div id="msg">
<dl>
<dt>Revision</dt> <dd><a href="http://trac.wordpress.org/changeset/15731">15731</a></dd>
<dt>Author</dt> <dd>scribu</dd>
<dt>Date</dt> <dd>2010-10-06 10:40:30 +0000 (Wed, 06 Oct 2010)</dd>
</dl>

<h3>Log Message</h3>
<pre>Generalize taxonomy queries:
        * transform wp_tax_query() into WP_Object_Query::get_tax_sql()
        * create parse_tax_query() method in WP_Query
        * add doc-block for $tax_query and $meta_query
See <a href="http://trac.wordpress.org/ticket/15032">#15032</a>. See <a href="http://trac.wordpress.org/ticket/12891">#12891</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkwpincludesclassesphp">trunk/wp-includes/classes.php</a></li>
<li><a href="#trunkwpincludesqueryphp">trunk/wp-includes/query.php</a></li>
<li><a href="#trunkwpincludestaxonomyphp">trunk/wp-includes/taxonomy.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkwpincludesclassesphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/classes.php (15730 => 15731)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/classes.php        2010-10-05 23:06:20 UTC (rev 15730)
+++ trunk/wp-includes/classes.php        2010-10-06 10:40:30 UTC (rev 15731)
</span><span class="lines">@@ -535,8 +535,18 @@
</span><span class="cx"> class WP_Object_Query {
</span><span class="cx"> 
</span><span class="cx">         /**
</span><del>-         * Metadata query
</del><ins>+         * List of metadata queries
</ins><span class="cx">          *
</span><ins>+         * A query is an associative array:
+         * - 'key' string The meta key
+         * - 'value' string|array The meta value
+         * - 'compare' (optional) string How to compare the key to the value. 
+         *                Possible values: '=', '!=', '&gt;', '&gt;=', '&lt;', '&lt;=', 'LIKE', 'IN', 'BETWEEN'. 
+         *                Default: '='
+         * - 'type' string (optional) The type of the value.
+         *                Possible values: 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'. 
+         *                Default: 'CHAR'
+         *
</ins><span class="cx">          * @since 3.1.0
</span><span class="cx">          * @access public
</span><span class="cx">          * @var array
</span><span class="lines">@@ -544,6 +554,27 @@
</span><span class="cx">         var $meta_query = array();
</span><span class="cx"> 
</span><span class="cx">         /*
</span><ins>+         * List of taxonomy queries
+         *
+         * A query is an associative array:
+         * - 'taxonomy' string|array The taxonomy being queried
+         * - 'terms' string|array The list of terms
+         * - 'field' string (optional) Which term field is being used.
+         *                Possible values: 'term_id', 'slug' or 'name'
+         *                Default: 'slug'
+         * - 'operator' string (optional)
+         *                Possible values: 'IN' and 'NOT IN'. 
+         *                Default: 'IN'
+         * - 'include_children' bool (optional) Wether to include child terms. 
+         *                Default: true
+         *
+         * @since 3.1.0
+         * @access public
+         * @var array
+         */
+        var $tax_query = array();
+
+        /*
</ins><span class="cx">          * Populates the $meta_query property
</span><span class="cx">          *
</span><span class="cx">          * @access protected
</span><span class="lines">@@ -573,6 +604,8 @@
</span><span class="cx">          * @access protected
</span><span class="cx">          * @since 3.1.0
</span><span class="cx">          *
</span><ins>+         * @uses $this-&gt;meta_query
+         *
</ins><span class="cx">          * @param string $primary_table
</span><span class="cx">          * @param string $primary_id_column
</span><span class="cx">          * @param string $meta_table
</span><span class="lines">@@ -646,6 +679,44 @@
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         /*
</span><ins>+         * Used internally to generate an SQL string for searching across multiple taxonomies
+         *
+         * @access protected
+         * @since 3.1.0
+         *
+         * @uses $this-&gt;tax_query
+         *
+         * @param string $object_id_column
+         * @return string
+         */
+        function get_tax_sql( $object_id_column ) {
+                global $wpdb;
+
+                $sql = array();
+                foreach ( $this-&gt;tax_query as $query ) {
+                        if ( !isset( $query['include_children'] ) )
+                                $query['include_children'] = true;
+                        $query['do_query'] = false;
+                        $sql[] = get_objects_in_term( $query['terms'], $query['taxonomy'], $query );
+                }
+
+                if ( 1 == count( $sql ) ) {
+                        $ids = $wpdb-&gt;get_col( $sql[0] );
+                } else {
+                        $r = &quot;SELECT object_id FROM $wpdb-&gt;term_relationships WHERE 1=1&quot;;
+                        foreach ( $sql as $query )
+                                $r .= &quot; AND object_id IN ($query)&quot;;
+
+                        $ids = $wpdb-&gt;get_col( $r );
+                }
+
+                if ( !empty( $ids ) )
+                        return &quot; AND $object_id_column IN(&quot; . implode( ', ', $ids ) . &quot;)&quot;;
+                else
+                        return ' AND 0 = 1';
+        }
+
+        /*
</ins><span class="cx">          * Used internally to generate an SQL string for searching across multiple columns
</span><span class="cx">          *
</span><span class="cx">          * @access protected
</span></span></pre></div>
<a id="trunkwpincludesqueryphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/query.php (15730 => 15731)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/query.php        2010-10-05 23:06:20 UTC (rev 15730)
+++ trunk/wp-includes/query.php        2010-10-06 10:40:30 UTC (rev 15731)
</span><span class="lines">@@ -662,15 +662,6 @@
</span><span class="cx">         var $query_vars = array();
</span><span class="cx"> 
</span><span class="cx">         /**
</span><del>-         * Taxonomy query, after parsing
-         *
-         * @since 3.1.0
-         * @access public
-         * @var array
-         */
-        var $tax_query = array();
-
-        /**
</del><span class="cx">          * Holds the data for a single object that is queried.
</span><span class="cx">          *
</span><span class="cx">          * Holds the contents of a post, page, category, attachment.
</span><span class="lines">@@ -1382,6 +1373,8 @@
</span><span class="cx">                                 $this-&gt;is_tax = true;
</span><span class="cx">                         }
</span><span class="cx"> 
</span><ins>+                        $this-&gt;parse_tax_query( $qv );
+
</ins><span class="cx">                         $this-&gt;parse_meta_query( $qv );
</span><span class="cx"> 
</span><span class="cx">                         if ( empty($qv['author']) || ($qv['author'] == '0') ) {
</span><span class="lines">@@ -1490,6 +1483,119 @@
</span><span class="cx">                         do_action_ref_array('parse_query', array(&amp;$this));
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        function parse_tax_query( $q ) {
+                $tax_query = array();
+
+                if ( $this-&gt;is_tax ) {
+                        foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy =&gt; $t ) {
+                                if ( $t-&gt;query_var &amp;&amp; !empty( $q[$t-&gt;query_var] ) ) {
+                                        $tax_query_defaults = array(
+                                                'taxonomy' =&gt; $taxonomy,
+                                                'field' =&gt; 'slug',
+                                                'operator' =&gt; 'IN'
+                                        );
+
+                                        $term = str_replace( ' ', '+', $q[$t-&gt;query_var] );
+
+                                        if ( strpos($term, '+') !== false ) {
+                                                $terms = preg_split( '/[+\s]+/', $term );
+                                                foreach ( $terms as $term ) {
+                                                        $tax_query[] = array_merge( $tax_query_defaults, array(
+                                                                'terms' =&gt; array( $term )
+                                                        ) );
+                                                }
+                                        } else {
+                                                $tax_query[] = array_merge( $tax_query_defaults, array(
+                                                        'terms' =&gt; preg_split('/[,\s]+/', $term)
+                                                ) );
+                                        }
+                                }
+                        }
+                }
+
+                // Category stuff
+                if ( !empty($q['cat']) &amp;&amp; '0' != $q['cat'] &amp;&amp; !$this-&gt;is_singular ) {
+                        $q['cat'] = ''.urldecode($q['cat']).'';
+                        $q['cat'] = addslashes_gpc($q['cat']);
+                        $cat_array = preg_split('/[,\s]+/', $q['cat']);
+                        $q['cat'] = '';
+                        $req_cats = array();
+                        foreach ( (array) $cat_array as $cat ) {
+                                $cat = intval($cat);
+                                $req_cats[] = $cat;
+                                $in = ($cat &gt; 0);
+                                $cat = abs($cat);
+                                if ( $in ) {
+                                        $q['category__in'][] = $cat;
+                                } else {
+                                        $q['category__not_in'][] = $cat;
+                                }
+                        }
+                        $q['cat'] = implode(',', $req_cats);
+                }
+
+                if ( !empty($q['category__in']) ) {
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'category',
+                                'terms' =&gt; $q['category__in'],
+                                'operator' =&gt; 'IN',
+                                'field' =&gt; 'term_id'
+                        );
+                }
+
+                if ( !empty($q['category__not_in']) ) {
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'category',
+                                'terms' =&gt; $q['category__not_in'],
+                                'operator' =&gt; 'NOT IN',
+                                'field' =&gt; 'term_id'
+                        );
+                }
+
+                // Category stuff for nice URLs
+                if ( '' != $q['category_name'] &amp;&amp; !$this-&gt;is_singular ) {
+                        $q['category_name'] = str_replace( '%2F', '/', urlencode(urldecode($q['category_name'])) );
+                        $q['category_name'] = '/' . trim( $q['category_name'], '/' );
+
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'category',
+                                'terms' =&gt; array( basename( $q['category_name'] ) ),
+                                'operator' =&gt; 'IN',
+                                'field' =&gt; 'slug'
+                        );
+                }
+
+                // Tag stuff
+                if ( !empty($qv['tag_id']) ) {
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'post_tag',
+                                'terms' =&gt; $qv['tag_id'],
+                                'operator' =&gt; 'IN',
+                                'field' =&gt; 'term_id'
+                        );
+                }
+
+                if ( !empty($q['tag__in']) ) {
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'post_tag',
+                                'terms' =&gt; $q['tag__in'],
+                                'operator' =&gt; 'IN',
+                                'field' =&gt; 'term_id'
+                        );
+                }
+
+                if ( !empty($q['tag__not_in']) ) {
+                        $tax_query[] = array(
+                                'taxonomy' =&gt; 'post_tag',
+                                'terms' =&gt; $q['tag__not_in'],
+                                'operator' =&gt; 'NOT IN',
+                                'field' =&gt; 'term_id'
+                        );
+                }
+
+                $this-&gt;tax_query = $tax_query;
+        }
+
</ins><span class="cx">         /**
</span><span class="cx">          * Sets the 404 property and saves whether query is feed.
</span><span class="cx">          *
</span><span class="lines">@@ -1815,118 +1921,7 @@
</span><span class="cx">                 $search = apply_filters_ref_array('posts_search', array( $search, &amp;$this ) );
</span><span class="cx"> 
</span><span class="cx">                 // Taxonomies
</span><del>-                $tax_query = array();
-
-                if ( $this-&gt;is_tax ) {
-                        foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy =&gt; $t ) {
-                                if ( $t-&gt;query_var &amp;&amp; !empty( $q[$t-&gt;query_var] ) ) {
-                                        $tax_query_defaults = array(
-                                                'taxonomy' =&gt; $taxonomy,
-                                                'field' =&gt; 'slug',
-                                                'operator' =&gt; 'IN'
-                                        );
-
-                                        $term = str_replace( ' ', '+', $q[$t-&gt;query_var] );
-
-                                        if ( strpos($term, '+') !== false ) {
-                                                $terms = preg_split( '/[+\s]+/', $term );
-                                                foreach ( $terms as $term ) {
-                                                        $tax_query[] = array_merge( $tax_query_defaults, array(
-                                                                'terms' =&gt; array( $term )
-                                                        ) );
-                                                }
-                                        } else {
-                                                $tax_query[] = array_merge( $tax_query_defaults, array(
-                                                        'terms' =&gt; preg_split('/[,\s]+/', $term)
-                                                ) );
-                                        }
-                                }
-                        }
-                }
-
-                // Category stuff
-                if ( !empty($q['cat']) &amp;&amp; '0' != $q['cat'] &amp;&amp; !$this-&gt;is_singular ) {
-                        $q['cat'] = ''.urldecode($q['cat']).'';
-                        $q['cat'] = addslashes_gpc($q['cat']);
-                        $cat_array = preg_split('/[,\s]+/', $q['cat']);
-                        $q['cat'] = '';
-                        $req_cats = array();
-                        foreach ( (array) $cat_array as $cat ) {
-                                $cat = intval($cat);
-                                $req_cats[] = $cat;
-                                $in = ($cat &gt; 0);
-                                $cat = abs($cat);
-                                if ( $in ) {
-                                        $q['category__in'][] = $cat;
-                                } else {
-                                        $q['category__not_in'][] = $cat;
-                                }
-                        }
-                        $q['cat'] = implode(',', $req_cats);
-                }
-
-                if ( !empty($q['category__in']) ) {
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'category',
-                                'terms' =&gt; $q['category__in'],
-                                'operator' =&gt; 'IN',
-                                'field' =&gt; 'term_id'
-                        );
-                }
-
-                if ( !empty($q['category__not_in']) ) {
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'category',
-                                'terms' =&gt; $q['category__not_in'],
-                                'operator' =&gt; 'NOT IN',
-                                'field' =&gt; 'term_id'
-                        );
-                }
-
-                // Category stuff for nice URLs
-                if ( '' != $q['category_name'] &amp;&amp; !$this-&gt;is_singular ) {
-                        $q['category_name'] = str_replace( '%2F', '/', urlencode(urldecode($q['category_name'])) );
-                        $q['category_name'] = '/' . trim( $q['category_name'], '/' );
-
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'category',
-                                'terms' =&gt; array( basename( $q['category_name'] ) ),
-                                'operator' =&gt; 'IN',
-                                'field' =&gt; 'slug'
-                        );
-                }
-
-                // Tag stuff
-                if ( !empty($qv['tag_id']) ) {
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'post_tag',
-                                'terms' =&gt; $qv['tag_id'],
-                                'operator' =&gt; 'IN',
-                                'field' =&gt; 'term_id'
-                        );
-                }
-
-                if ( !empty($q['tag__in']) ) {
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'post_tag',
-                                'terms' =&gt; $q['tag__in'],
-                                'operator' =&gt; 'IN',
-                                'field' =&gt; 'term_id'
-                        );
-                }
-
-                if ( !empty($q['tag__not_in']) ) {
-                        $tax_query[] = array(
-                                'taxonomy' =&gt; 'post_tag',
-                                'terms' =&gt; $q['tag__not_in'],
-                                'operator' =&gt; 'NOT IN',
-                                'field' =&gt; 'term_id'
-                        );
-                }
-
-                if ( !empty( $tax_query ) ) {
-                        $this-&gt;tax_query = $tax_query;
-
</del><ins>+                if ( !empty( $this-&gt;tax_query ) ) {
</ins><span class="cx">                         if ( empty($post_type) ) {
</span><span class="cx">                                 $post_type = 'any';
</span><span class="cx">                                 $post_status_join = true;
</span><span class="lines">@@ -1934,15 +1929,11 @@
</span><span class="cx">                                 $post_status_join = true;
</span><span class="cx">                         }
</span><span class="cx"> 
</span><del>-                        $ids = wp_tax_query( $tax_query );
-                        if ( !empty($ids) )
-                                $where .= &quot; AND $wpdb-&gt;posts.ID IN(&quot; . implode( ', ', $ids ) . &quot;)&quot;;
-                        else
-                                $where .= ' AND 0 = 1';
</del><ins>+                        $where .= $this-&gt;get_tax_sql( &quot;$wpdb-&gt;posts.ID&quot; );
</ins><span class="cx"> 
</span><span class="cx">                         // Back-compat
</span><span class="cx">                         if ( !empty( $ids ) ) {
</span><del>-                                $cat_query = wp_list_filter( $tax_query, array( 'taxonomy' =&gt; 'category' ) );
</del><ins>+                                $cat_query = wp_list_filter( $this-&gt;tax_query, array( 'taxonomy' =&gt; 'category' ) );
</ins><span class="cx">                                 if ( !empty( $cat_query ) ) {
</span><span class="cx">                                         $cat_query = reset( $cat_query );
</span><span class="cx">                                         $cat = get_term_by( $cat_query['field'], $cat_query['terms'][0], 'category' );
</span></span></pre></div>
<a id="trunkwpincludestaxonomyphp"></a>
<div class="modfile"><h4>Modified: trunk/wp-includes/taxonomy.php (15730 => 15731)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/wp-includes/taxonomy.php        2010-10-05 23:06:20 UTC (rev 15730)
+++ trunk/wp-includes/taxonomy.php        2010-10-06 10:40:30 UTC (rev 15731)
</span><span class="lines">@@ -532,40 +532,6 @@
</span><span class="cx">         return $wpdb-&gt;get_col( $sql );        
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-/*
- * Retrieve object_ids matching one or more taxonomy queries
- *
- * @since 3.1.0
- *
- * @param array $queries A list of taxonomy queries. A query is an associative array:
- *   'taxonomy' string|array The taxonomy being queried
- *   'terms' string|array The list of terms
- *   'field' string Which term field is being used. Can be 'term_id', 'slug' or 'name'
- *   'operator' string Can be 'IN' and 'NOT IN'
- *
- * @return array|WP_Error List of matching object_ids; WP_Error on failure.
- */
-function wp_tax_query( $queries ) {
-        global $wpdb;
-
-        $sql = array();
-        foreach ( $queries as $query ) {
-                if ( !isset( $query['include_children'] ) )
-                        $query['include_children'] = true;
-                $query['do_query'] = false;
-                $sql[] = get_objects_in_term( $query['terms'], $query['taxonomy'], $query );
-        }
-
-        if ( 1 == count( $sql ) )
-                return $wpdb-&gt;get_col( $sql[0] );
-
-        $r = &quot;SELECT object_id FROM $wpdb-&gt;term_relationships WHERE 1=1&quot;;
-        foreach ( $sql as $query )
-                $r .= &quot; AND object_id IN ($query)&quot;;
-
-        return $wpdb-&gt;get_col( $r );
-}
-
</del><span class="cx"> /**
</span><span class="cx">  * Get all Term data from database by Term ID.
</span><span class="cx">  *
</span></span></pre>
</div>
</div>

</body>
</html>