From 5bcee9e93931fae6fd3584a33746d44a8125c4cd Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Sun, 25 Jan 2015 02:45:11 +0000 Subject: [PATCH] In `get_terms()`, check hierarchy for all `$taxonomies` before bailing early from 'parent' or 'child_of'. There is a pre-check in `get_terms()` that prevents an unnecessary database query if the 'parent' or 'child_of' parameter is not found in the cached term hierarchy (since a term without an index in the hierarchy cache has no descendants). Previously, only the first item in the `$taxonomies` array was being checked, with the result that an empty array was being erroneously returned in cases where the 'parent' or 'child_of' term is in a subsequent taxonomy. See #31118. git-svn-id: https://develop.svn.wordpress.org/trunk@31276 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/taxonomy.php | 35 ++++++++---- tests/phpunit/tests/term/getTerms.php | 82 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 9535b4e902..e5816580d1 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1657,18 +1657,29 @@ function get_terms( $taxonomies, $args = '' ) { */ $args = apply_filters( 'get_terms_args', $args, $taxonomies ); + // Avoid the query if the queried parent/child_of term has no descendants. $child_of = $args['child_of']; + $parent = $args['parent']; + if ( $child_of ) { - $hierarchy = _get_term_hierarchy( reset( $taxonomies ) ); - if ( ! isset( $hierarchy[ $child_of ] ) ) { - return $empty_array; - } + $_parent = $child_of; + } elseif ( $parent ) { + $_parent = $parent; + } else { + $_parent = false; } - $parent = $args['parent']; - if ( $parent ) { - $hierarchy = _get_term_hierarchy( reset( $taxonomies ) ); - if ( ! isset( $hierarchy[ $parent ] ) ) { + if ( $_parent ) { + $in_hierarchy = false; + foreach ( $taxonomies as $_tax ) { + $hierarchy = _get_term_hierarchy( $_tax ); + + if ( isset( $hierarchy[ $_parent ] ) ) { + $in_hierarchy = true; + } + } + + if ( ! $in_hierarchy ) { return $empty_array; } } @@ -1942,9 +1953,11 @@ function get_terms( $taxonomies, $args = '' ) { } if ( $child_of ) { - $children = _get_term_hierarchy( reset( $taxonomies ) ); - if ( ! empty( $children ) ) { - $terms = _get_term_children( $child_of, $terms, reset( $taxonomies ) ); + foreach ( $taxonomies as $_tax ) { + $children = _get_term_hierarchy( $_tax ); + if ( ! empty( $children ) ) { + $terms = _get_term_children( $child_of, $terms, $_tax ); + } } } diff --git a/tests/phpunit/tests/term/getTerms.php b/tests/phpunit/tests/term/getTerms.php index b8ea31e9b2..625d0657a0 100644 --- a/tests/phpunit/tests/term/getTerms.php +++ b/tests/phpunit/tests/term/getTerms.php @@ -376,6 +376,47 @@ class Tests_Term_getTerms extends WP_UnitTestCase { $this->assertEquals( 1, count( $terms ) ); } + /** + * @ticket 31118 + */ + public function test_child_of_should_skip_query_when_specified_parent_is_not_found_in_hierarchy_cache() { + global $wpdb; + + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true, ) ); + + $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + + $num_queries = $wpdb->num_queries; + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'child_of' => $terms[0], + ) ); + + $this->assertEmpty( $found ); + $this->assertSame( $num_queries, $wpdb->num_queries ); + } + + /** + * @ticket 31118 + */ + public function test_child_of_should_respect_multiple_taxonomies() { + register_taxonomy( 'wptests_tax1', 'post', array( 'hierarchical' => true ) ); + register_taxonomy( 'wptests_tax2', 'post', array( 'hierarchical' => true ) ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + $t3 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2', 'parent' => $t2 ) ); + + $found = get_terms( array( 'wptests_tax1', 'wptests_tax2' ), array( + 'fields' => 'ids', + 'hide_empty' => false, + 'child_of' => $t2, + ) ); + + $this->assertEqualSets( array( $t3 ), $found ); + } + /** * @ticket 27123 */ @@ -1156,6 +1197,47 @@ class Tests_Term_getTerms extends WP_UnitTestCase { $this->assertEqualSets( $expected, $actual ); } + /** + * @ticket 31118 + */ + public function test_parent_should_skip_query_when_specified_parent_is_not_found_in_hierarchy_cache() { + global $wpdb; + + register_taxonomy( 'wptests_tax', 'post', array( 'hierarchical' => true, ) ); + + $terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + + $num_queries = $wpdb->num_queries; + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'parent' => $terms[0], + ) ); + + $this->assertEmpty( $found ); + $this->assertSame( $num_queries, $wpdb->num_queries ); + } + + /** + * @ticket 31118 + */ + public function test_parent_should_respect_multiple_taxonomies() { + register_taxonomy( 'wptests_tax1', 'post', array( 'hierarchical' => true ) ); + register_taxonomy( 'wptests_tax2', 'post', array( 'hierarchical' => true ) ); + + $t1 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax1' ) ); + $t2 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + $t3 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2', 'parent' => $t2 ) ); + + $found = get_terms( array( 'wptests_tax1', 'wptests_tax2' ), array( + 'fields' => 'ids', + 'hide_empty' => false, + 'parent' => $t2, + ) ); + + $this->assertEqualSets( array( $t3 ), $found ); + } + public function test_hierarchical_false_parent_should_override_child_of() { $initial_terms = $this->create_hierarchical_terms();