From 7ec3d1e6f5621fa1bcd428021f61c3ffc7e5208c Mon Sep 17 00:00:00 2001 From: wppoland Date: Wed, 23 Sep 2026 08:48:44 +0200 Subject: [PATCH] Taxonomy: Add 's' as an alias of 'search' in WP_Term_Query. - Adds 's' as a shorthand alias for 'search' in WP_Term_Query, matching WP_Query convention. - Re-resolves 's' alias in get_terms() after get_terms_args filter to honor callbacks setting 's'. - Normalizes query vars in generate_cache_key() by unsetting 's' so equivalent queries share cache keys. - Adds comprehensive unit tests covering alias resolution, precedence, non-matching terms, pre_get_terms callback, and shared cache key generation. Props mukeshpanchal27, westonruter, wppoland. Fixes #51811. --- src/wp-includes/class-wp-term-query.php | 15 ++- src/wp-includes/taxonomy.php | 1 + tests/phpunit/tests/term/query.php | 158 ++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-term-query.php b/src/wp-includes/class-wp-term-query.php index 56c8585d7224e..be6bf0cc5fa21 100644 --- a/src/wp-includes/class-wp-term-query.php +++ b/src/wp-includes/class-wp-term-query.php @@ -92,6 +92,7 @@ class WP_Term_Query { * @since 5.1.0 Introduced the 'meta_compare_key' parameter. * @since 5.3.0 Introduced the 'meta_type_key' parameter. * @since 6.4.0 Introduced the 'cache_results' parameter. + * @since 7.2.0 Introduced the 's' parameter as an alias of 'search'. * * @param string|array $query { * Optional. Array or query string of term query parameters. Default empty. @@ -158,6 +159,7 @@ class WP_Term_Query { * (even if `$hide_empty` is set to true). Default true. * @type string $search Search criteria to match terms. Will be SQL-formatted with * wildcards before and after. Default empty. + * @type string $s Alias of `$search`. Default empty. * @type string $name__like Retrieve terms with criteria by which a term is LIKE * `$name__like`. Default empty. * @type string $description__like Retrieve terms where the description is LIKE @@ -211,6 +213,7 @@ public function __construct( $query = '' ) { 'term_taxonomy_id' => '', 'hierarchical' => true, 'search' => '', + 's' => '', 'name__like' => '', 'description__like' => '', 'pad_counts' => false, @@ -261,6 +264,11 @@ public function parse_query( $query = '' ) { $query = wp_parse_args( $query, $this->query_var_defaults ); + // 's' is a shorthand alias for 'search', matching WP_Query convention. + if ( '' === $query['search'] && '' !== $query['s'] ) { + $query['search'] = $query['s']; + } + $query['number'] = absint( $query['number'] ); $query['offset'] = absint( $query['offset'] ); @@ -425,6 +433,11 @@ public function get_terms() { */ $args = apply_filters( 'get_terms_args', $args, $taxonomies ); + // Re-resolve 's' alias in case it was modified in a 'pre_get_terms' callback or 'get_terms_args' filter. + if ( '' === $args['search'] && '' !== $args['s'] ) { + $args['search'] = $args['s']; + } + // Avoid the query if the queried parent/child_of term has no descendants. $child_of = $args['child_of']; $parent = $args['parent']; @@ -1182,7 +1195,7 @@ protected function generate_cache_key( array $args, $sql ) { // $args can be anything. Only use the args defined in defaults to compute the key. $cache_args = wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ); - unset( $cache_args['cache_results'], $cache_args['update_term_meta_cache'] ); + unset( $cache_args['cache_results'], $cache_args['update_term_meta_cache'], $cache_args['s'] ); if ( 'count' !== $args['fields'] && 'all_with_object_id' !== $args['fields'] ) { $cache_args['fields'] = 'all'; diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 6dbc336395f74..b0760a039eeb7 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -1308,6 +1308,7 @@ function get_term_to_edit( $id, $taxonomy ) { * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter. * Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata. * @since 4.8.0 Introduced 'suppress_filter' parameter. + * @since 7.2.0 Introduced the 's' parameter as an alias of 'search'. * * @param array|string $args Optional. Array or string of arguments. See {@see WP_Term_Query::__construct()} * for information on accepted arguments. Default empty array. diff --git a/tests/phpunit/tests/term/query.php b/tests/phpunit/tests/term/query.php index a62072af65bf3..78f2f4bc19f21 100644 --- a/tests/phpunit/tests/term/query.php +++ b/tests/phpunit/tests/term/query.php @@ -1254,4 +1254,162 @@ public function test_id_and_parent_fields_should_return_parent_ids_keyed_by_term $q2 = new WP_Term_Query(); $this->assertSame( $expected, $q2->query( $query_args ), 'Second query is not of the expected form.' ); } + + /** + * @ticket 51811 + * + * @covers WP_Term_Query::parse_query + */ + public function test_s_param_is_alias_for_search() { + register_taxonomy( 'wptests_tax', 'post' ); + + $term = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + 'name' => 'Unique Findable Term', + ) + ); + + $q = new WP_Term_Query( + array( + 'taxonomy' => 'wptests_tax', + 's' => 'Unique Findable', + 'hide_empty' => false, + 'fields' => 'ids', + ) + ); + + $this->assertContains( $term, $q->terms, 'The s parameter should work as an alias for search.' ); + } + + /** + * @ticket 51811 + * + * @covers WP_Term_Query::parse_query + */ + public function test_search_takes_precedence_over_s() { + register_taxonomy( 'wptests_tax', 'post' ); + + $term = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + 'name' => 'Precedence Test Term', + ) + ); + + $q = new WP_Term_Query( + array( + 'taxonomy' => 'wptests_tax', + 'search' => 'Precedence Test', + 's' => 'nonexistent', + 'hide_empty' => false, + 'fields' => 'ids', + ) + ); + + $this->assertContains( $term, $q->terms, 'The search parameter should take precedence over s.' ); + } + + /** + * @ticket 51811 + * + * @covers WP_Term_Query::parse_query + */ + public function test_s_param_returns_empty_when_no_match() { + register_taxonomy( 'wptests_tax', 'post' ); + + self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + 'name' => 'Existing Non Matching Term', + ) + ); + + $q = new WP_Term_Query( + array( + 'taxonomy' => 'wptests_tax', + 's' => 'absolutelynonexistentterm', + 'hide_empty' => false, + 'fields' => 'ids', + ) + ); + + $this->assertEmpty( $q->terms, 'The s parameter should return empty for non-matching terms.' ); + } + + /** + * @ticket 51811 + * + * @covers WP_Term_Query::get_terms + */ + public function test_s_param_can_be_set_in_pre_get_terms() { + register_taxonomy( 'wptests_tax', 'post' ); + + $term1 = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + 'name' => 'Apple Term', + ) + ); + self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + 'name' => 'Banana Term', + ) + ); + + $callback = static function ( $query ) { + $query->query_vars['s'] = 'Apple'; + }; + + add_action( 'pre_get_terms', $callback ); + + $q = new WP_Term_Query( + array( + 'taxonomy' => 'wptests_tax', + 'hide_empty' => false, + 'fields' => 'ids', + ) + ); + + remove_action( 'pre_get_terms', $callback ); + + $this->assertSame( array( $term1 ), $q->terms, "Setting 's' in pre_get_terms should filter terms." ); + } + + /** + * @ticket 51811 + * + * @covers WP_Term_Query::generate_cache_key + */ + public function test_s_and_search_produce_identical_cache_key() { + register_taxonomy( 'wptests_tax', 'post' ); + + $q1 = new WP_Term_Query(); + $q1->query_vars = array( + 'taxonomy' => 'wptests_tax', + 'search' => 'Match', + 'cache_results' => true, + ); + $q1->parse_query( $q1->query_vars ); + + $reflection = new ReflectionMethod( $q1, 'generate_cache_key' ); + if ( PHP_VERSION_ID < 80100 ) { + $reflection->setAccessible( true ); + } + + $key1 = $reflection->invoke( $q1, $q1->query_vars, 'SELECT * FROM wp_terms' ); + + $q2 = new WP_Term_Query(); + $q2->query_vars = array( + 'taxonomy' => 'wptests_tax', + 's' => 'Match', + 'cache_results' => true, + ); + $q2->parse_query( $q2->query_vars ); + + $key2 = $reflection->invoke( $q2, $q2->query_vars, 'SELECT * FROM wp_terms' ); + + $this->assertSame( $key1, $key2, 'Queries using search and s should produce identical cache keys.' ); + } }