diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index d6afdef470af2..11bc499fc6720 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -337,6 +337,13 @@ public function get_items( $request ) { } } + if ( + isset( $registered['search_semantics'], $request['search_semantics'] ) + && 'exact' === $request['search_semantics'] + ) { + $args['exact'] = true; + } + $args = $this->prepare_tax_query( $args, $request ); // Force the post_type argument, since it's not a user input variable. @@ -2886,6 +2893,12 @@ public function get_collection_params() { ); } + $query_params['search_semantics'] = array( + 'description' => __( 'How to interpret the search input.' ), + 'type' => 'string', + 'enum' => array( 'exact' ), + ); + $query_params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 737c32b8e1ab1..55ea686d22f25 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -229,6 +229,7 @@ public function test_registered_query_params() { 'per_page', 'search', 'search_columns', + 'search_semantics', 'slug', 'status', ), diff --git a/tests/phpunit/tests/rest-api/rest-pages-controller.php b/tests/phpunit/tests/rest-api/rest-pages-controller.php index 209229256a11f..9717a7fcda1c6 100644 --- a/tests/phpunit/tests/rest-api/rest-pages-controller.php +++ b/tests/phpunit/tests/rest-api/rest-pages-controller.php @@ -85,6 +85,7 @@ public function test_registered_query_params() { 'per_page', 'search', 'search_columns', + 'search_semantics', 'slug', 'status', ), diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 8b81b9f648651..3085d066fc6ae 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -206,6 +206,7 @@ public function test_registered_query_params() { 'per_page', 'search', 'search_columns', + 'search_semantics', 'slug', 'status', 'sticky', @@ -765,6 +766,64 @@ public function test_get_items_status_without_permissions() { } } + /** + * @ticket 56350 + * + * @dataProvider data_get_items_exact_search + * + * @param string $search_term The search term. + * @param bool $exact_search Whether the search is an exact or general search. + * @param int $expected The expected number of matching posts. + */ + public function test_get_items_exact_search( $search_term, $exact_search, $expected ) { + self::factory()->post->create( + array( + 'post_title' => 'Rye', + 'post_content' => 'This is a post about Rye Bread', + ) + ); + + self::factory()->post->create( + array( + 'post_title' => 'Types of Bread', + 'post_content' => 'Types of bread are White and Rye Bread', + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); + $request['search'] = $search_term; + if ( $exact_search ) { + $request['search_semantics'] = 'exact'; + } + $response = rest_get_server()->dispatch( $request ); + $this->assertCount( $expected, $response->get_data() ); + } + + /** + * Data provider for test_get_items_exact_search(). + * + * @return array[] + */ + public function data_get_items_exact_search() { + return array( + 'general search, one exact match and one partial match' => array( + 'search_term' => 'Rye', + 'exact_search' => false, + 'expected' => 2, + ), + 'exact search, one exact match and one partial match' => array( + 'search_term' => 'Rye', + 'exact_search' => true, + 'expected' => 1, + ), + 'exact search, no match and one partial match' => array( + 'search_term' => 'Rye Bread', + 'exact_search' => true, + 'expected' => 0, + ), + ); + } + public function test_get_items_order_and_orderby() { self::factory()->post->create( array( diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 7e97f19b18588..e674a12b4ea08 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -362,6 +362,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -1719,6 +1727,14 @@ mockedApiResponse.Schema = { "type": "integer", "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -2820,6 +2836,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -3571,6 +3595,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -4382,6 +4414,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -6995,6 +7035,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -7812,6 +7860,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer", @@ -8017,6 +8073,14 @@ mockedApiResponse.Schema = { "default": [], "required": false }, + "search_semantics": { + "description": "How to interpret the search input.", + "type": "string", + "enum": [ + "exact" + ], + "required": false + }, "offset": { "description": "Offset the result set by a specific number of items.", "type": "integer",