diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 5bf488755114c..58cea60704211 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -299,6 +299,7 @@ function create_initial_rest_routes() { new WP_REST_Post_Search_Handler(), new WP_REST_Term_Search_Handler(), new WP_REST_Post_Format_Search_Handler(), + new WP_REST_Media_Search_Handler(), ); /** diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php index 55fe1ad63ae7a..b4029f957250f 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php @@ -41,6 +41,11 @@ class WP_REST_Search_Controller extends WP_REST_Controller { */ const PROP_SUBTYPE = 'subtype'; + /** + * Label property name. + */ + const PROP_LABEL = 'label'; + /** * Identifier for the 'any' type. */ @@ -283,6 +288,12 @@ public function get_item_schema() { 'context' => array( 'view', 'embed' ), 'readonly' => true, ), + self::PROP_LABEL => array( + 'description' => __( 'Object human readable subtype.' ), + 'type' => 'string', + 'context' => array( 'view', 'embed' ), + 'readonly' => true, + ), ), ); diff --git a/src/wp-includes/rest-api/search/class-wp-rest-media-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-media-search-handler.php new file mode 100644 index 0000000000000..0eca4cad5e5ae --- /dev/null +++ b/src/wp-includes/rest-api/search/class-wp-rest-media-search-handler.php @@ -0,0 +1,109 @@ +type = 'media'; + $this->subtypes = array(); + } + + /** + * Searches the object type content for a given search request. + * + * @param WP_REST_Request $request Full REST request. + * + * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing + * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the + * total count for the matching search results. + * @since 6.6.0 + * + */ + public function search_items( WP_REST_Request $request ) { + + $query_args = array( + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'paged' => (int) $request['page'], + 'posts_per_page' => (int) $request['per_page'], + ); + + if ( ! empty( $request['search'] ) ) { + $query_args['s'] = $request['search']; + + // Filter query clauses to include filenames. + add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); + } + + if ( ! empty( $request['exclude'] ) ) { + $query_args['post__not_in'] = $request['exclude']; + } + + if ( ! empty( $request['include'] ) ) { + $query_args['post__in'] = $request['include']; + } + + /** + * Filters the query arguments for a REST API search request. + * + * Enables adding extra arguments or setting defaults for a media search request. + * + * @param array $query_args Key value array of query var to query value. + * @param WP_REST_Request $request The request used. + * + * @since 6.6.0 + * + */ + $query_args = apply_filters( 'rest_media_search_query', $query_args, $request ); + + $query = new WP_Query(); + $posts = $query->query( $query_args ); + // Querying the whole post object will warm the object cache, avoiding an extra query per result. + $found_ids = wp_list_pluck( $posts, 'ID' ); + $total = $query->found_posts; + + return array( + self::RESULT_IDS => $found_ids, + self::RESULT_TOTAL => $total, + ); + } + + /** + * Prepares the search result for a given ID. + * + * @param int $id Item ID. + * @param array $fields Fields to include for the item. + * + * @return array Associative array containing all fields for the item. + * @since 6.6.0 + * + */ + public function prepare_item( $id, array $fields ) { + $data = parent::prepare_item( $id, $fields ); + + if ( isset( $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] ) ) { + unset( $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] ); + } + + return $data; + } +} diff --git a/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php index 8d815b1491b70..e3666510b883b 100644 --- a/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php +++ b/src/wp-includes/rest-api/search/class-wp-rest-post-format-search-handler.php @@ -121,6 +121,10 @@ public function prepare_item( $id, array $fields ) { $data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type; } + if ( in_array( WP_REST_Search_Controller::PROP_LABEL, $fields, true ) ) { + $data[ WP_REST_Search_Controller::PROP_LABEL ] = get_post_format_string( $id ); + } + return $data; } diff --git a/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php index 1d47723b41488..230fa73a43d31 100644 --- a/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php +++ b/src/wp-includes/rest-api/search/class-wp-rest-post-search-handler.php @@ -151,6 +151,12 @@ public function prepare_item( $id, array $fields ) { $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type; } + if ( in_array( WP_REST_Search_Controller::PROP_LABEL, $fields, true ) ) { + $ptype = get_post_type_object( $post->post_type ); + $label = $ptype ? $ptype->labels->singular_name : $post->post_type; + $data[ WP_REST_Search_Controller::PROP_LABEL ] = $label; + } + return $data; } diff --git a/src/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php b/src/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php index 6527697208bbb..8507e00802389 100644 --- a/src/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php +++ b/src/wp-includes/rest-api/search/class-wp-rest-term-search-handler.php @@ -143,6 +143,12 @@ public function prepare_item( $id, array $fields ) { $data[ WP_REST_Search_Controller::PROP_TYPE ] = $term->taxonomy; } + if ( in_array( WP_REST_Search_Controller::PROP_LABEL, $fields, true ) ) { + $taxonomy = get_taxonomy( $term->taxonomy ); + $label = $taxonomy ? $taxonomy->labels->singular_name : $term->taxonomy; + $data[ WP_REST_Search_Controller::PROP_LABEL ] = $label; + } + return $data; } diff --git a/src/wp-settings.php b/src/wp-settings.php index 9673479bfab76..5e28be48ca7c3 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -328,6 +328,7 @@ require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-post-search-handler.php'; require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-term-search-handler.php'; require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-post-format-search-handler.php'; +require ABSPATH . WPINC . '/rest-api/search/class-wp-rest-media-search-handler.php'; require ABSPATH . WPINC . '/sitemaps.php'; require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps.php'; require ABSPATH . WPINC . '/sitemaps/class-wp-sitemaps-index.php'; diff --git a/tests/phpunit/includes/class-wp-rest-test-search-handler.php b/tests/phpunit/includes/class-wp-rest-test-search-handler.php index 10a686972b738..ff25f1f00cac8 100644 --- a/tests/phpunit/includes/class-wp-rest-test-search-handler.php +++ b/tests/phpunit/includes/class-wp-rest-test-search-handler.php @@ -16,17 +16,28 @@ class WP_REST_Test_Search_Handler extends WP_REST_Search_Handler { public function __construct( $amount = 10 ) { $this->type = 'test'; - $this->subtypes = array( 'test_first_type', 'test_second_type' ); + $this->subtypes = array( 'test_first_type', 'test_second_type' ); + $test_first_type_object = new \WP_Post_Type( + 'test_first_type', + array( + 'labels' => array( + 'name' => 'Test first types', + 'singular_name' => 'Test first type', + ), + ) + ); $this->items = array(); for ( $i = 1; $i <= $amount; $i++ ) { - $subtype = $i > $amount / 2 ? 'test_second_type' : 'test_first_type'; + $subtype = $i > $amount / 2 ? 'test_second_type' : 'test_first_type'; + $subtype_object = $i > $amount / 2 ? false : $test_first_type_object; $this->items[ $i ] = (object) array( 'test_id' => $i, 'test_title' => sprintf( 'Title %d', $i ), 'test_url' => sprintf( home_url( '/tests/%d' ), $i ), 'test_type' => $subtype, + 'test_label' => $subtype_object ? $subtype_object->labels->singular_name : $subtype, ); } } @@ -82,6 +93,10 @@ public function prepare_item( $id, array $fields ) { $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $test->test_type; } + if ( in_array( WP_REST_Search_Controller::PROP_LABEL, $fields, true ) ) { + $data[ WP_REST_Search_Controller::PROP_LABEL ] = $test->test_label; + } + return $data; } diff --git a/tests/phpunit/tests/rest-api/rest-search-controller.php b/tests/phpunit/tests/rest-api/rest-search-controller.php index 668cfb4e6bdeb..9ed60968c26da 100644 --- a/tests/phpunit/tests/rest-api/rest-search-controller.php +++ b/tests/phpunit/tests/rest-api/rest-search-controller.php @@ -30,6 +30,13 @@ class WP_Test_REST_Search_Controller extends WP_Test_REST_Controller_Testcase { */ private static $my_content_post_ids = array(); + /** + * Attachments + * + * @var array + */ + private static $my_attachment_ids; + /** * Categories. * @@ -90,6 +97,13 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'name' => 'Test Tag', ) ); + + self::$my_attachment_ids = $factory->attachment->create_many( + 4, + array( + 'post_title' => 'my-footitle', + ) + ); } /** @@ -413,6 +427,7 @@ public function test_prepare_item() { 'url', 'type', 'subtype', + 'label', '_links', ), array_keys( $data[0] ) @@ -458,6 +473,7 @@ public function test_get_item_schema() { $this->assertArrayHasKey( 'url', $properties ); $this->assertArrayHasKey( 'type', $properties ); $this->assertArrayHasKey( 'subtype', $properties ); + $this->assertArrayHasKey( 'label', $properties ); } /** @@ -523,6 +539,7 @@ public function test_custom_search_handler_prepare_item() { 'url', 'type', 'subtype', + 'label', ), array_keys( $data ) ); @@ -902,4 +919,69 @@ public function test_sanitize_subtypes_validates_type() { $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); } + + /** + * Search through attachments. + * + * @ticket 61254 + */ + public function test_get_items_search_type_media() { + $response = $this->do_request_with_params( + array( + 'per_page' => 100, + 'type' => 'media', + ) + ); + $this->assertSame( 200, $response->get_status() ); + $this->assertSameSets( + self::$my_attachment_ids, + wp_list_pluck( $response->get_data(), 'id' ) + ); + } + + /** + * @ticket 61254 + */ + public function test_label_field_contain_the_post_type_singular_name() { + $response = $this->do_request_with_params( + array( + 'subtype' => 'page', + 'per_page' => 1, + ) + ); + + $response_data = $response->get_data(); + $this->assertSame( 'Page', $response_data[0]['label'] ); + } + + /** + * @ticket 61254 + */ + public function test_label_field_contain_the_taxonomy_name() { + $response = $this->do_request_with_params( + array( + 'type' => 'term', + 'subtype' => 'category', + 'per_page' => 1, + ) + ); + + $response_data = $response->get_data(); + $this->assertSame( 'Category', $response_data[0]['label'] ); + } + + /** + * @ticket 61254 + */ + public function test_label_field_contain_the_post_format_name() { + $response = $this->do_request_with_params( + array( + 'type' => 'post-format', + 'per_page' => 1, + ) + ); + + $response_data = $response->get_data(); + $this->assertSame( 'Aside', $response_data[0]['label'] ); + } } diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 928ea25e08f89..f4e5684bec8eb 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -10202,7 +10202,8 @@ mockedApiResponse.Schema = { "enum": [ "post", "term", - "post-format" + "post-format", + "media" ], "required": false },