From 2030231f663de00b61e2617eb23d3e3a139f751f Mon Sep 17 00:00:00 2001 From: scribu Date: Mon, 13 Sep 2010 16:44:14 +0000 Subject: [PATCH] Clean up taxonomy queries in WP_Query. See #12891 git-svn-id: http://svn.automattic.com/wordpress/trunk@15613 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/general-template.php | 15 +- wp-includes/query.php | 248 ++++++++++++------------------- wp-includes/taxonomy.php | 128 +++++++++++++--- wp-includes/theme.php | 7 +- 4 files changed, 210 insertions(+), 188 deletions(-) diff --git a/wp-includes/general-template.php b/wp-includes/general-template.php index c4ddf409e9..07ab3eacf5 100644 --- a/wp-includes/general-template.php +++ b/wp-includes/general-template.php @@ -1589,6 +1589,8 @@ function feed_links( $args = array() ) { * @param array $args Optional arguments. */ function feed_links_extra( $args = array() ) { + global $wp_query; + $defaults = array( /* translators: Separator between blog name and feed type in feed links */ 'separator' => _x('»', 'feed link'), @@ -1614,16 +1616,15 @@ function feed_links_extra( $args = array() ) { $href = get_post_comments_feed_link( $post->ID ); } } elseif ( is_category() ) { - $cat_id = intval( get_query_var('cat') ); + $term = $wp_query->get_queried_object(); - $title = esc_attr(sprintf( $args['cattitle'], get_bloginfo('name'), $args['separator'], get_cat_name( $cat_id ) )); - $href = get_category_feed_link( $cat_id ); + $title = esc_attr(sprintf( $args['cattitle'], get_bloginfo('name'), $args['separator'], $term->name )); + $href = get_category_feed_link( $term->term_id ); } elseif ( is_tag() ) { - $tag_id = intval( get_query_var('tag_id') ); - $tag = get_tag( $tag_id ); + $term = $wp_query->get_queried_object(); - $title = esc_attr(sprintf( $args['tagtitle'], get_bloginfo('name'), $args['separator'], $tag->name )); - $href = get_tag_feed_link( $tag_id ); + $title = esc_attr(sprintf( $args['tagtitle'], get_bloginfo('name'), $args['separator'], $term->name )); + $href = get_tag_feed_link( $term->term_id ); } elseif ( is_author() ) { $author_id = intval( get_query_var('author') ); diff --git a/wp-includes/query.php b/wp-includes/query.php index ef489c501a..db1deef561 100644 --- a/wp-includes/query.php +++ b/wp-includes/query.php @@ -661,6 +661,15 @@ class WP_Query { */ var $query_vars = array(); + /** + * Taxonomy query, after parsing + * + * @since 3.1.0 + * @access public + * @var array + */ + var $tax_query = array(); + /** * Holds the data for a single object that is queried. * @@ -1528,7 +1537,6 @@ class WP_Query { // First let's clear some variables $distinct = ''; - $whichcat = ''; $whichauthor = ''; $whichmimetype = ''; $where = ''; @@ -1785,13 +1793,38 @@ class WP_Query { // Allow plugins to contextually add/remove/modify the search section of the database query $search = apply_filters_ref_array('posts_search', array( $search, &$this ) ); - // Category stuff + // Taxonomies + $tax_query = array(); - if ( empty($q['cat']) || ($q['cat'] == '0') || - // Bypass cat checks if fetching specific posts - $this->is_singular ) { - $whichcat = ''; - } else { + if ( $this->is_tax ) { + foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy => $t ) { + if ( $t->query_var && !empty( $q[$t->query_var] ) ) { + $tax_query_defaults = array( + 'taxonomy' => $taxonomy, + 'field' => 'slug', + 'operator' => 'IN' + ); + + $term = str_replace( ' ', '+', $q[$t->query_var] ); + + if ( strpos($term, '+') !== false ) { + $terms = preg_split( '/[+\s]+/', $term ); + foreach ( $terms as $term ) { + $tax_query[] = array_merge( $tax_query_defaults, array( + 'terms' => array( $term ) + ) ); + } + } else { + $tax_query[] = array_merge( $tax_query_defaults, array( + 'terms' => preg_split('/[,\s]+/', $term) + ) ); + } + } + } + } + + // Category stuff + if ( !empty($q['cat']) && '0' != $q['cat'] && !$this->is_singular ) { $q['cat'] = ''.urldecode($q['cat']).''; $q['cat'] = addslashes_gpc($q['cat']); $cat_array = preg_split('/[,\s]+/', $q['cat']); @@ -1804,25 +1837,29 @@ class WP_Query { $cat = abs($cat); if ( $in ) { $q['category__in'][] = $cat; - $q['category__in'] = array_merge($q['category__in'], get_term_children($cat, 'category')); } else { $q['category__not_in'][] = $cat; - $q['category__not_in'] = array_merge($q['category__not_in'], get_term_children($cat, 'category')); } } $q['cat'] = implode(',', $req_cats); } if ( !empty($q['category__in']) ) { - $join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) "; - $whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'category' "; - $include_cats = "'" . implode("', '", $q['category__in']) . "'"; - $whichcat .= " AND $wpdb->term_taxonomy.term_id IN ($include_cats) "; + $tax_query[] = array( + 'taxonomy' => 'category', + 'terms' => $q['category__in'], + 'operator' => 'IN', + 'field' => 'term_id' + ); } if ( !empty($q['category__not_in']) ) { - $cat_string = "'" . implode("', '", $q['category__not_in']) . "'"; - $whichcat .= " AND $wpdb->posts.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy = 'category' AND tt.term_id IN ($cat_string) )"; + $tax_query[] = array( + 'taxonomy' => 'category', + 'terms' => $q['category__not_in'], + 'operator' => 'NOT IN', + 'field' => 'term_id' + ); } // Category stuff for nice URLs @@ -1851,137 +1888,50 @@ class WP_Query { $q['cat'] = $reqcat; - $join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) "; - $whichcat = " AND $wpdb->term_taxonomy.taxonomy = 'category' "; - $in_cats = array($q['cat']); - $in_cats = array_merge($in_cats, get_term_children($q['cat'], 'category')); - $in_cats = "'" . implode("', '", $in_cats) . "'"; - $whichcat .= "AND $wpdb->term_taxonomy.term_id IN ($in_cats)"; - $groupby = "{$wpdb->posts}.ID"; + $tax_query[] = array( + 'taxonomy' => 'category', + 'terms' => array( $q['cat'] ), + 'operator' => 'IN', + 'field' => 'term_id' + ); } - // Tags - if ( '' != $q['tag'] ) { - if ( strpos($q['tag'], ',') !== false ) { - $tags = preg_split('/[,\s]+/', $q['tag']); - foreach ( (array) $tags as $tag ) { - $tag = sanitize_term_field('slug', $tag, 0, 'post_tag', 'db'); - $q['tag_slug__in'][] = $tag; - } - } else if ( preg_match('/[+\s]+/', $q['tag']) || !empty($q['cat']) ) { - $tags = preg_split('/[+\s]+/', $q['tag']); - foreach ( (array) $tags as $tag ) { - $tag = sanitize_term_field('slug', $tag, 0, 'post_tag', 'db'); - $q['tag_slug__and'][] = $tag; - } - } else { - $q['tag'] = sanitize_term_field('slug', $q['tag'], 0, 'post_tag', 'db'); - $q['tag_slug__in'][] = $q['tag']; - } + // Tag stuff + if ( !empty($qv['tag_id']) ) { + $tax_query[] = array( + 'taxonomy' => 'post_tag', + 'terms' => $qv['tag_id'], + 'operator' => 'IN', + 'field' => 'term_id' + ); } - if ( !empty($q['category__in']) || !empty($q['meta_key']) || !empty($q['tag__in']) || !empty($q['tag_slug__in']) ) { - $groupby = "{$wpdb->posts}.ID"; - } - - if ( !empty($q['tag__in']) && empty($q['cat']) ) { - $join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) "; - $whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'post_tag' "; - $include_tags = "'" . implode("', '", $q['tag__in']) . "'"; - $whichcat .= " AND $wpdb->term_taxonomy.term_id IN ($include_tags) "; - $reqtag = term_exists( $q['tag__in'][0], 'post_tag' ); - if ( !empty($reqtag) ) - $q['tag_id'] = $reqtag['term_id']; - } - - if ( !empty($q['tag_slug__in']) && empty($q['cat']) ) { - $join = " INNER JOIN $wpdb->term_relationships ON ($wpdb->posts.ID = $wpdb->term_relationships.object_id) INNER JOIN $wpdb->term_taxonomy ON ($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id) INNER JOIN $wpdb->terms ON ($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id) "; - $whichcat .= " AND $wpdb->term_taxonomy.taxonomy = 'post_tag' "; - $include_tags = "'" . implode("', '", $q['tag_slug__in']) . "'"; - $whichcat .= " AND $wpdb->terms.slug IN ($include_tags) "; - $reqtag = get_term_by( 'slug', $q['tag_slug__in'][0], 'post_tag' ); - if ( !empty($reqtag) ) - $q['tag_id'] = $reqtag->term_id; + if ( !empty($q['tag__in']) ) { + $tax_query[] = array( + 'taxonomy' => 'post_tag', + 'terms' => $q['tag__in'], + 'operator' => 'IN', + 'field' => 'term_id' + ); } if ( !empty($q['tag__not_in']) ) { - $tag_string = "'" . implode("', '", $q['tag__not_in']) . "'"; - $whichcat .= " AND $wpdb->posts.ID NOT IN ( SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy = 'post_tag' AND tt.term_id IN ($tag_string) )"; + $tax_query[] = array( + 'taxonomy' => 'post_tag', + 'terms' => $q['tag__not_in'], + 'operator' => 'NOT IN', + 'field' => 'term_id' + ); } - // Tag and slug intersections. - $intersections = array('category__and' => 'category', 'tag__and' => 'post_tag', 'tag_slug__and' => 'post_tag', 'tag__in' => 'post_tag', 'tag_slug__in' => 'post_tag'); - $tagin = array('tag__in', 'tag_slug__in'); // These are used to make some exceptions below - foreach ( $intersections as $item => $taxonomy ) { - if ( empty($q[$item]) ) continue; - if ( in_array($item, $tagin) && empty($q['cat']) ) continue; // We should already have what we need if categories aren't being used + if ( !empty( $tax_query ) ) { + $this->tax_query = $tax_query; - if ( $item != 'category__and' ) { - $reqtag = term_exists( $q[$item][0], 'post_tag' ); - if ( !empty($reqtag) ) - $q['tag_id'] = $reqtag['term_id']; - } - - if ( in_array( $item, array('tag_slug__and', 'tag_slug__in' ) ) ) - $taxonomy_field = 'slug'; - else - $taxonomy_field = 'term_id'; - - $q[$item] = array_unique($q[$item]); - $tsql = "SELECT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->term_relationships tr ON (p.ID = tr.object_id) INNER JOIN $wpdb->term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) INNER JOIN $wpdb->terms t ON (tt.term_id = t.term_id)"; - $tsql .= " WHERE tt.taxonomy = '$taxonomy' AND t.$taxonomy_field IN ('" . implode("', '", $q[$item]) . "')"; - if ( !in_array($item, $tagin) ) { // This next line is only helpful if we are doing an and relationship - $tsql .= " GROUP BY p.ID HAVING count(p.ID) = " . count($q[$item]); - } - $post_ids = $wpdb->get_col($tsql); - - if ( count($post_ids) ) - $whichcat .= " AND $wpdb->posts.ID IN (" . implode(', ', $post_ids) . ") "; - else { - $whichcat = " AND 0 = 1"; - break; - } + $where .= " AND $wpdb->posts.ID IN( " . implode( ', ', wp_tax_query( $tax_query ) ) . ")"; } - // Taxonomies - if ( $this->is_tax ) { - if ( '' != $q['taxonomy'] ) { - $taxonomy = $q['taxonomy']; - $tt[$taxonomy] = $q['term']; - } else { - foreach ( $GLOBALS['wp_taxonomies'] as $taxonomy => $t ) { - if ( $t->query_var && '' != $q[$t->query_var] ) { - $tt[$taxonomy] = $q[$t->query_var]; - break; - } - } - } - - $terms = get_terms($taxonomy, array('slug' => $tt[$taxonomy], 'hide_empty' => !is_taxonomy_hierarchical($taxonomy))); - - if ( is_wp_error($terms) || empty($terms) ) { - $whichcat = " AND 0 "; - } else { - foreach ( $terms as $term ) { - $term_ids[] = $term->term_id; - if ( is_taxonomy_hierarchical($taxonomy) ) { - $children = get_term_children($term->term_id, $taxonomy); - $term_ids = array_merge($term_ids, $children); - } - } - $post_ids = get_objects_in_term($term_ids, $taxonomy); - if ( !is_wp_error($post_ids) && !empty($post_ids) ) { - $whichcat .= " AND $wpdb->posts.ID IN (" . implode(', ', $post_ids) . ") "; - if ( empty($post_type) ) { - $post_type = 'any'; - $post_status_join = true; - } elseif ( in_array('attachment', (array)$post_type) ) { - $post_status_join = true; - } - } else { - $whichcat = " AND 0 "; - } - } + if ( !empty($q['meta_key']) ) { + $groupby = "{$wpdb->posts}.ID"; } // Author/user stuff @@ -2033,7 +1983,7 @@ class WP_Query { $whichmimetype = wp_post_mime_type_where($q['post_mime_type'], $table_alias); } - $where .= $search . $whichcat . $whichauthor . $whichmimetype; + $where .= $search . $whichauthor . $whichmimetype; if ( empty($q['order']) || ((strtoupper($q['order']) != 'ASC') && (strtoupper($q['order']) != 'DESC')) ) $q['order'] = 'DESC'; @@ -2625,27 +2575,13 @@ class WP_Query { $this->queried_object = NULL; $this->queried_object_id = 0; - if ( $this->is_category ) { - $cat = $this->get('cat'); - $category = &get_category($cat); - if ( is_wp_error( $category ) ) - return NULL; - $this->queried_object = &$category; - $this->queried_object_id = (int) $cat; - } elseif ( $this->is_tag ) { - $tag_id = $this->get('tag_id'); - $tag = &get_term($tag_id, 'post_tag'); - if ( is_wp_error( $tag ) ) - return NULL; - $this->queried_object = &$tag; - $this->queried_object_id = (int) $tag_id; - } elseif ( $this->is_tax ) { - $tax = $this->get('taxonomy'); - $slug = $this->get('term'); - $term = &get_terms($tax, array( 'slug' => $slug, 'hide_empty' => false ) ); - if ( is_wp_error($term) || empty($term) ) - return NULL; - $term = $term[0]; + if ( $this->tax_query ) { + $query = reset( $this->tax_query ); + if ( 'term_id' == $query['field'] ) + $term = get_term( reset( $query['terms'] ), $query['taxonomy'] ); + else + $term = get_term_by( $query['field'], reset( $query['terms'] ), $query['taxonomy'] ); + $this->queried_object = $term; $this->queried_object_id = $term->term_id; } elseif ( $this->is_posts_page ) { diff --git a/wp-includes/taxonomy.php b/wp-includes/taxonomy.php index 1d63a5ff66..f830027ae1 100644 --- a/wp-includes/taxonomy.php +++ b/wp-includes/taxonomy.php @@ -23,12 +23,12 @@ function create_initial_taxonomies() { 'public' => true, 'show_ui' => true, '_builtin' => true, - ) ) ; + ) ); register_taxonomy( 'post_tag', 'post', array( 'hierarchical' => false, 'update_count_callback' => '_update_post_term_count', - 'query_var' => false, + 'query_var' => 'tag', 'rewrite' => false, 'public' => true, 'show_ui' => true, @@ -432,43 +432,125 @@ function register_taxonomy_for_object_type( $taxonomy, $object_type) { * @uses $wpdb * @uses wp_parse_args() Creates an array from string $args. * - * @param int|array $term_ids Term id or array of term ids of terms that will be used + * @param mixed $terms Term id/slug/name or array of such to match against * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names - * @param array|string $args Change the order of the object_ids, either ASC or DESC - * @return WP_Error|array If the taxonomy does not exist, then WP_Error will be returned. On success - * the array can be empty meaning that there are no $object_ids found or it will return the $object_ids found. + * @param array|string $args + * 'include_children' bool Wether to include term children (hierarchical taxonomies only) + * 'field' string Which term field is being used. Can be 'term_id', 'slug' or 'name' + * 'operator' string Can be 'IN' and 'NOT IN' + * 'do_query' bool Wether to execute the query or return the SQL string + * + * @return WP_Error If the taxonomy does not exist + * @return array The list of found object_ids + * @return string The SQL string, if do_query is set to false */ -function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) { +function get_objects_in_term( $terms, $taxonomies, $args = array() ) { global $wpdb; - if ( ! is_array( $term_ids ) ) - $term_ids = array( $term_ids ); + extract( wp_parse_args( $args, array( + 'include_children' => false, + 'field' => 'term_id', + 'operator' => 'IN', + 'do_query' => true, + ) ), EXTR_SKIP ); - if ( ! is_array( $taxonomies ) ) - $taxonomies = array( $taxonomies ); + $taxonomies = (array) $taxonomies; - foreach ( (array) $taxonomies as $taxonomy ) { + foreach ( $taxonomies as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) - return new WP_Error( 'invalid_taxonomy', __( 'Invalid Taxonomy' ) ); + return new WP_Error( 'invalid_taxonomy', sprintf( __( 'Invalid Taxonomy: %s' ), $taxonomy ) ); } - $defaults = array( 'order' => 'ASC' ); - $args = wp_parse_args( $args, $defaults ); - extract( $args, EXTR_SKIP ); + $terms = array_unique( (array) $terms ); + if ( empty($terms) ) + continue; - $order = ( 'desc' == strtolower( $order ) ) ? 'DESC' : 'ASC'; + if ( !in_array( $field, array( 'term_id', 'slug', 'name' ) ) ) + $field = 'term_id'; - $term_ids = array_map('intval', $term_ids ); + if ( !in_array( $operator, array( 'IN', 'NOT IN' ) ) ) + $operator = 'IN'; $taxonomies = "'" . implode( "', '", $taxonomies ) . "'"; - $term_ids = "'" . implode( "', '", $term_ids ) . "'"; - $object_ids = $wpdb->get_col("SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order"); + switch ( $field ) { + case 'term_id': + $terms = array_map( 'intval', $terms ); - if ( ! $object_ids ) - return array(); + if ( is_taxonomy_hierarchical( $taxonomy ) && $include_children ) { + $children = $terms; + foreach ( $terms as $term ) + $children = array_merge( $children, get_term_children( $term, $taxonomy ) ); + $terms = $children; + } - return $object_ids; + $terms = implode( ',', $terms ); + $sql = " + SELECT object_id + FROM $wpdb->term_relationships + INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id) + WHERE taxonomy IN ($taxonomies) + AND term_id $operator ($terms) + "; + break; + + case 'slug': + case 'name': + foreach ( $terms as $i => $term ) { + $terms[$i] = sanitize_term_field('slug', $term, 0, $taxonomy, 'db'); + } + $terms = array_filter($terms); + + $terms = "'" . implode( "','", $terms ) . "'"; + $sql = " + SELECT object_id + FROM $wpdb->term_relationships + INNER JOIN $wpdb->term_taxonomy USING (term_taxonomy_id) + INNER JOIN $wpdb->terms USING (term_id) + WHERE taxonomy IN ($taxonomies) + AND $field $operator ($terms) + "; + break; + } + + if ( !$do_query ) + return $sql; + + return $wpdb->get_col( $sql ); +} + +/* + * 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->get_col( $sql[0] ); + + $r = "SELECT object_id FROM $wpdb->term_relationships WHERE 1=1"; + foreach ( $sql as $query ) + $r .= " AND object_id IN ($query)"; + + return $wpdb->get_col( $r ); } /** diff --git a/wp-includes/theme.php b/wp-includes/theme.php index fdeb3543af..d0779bcb29 100644 --- a/wp-includes/theme.php +++ b/wp-includes/theme.php @@ -807,8 +807,11 @@ function get_category_template() { * @return string */ function get_tag_template() { - $tag_id = absint( get_query_var('tag_id') ); - $tag_name = get_query_var('tag'); + global $wp_query; + + $tag = $wp_query->get_queried_object(); + $tag_name = $tag->slug; + $tag_id = $tag->term_id; $templates = array();