From eb8e2fb6ed65446022fb094f9ff74860d9683991 Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Sat, 6 Feb 2016 04:41:26 +0000 Subject: [PATCH] Allow `get_terms()` results to ordered by metadata. The `$orderby` parameter of `get_terms()` now accepts the following values, related to term meta: * 'meta_value' * 'meta_value_num' * the value of the `$meta_key` parameter * any key from the `$meta_query` array This brings order-by-meta support for terms in line with post, comment, and user queries. As a byproduct of these improvements, `$meta_key` and `$meta_value` parameters have been introduced to `get_terms()`. They interact with `$meta_query` in the same way as in `WP_Query` and other query classes. Props jadpm, eherman24. Fixes #34996. git-svn-id: https://develop.svn.wordpress.org/trunk@36485 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/taxonomy.php | 55 ++++- tests/phpunit/tests/term/getTerms.php | 331 ++++++++++++++++++++++++++ 2 files changed, 382 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index c963589226..4766368be0 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1060,6 +1060,7 @@ function get_term_to_edit( $id, $taxonomy ) { * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter. * Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return * a list of WP_Term objects. + * @since 4.5.0 Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata. * * @global wpdb $wpdb WordPress database abstraction object. * @global array $wp_filter @@ -1071,7 +1072,9 @@ function get_term_to_edit( $id, $taxonomy ) { * @type string $orderby Field(s) to order terms by. Accepts term fields ('name', 'slug', * 'term_group', 'term_id', 'id', 'description'), 'count' for term * taxonomy count, 'include' to match the 'order' of the $include param, - * or 'none' to skip ORDER BY. Defaults to 'name'. + * 'meta_value', 'meta_value_num', the value of `$meta_key`, the array + * keys of `$meta_query`, or 'none' to omit the ORDER BY clause. + * Defaults to 'name'. * @type string $order Whether to order terms in ascending or descending order. * Accepts 'ASC' (ascending) or 'DESC' (descending). * Default 'ASC'. @@ -1119,6 +1122,10 @@ function get_term_to_edit( $id, $taxonomy ) { * @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true. * @type array $meta_query Meta query clauses to limit retrieved terms by. * See `WP_Meta_Query`. Default empty. + * @type string $meta_key Limit terms to those matching a specific metadata key. Can be used in + * conjunction with `$meta_value`. + * @type string $meta_value Limit terms to those matching a specific metadata value. Usually used + * in conjunction with `$meta_key`. * } * @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of $taxonomies * do not exist. @@ -1414,13 +1421,53 @@ function get_terms( $taxonomies, $args = '' ) { // Meta query support. $join = ''; $distinct = ''; - if ( ! empty( $args['meta_query'] ) ) { - $mquery = new WP_Meta_Query( $args['meta_query'] ); - $mq_sql = $mquery->get_sql( 'term', 't', 'term_id' ); + $mquery = new WP_Meta_Query(); + $mquery->parse_query_vars( $args ); + $mq_sql = $mquery->get_sql( 'term', 't', 'term_id' ); + $meta_clauses = $mquery->get_clauses(); + + if ( ! empty( $meta_clauses ) ) { $join .= $mq_sql['join']; $where .= $mq_sql['where']; $distinct .= "DISTINCT"; + + // 'orderby' support. + $allowed_keys = array(); + $primary_meta_key = null; + $primary_meta_query = reset( $meta_clauses ); + if ( ! empty( $primary_meta_query['key'] ) ) { + $primary_meta_key = $primary_meta_query['key']; + $allowed_keys[] = $primary_meta_key; + } + $allowed_keys[] = 'meta_value'; + $allowed_keys[] = 'meta_value_num'; + $allowed_keys = array_merge( $allowed_keys, array_keys( $meta_clauses ) ); + + if ( ! empty( $args['orderby'] ) && in_array( $args['orderby'], $allowed_keys ) ) { + switch( $args['orderby'] ) { + case $primary_meta_key: + case 'meta_value': + if ( ! empty( $primary_meta_query['type'] ) ) { + $orderby = "ORDER BY CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})"; + } else { + $orderby = "ORDER BY {$primary_meta_query['alias']}.meta_value"; + } + break; + + case 'meta_value_num': + $orderby = "ORDER BY {$primary_meta_query['alias']}.meta_value+0"; + break; + + default: + if ( array_key_exists( $args['orderby'], $meta_clauses ) ) { + // $orderby corresponds to a meta_query clause. + $meta_clause = $meta_clauses[ $args['orderby'] ]; + $orderby = "ORDER BY CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})"; + } + break; + } + } } $selects = array(); diff --git a/tests/phpunit/tests/term/getTerms.php b/tests/phpunit/tests/term/getTerms.php index 111a95ef76..9f7facc9cc 100644 --- a/tests/phpunit/tests/term/getTerms.php +++ b/tests/phpunit/tests/term/getTerms.php @@ -1233,6 +1233,337 @@ class Tests_Term_getTerms extends WP_UnitTestCase { $this->assertEquals( array( $t1, $t2, $t3 ), $found ); } + /** + * @ticket 34996 + */ + public function test_orderby_meta_value() { + register_taxonomy( 'wptests_tax', 'post' ); + $terms = self::factory()->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + add_term_meta( $terms[0], 'foo', 'zzz' ); + add_term_meta( $terms[0], 'fee', 'ber' ); + add_term_meta( $terms[1], 'foo', 'aaa' ); + add_term_meta( $terms[1], 'fee', 'ber' ); + add_term_meta( $terms[2], 'foo', 'jjj' ); + add_term_meta( $terms[2], 'fee', 'ber' ); + + // Matches the first meta query clause. + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + } + + /** + * @ticket 34996 + */ + public function test_orderby_meta_value_num() { + register_taxonomy( 'wptests_tax', 'post' ); + $terms = self::factory()->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + add_term_meta( $terms[0], 'foo', '999' ); + add_term_meta( $terms[0], 'fee', 'ber' ); + add_term_meta( $terms[1], 'foo', '111' ); + add_term_meta( $terms[1], 'fee', 'ber' ); + add_term_meta( $terms[2], 'foo', '555' ); + add_term_meta( $terms[2], 'fee', 'ber' ); + + // Matches the first meta query clause. + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'orderby' => 'meta_value_num', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + } + + /** + * @ticket 34996 + */ + public function test_orderby_meta_value_with_meta_key() { + register_taxonomy( 'wptests_tax', 'post' ); + $terms = self::factory()->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + add_term_meta( $terms[0], 'foo', 'bar' ); + add_term_meta( $terms[0], 'fee', 'zzz' ); + add_term_meta( $terms[0], 'faa', 'jjj' ); + add_term_meta( $terms[1], 'foo', 'bar' ); + add_term_meta( $terms[1], 'fee', 'aaa' ); + add_term_meta( $terms[1], 'faa', 'aaa' ); + add_term_meta( $terms[2], 'foo', 'bar' ); + add_term_meta( $terms[2], 'fee', 'jjj' ); + add_term_meta( $terms[2], 'faa', 'zzz' ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + // Matches the first meta query clause. + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + // Matches the meta query clause corresponding to the 'meta_key' param. + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'faa', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[0], $terms[2] ), $found ); + } + + /** + * @ticket 34996 + */ + public function test_orderby_meta_value_num_with_meta_key() { + register_taxonomy( 'wptests_tax', 'post' ); + $terms = self::factory()->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + add_term_meta( $terms[0], 'foo', 'bar' ); + add_term_meta( $terms[0], 'fee', '999' ); + add_term_meta( $terms[0], 'faa', '555' ); + add_term_meta( $terms[1], 'foo', 'bar' ); + add_term_meta( $terms[1], 'fee', '111' ); + add_term_meta( $terms[1], 'faa', '111' ); + add_term_meta( $terms[2], 'foo', 'bar' ); + add_term_meta( $terms[2], 'fee', '555' ); + add_term_meta( $terms[2], 'faa', '999' ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'fee', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'meta_key' => 'faa', + 'orderby' => 'meta_value', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[0], $terms[2] ), $found ); + } + + /** + * @ticket 34996 + */ + public function test_orderby_clause_key() { + register_taxonomy( 'wptests_tax', 'post' ); + $terms = self::factory()->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) ); + add_term_meta( $terms[0], 'foo', 'zzz' ); + add_term_meta( $terms[0], 'fee', 'jjj' ); + add_term_meta( $terms[1], 'foo', 'aaa' ); + add_term_meta( $terms[1], 'fee', 'aaa' ); + add_term_meta( $terms[2], 'foo', 'jjj' ); + add_term_meta( $terms[2], 'fee', 'zzz' ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + 'foo_key' => array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + 'fee_key' => array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'orderby' => 'foo_key', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[2], $terms[0] ), $found ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + 'foo_key' => array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + 'fee_key' => array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'orderby' => 'fee_key', + 'order' => 'ASC', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( array( $terms[1], $terms[0], $terms[2] ), $found ); + + $expected = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + 'foo_key' => array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + 'fee_key' => array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'fields' => 'ids', + ) ); + + $found = get_terms( 'wptests_tax', array( + 'hide_empty' => false, + 'meta_query' => array( + 'relation' => 'AND', + 'foo_key' => array( + 'key' => 'foo', + 'compare' => 'EXISTS', + ), + 'fee_key' => array( + 'key' => 'fee', + 'compare' => 'EXISTS', + ), + ), + 'orderby' => 'faa_key', + 'fields' => 'ids', + ) ); + + $this->assertEqualSets( $expected, $found ); + } + public function test_hierarchical_false_with_parent() { $initial_terms = $this->create_hierarchical_terms();