From c67e5da8c9a885aabb5d6dd601394a672f543089 Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Sat, 24 Jan 2015 18:47:30 +0000 Subject: [PATCH] Introduce 'childless' parameter to `get_terms()`. This new parameter allows developers to limit queried terms to terminal nodes - ie, those without any descendants. As part of the improvement, some internal logic in `get_terms()` has been consolidated. Parameters that resolve to a NOT IN clause containing term IDs ('exclude', 'exclude_tree', and 'childless') are now parsed into a single "exclusions" array before the SQL clause is generated. Props theMikeD, horike. Fixes #29839. git-svn-id: https://develop.svn.wordpress.org/trunk@31275 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/taxonomy.php | 28 ++++--- tests/phpunit/tests/term/getTerms.php | 115 ++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 3638f12612..9535b4e902 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1547,7 +1547,7 @@ function get_term_to_edit( $id, $taxonomy ) { * along with the $args array. * * @since 2.3.0 - * @since 4.2.0 Introduced 'name' parameter. + * @since 4.2.0 Introduced 'name' and 'childless' parameters. * * @global wpdb $wpdb WordPress database abstraction object. * @@ -1595,6 +1595,8 @@ function get_term_to_edit( $id, $taxonomy ) { * @type int $child_of Term ID to retrieve child terms of. If multiple taxonomies * are passed, $child_of is ignored. Default 0. * @type int|string $parent Parent term ID to retrieve direct-child terms of. Default empty. + * @type bool $childless True to limit results to terms that have no children. This parameter has + * no effect on non-hierarchical taxonomies. Default false. * @type string $cache_domain Unique cache key to be produced when this query is stored in an * object cache. Default is 'core'. * } @@ -1619,7 +1621,7 @@ function get_terms( $taxonomies, $args = '' ) { $defaults = array('orderby' => 'name', 'order' => 'ASC', 'hide_empty' => true, 'exclude' => array(), 'exclude_tree' => array(), 'include' => array(), - 'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', + 'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', 'childless' => false, 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'description__like' => '', 'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core' ); $args = wp_parse_args( $args, $defaults ); @@ -1638,6 +1640,7 @@ function get_terms( $taxonomies, $args = '' ) { } if ( 'all' == $args['get'] ) { + $args['childless'] = false; $args['child_of'] = 0; $args['hide_empty'] = 0; $args['hierarchical'] = false; @@ -1754,6 +1757,7 @@ function get_terms( $taxonomies, $args = '' ) { $where .= $inclusions; } + $exclusions = array(); if ( ! empty( $exclude_tree ) ) { $exclude_tree = wp_parse_id_list( $exclude_tree ); $excluded_children = $exclude_tree; @@ -1763,22 +1767,24 @@ function get_terms( $taxonomies, $args = '' ) { (array) get_terms( $taxonomies[0], array( 'child_of' => intval( $extrunk ), 'fields' => 'ids', 'hide_empty' => 0 ) ) ); } - $exclusions = implode( ',', array_map( 'intval', $excluded_children ) ); - } else { - $exclusions = ''; + $exclusions = array_merge( $excluded_children, $exclusions ); } if ( ! empty( $exclude ) ) { - $exterms = wp_parse_id_list( $exclude ); - if ( empty( $exclusions ) ) { - $exclusions = implode( ',', $exterms ); - } else { - $exclusions .= ', ' . implode( ',', $exterms ); + $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions ); + } + + // 'childless' terms are those without an entry in the flattened term hierarchy. + $childless = (bool) $args['childless']; + if ( $childless ) { + foreach ( $taxonomies as $_tax ) { + $term_hierarchy = _get_term_hierarchy( $_tax ); + $exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions ); } } if ( ! empty( $exclusions ) ) { - $exclusions = ' AND t.term_id NOT IN (' . $exclusions . ')'; + $exclusions = ' AND t.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')'; } /** diff --git a/tests/phpunit/tests/term/getTerms.php b/tests/phpunit/tests/term/getTerms.php index fc9d4a4a33..b8ea31e9b2 100644 --- a/tests/phpunit/tests/term/getTerms.php +++ b/tests/phpunit/tests/term/getTerms.php @@ -495,6 +495,121 @@ class Tests_Term_getTerms extends WP_UnitTestCase { $this->assertEqualSets( array( $t3, $t1 ), $found ); } + /** + * @ticket 29839 + */ + public function test_childless_should_return_all_terms_for_flat_hierarchy() { + // If run on a flat hierarchy it should return everything. + $flat_tax = 'countries'; + register_taxonomy( $flat_tax, 'post', array( 'hierarchical' => false ) ); + $australia = $this->factory->term->create( array( 'name' => 'Australia', 'taxonomy' => $flat_tax ) ); + $china = $this->factory->term->create( array( 'name' => 'China', 'taxonomy' => $flat_tax ) ); + $tanzania = $this->factory->term->create( array( 'name' => 'Tanzania', 'taxonomy' => $flat_tax ) ); + + $terms = get_terms( $flat_tax, array( + 'childless' => true, + 'hide_empty' => false, + 'fields' => 'ids', + ) ); + + $expected = array( $australia, $china, $tanzania ); + $this->assertEqualSets( $expected, $terms ); + } + + + /** + * @ticket 29839 + */ + public function test_childless_hierarchical_taxonomy() { + $tax = 'location'; + register_taxonomy( $tax, 'post', array( 'hierarchical' => true ) ); + /* + Canada + Ontario + Ottawa + Nepean + Toronto + Quebec + Montreal + PEI + */ + // Level 1 + $canada = $this->factory->term->create( array( 'name' => 'Canada', 'taxonomy' => $tax ) ); + + // Level 2 + $ontario = $this->factory->term->create( array( 'name' => 'Ontario', 'parent' => $canada, 'taxonomy' => $tax ) ); + $quebec = $this->factory->term->create( array( 'name' => 'Quebec', 'parent' => $canada, 'taxonomy' => $tax ) ); + $pei = $this->factory->term->create( array( 'name' => 'PEI', 'parent' => $canada, 'taxonomy' => $tax ) ); + + // Level 3 + $toronto = $this->factory->term->create( array( 'name' => 'Toronto', 'parent' => $ontario, 'taxonomy' => $tax ) ); + $ottawa = $this->factory->term->create( array( 'name' => 'Ottawa', 'parent' => $ontario, 'taxonomy' => $tax ) ); + $montreal = $this->factory->term->create( array( 'name' => 'Montreal', 'parent' => $quebec, 'taxonomy' => $tax ) ); + + // Level 4 + $nepean = $this->factory->term->create( array( 'name' => 'Nepean', 'parent' => $ottawa, 'taxonomy' => $tax ) ); + + $terms = get_terms( $tax, array( + 'childless' => true, + 'hide_empty' => false, + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $montreal, $nepean, $toronto, $pei ), $terms ); + } + + /** + * @ticket 29839 + */ + public function test_childless_hierarchical_taxonomy_used_with_child_of() { + $tax = 'location'; + register_taxonomy( $tax, 'post', array( 'hierarchical' => true ) ); + + // Level 1 + $canada = $this->factory->term->create( array( 'name' => 'Canada', 'taxonomy' => $tax ) ); + + // Level 2 + $ontario = $this->factory->term->create( array( 'name' => 'Ontario', 'parent' => $canada, 'taxonomy' => $tax ) ); + $quebec = $this->factory->term->create( array( 'name' => 'Quebec', 'parent' => $canada, 'taxonomy' => $tax ) ); + + // Level 3 + $laval = $this->factory->term->create( array( 'name' => 'Laval', 'parent' => $quebec, 'taxonomy' => $tax ) ); + $montreal = $this->factory->term->create( array( 'name' => 'Montreal', 'parent' => $quebec, 'taxonomy' => $tax ) ); + + // Level 4 + $dorval = $this->factory->term->create( array( 'name' => 'Dorval', 'parent' => $montreal, 'taxonomy' => $tax ) ); + + $terms = get_terms( $tax, array( + 'childless' => true, + 'child_of' => $quebec, + 'hide_empty' => false, + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $laval ), $terms ); + } + + /** + * @ticket 29839 + */ + public function test_childless_should_enforce_childless_status_for_all_queried_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_tax1', 'parent' => $t1 ) ); + $t3 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2' ) ); + $t4 = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax2', 'parent' => $t3 ) ); + + $found = get_terms( array( 'wptests_tax1', 'wptests_tax2' ), array( + 'fields' => 'ids', + 'hide_empty' => false, + 'childless' => true, + ) ); + + $this->assertEqualSets( array( $t2, $t4 ), $found ); + } + public function test_get_terms_hierarchical_tax_hide_empty_false_fields_ids() { // Set up a clean taxonomy. $tax = 'hierarchical_fields';