From fb227caed19358fd2d0b13232b27a574577a4587 Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Wed, 13 Sep 2017 14:47:07 +0000 Subject: [PATCH] Taxonomy: Force a `DISTINCT` term query when result count matters. Generally, duplicate terms returned by a term query are eliminated in PHP, after the database query takes place. This technique doesn't work properly when the query parameters specify the `number` of results, since the results of a `SELECT ... LIMIT x...` query may be deduplicated to a count less than `x`. In these cases, we force the original query to be `DISTINCT`. Props elvishp2006. Fixes #41796. git-svn-id: https://develop.svn.wordpress.org/trunk@41377 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/class-wp-term-query.php | 15 ++++++- tests/phpunit/tests/term/query.php | 57 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-term-query.php b/src/wp-includes/class-wp-term-query.php index 12e7dec242..f6f53f210c 100644 --- a/src/wp-includes/class-wp-term-query.php +++ b/src/wp-includes/class-wp-term-query.php @@ -551,6 +551,16 @@ class WP_Term_Query { $limits = ''; } + $do_distinct = false; + + /* + * Duplicate terms are generally removed when necessary after the database query. + * But when a LIMIT clause is included in the query, we let MySQL enforce + * distinctness so the count is correct. + */ + if ( ! empty( $limits ) && 'all_with_object_id' !== $args['fields'] ) { + $do_distinct = true; + } if ( ! empty( $args['search'] ) ) { $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] ); @@ -568,8 +578,7 @@ class WP_Term_Query { if ( ! empty( $meta_clauses ) ) { $join .= $mq_sql['join']; $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] ); - $distinct .= "DISTINCT"; - + $do_distinct = true; } $selects = array(); @@ -631,6 +640,8 @@ class WP_Term_Query { $where = implode( ' AND ', $this->sql_clauses['where'] ); + $distinct = $do_distinct ? 'DISTINCT' : ''; + /** * Filters the terms query SQL clauses. * diff --git a/tests/phpunit/tests/term/query.php b/tests/phpunit/tests/term/query.php index b2e2a27621..855db6fb28 100644 --- a/tests/phpunit/tests/term/query.php +++ b/tests/phpunit/tests/term/query.php @@ -427,4 +427,61 @@ class Tests_Term_Query extends WP_UnitTestCase { $terms = wp_get_object_terms( $post_id, array( 'category', 'wptests_tax' ) ); $this->assertEquals( array( $term_ids[1], $term_ids[0], 1 ), wp_list_pluck( $terms, 'term_id' ) ); } + + /** + * @ticket 41796 + */ + public function test_number_should_work_with_object_ids() { + register_taxonomy( 'wptests_tax', 'post' ); + + $term_1 = self::factory()->term->create( array( + 'taxonomy' => 'wptests_tax', + ) ); + $term_2 = self::factory()->term->create( array( + 'taxonomy' => 'wptests_tax', + ) ); + + $post_1 = self::factory()->post->create(); + $post_2 = self::factory()->post->create(); + + wp_set_object_terms( $post_1, array( $term_1, $term_2 ), 'wptests_tax' ); + wp_set_object_terms( $post_2, array( $term_1 ), 'wptests_tax' ); + + $q = new WP_Term_Query( array( + 'taxonomy' => 'wptests_tax', + 'object_ids' => array( $post_1, $post_2 ), + 'number' => 2, + ) ); + + $this->assertEqualSets( array( $term_1, $term_2 ), wp_list_pluck( $q->terms, 'term_id' ) ); + } + + /** + * @ticket 41796 + */ + public function test_number_should_work_with_object_ids_and_all_with_object_id() { + register_taxonomy( 'wptests_tax', 'post' ); + + $term_1 = self::factory()->term->create( array( + 'taxonomy' => 'wptests_tax', + ) ); + $term_2 = self::factory()->term->create( array( + 'taxonomy' => 'wptests_tax', + ) ); + + $post_1 = self::factory()->post->create(); + $post_2 = self::factory()->post->create(); + + wp_set_object_terms( $post_1, array( $term_1, $term_2 ), 'wptests_tax' ); + wp_set_object_terms( $post_2, array( $term_1 ), 'wptests_tax' ); + + $q = new WP_Term_Query( array( + 'taxonomy' => 'wptests_tax', + 'object_ids' => array( $post_1, $post_2 ), + 'fields' => 'all_with_object_id', + 'number' => 2, + ) ); + + $this->assertEqualSets( array( $term_1, $term_1 ), wp_list_pluck( $q->terms, 'term_id' ) ); + } }