diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 9ec818298aff9..faeb01c93904a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -70,6 +70,7 @@ public function register_routes() { * prepares for WP_Query. * * @since 4.7.0 + * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. * * @param array $prepared_args Optional. Array of prepared arguments. Default empty array. * @param WP_REST_Request $request Optional. Request to prepare items for. @@ -82,19 +83,30 @@ protected function prepare_items_query( $prepared_args = array(), $request = nul $query_args['post_status'] = 'inherit'; } - $media_types = $this->get_media_types(); + $all_mime_types = array(); + $media_types = $this->get_media_types(); - if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) { - $query_args['post_mime_type'] = $media_types[ $request['media_type'] ]; + if ( ! empty( $request['media_type'] ) && is_array( $request['media_type'] ) ) { + foreach ( $request['media_type'] as $type ) { + if ( isset( $media_types[ $type ] ) ) { + $all_mime_types = array_merge( $all_mime_types, $media_types[ $type ] ); + } + } } - if ( ! empty( $request['mime_type'] ) ) { - $parts = explode( '/', $request['mime_type'] ); - if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) { - $query_args['post_mime_type'] = $request['mime_type']; + if ( ! empty( $request['mime_type'] ) && is_array( $request['mime_type'] ) ) { + foreach ( $request['mime_type'] as $mime_type ) { + $parts = explode( '/', $mime_type ); + if ( isset( $media_types[ $parts[0] ] ) && in_array( $mime_type, $media_types[ $parts[0] ], true ) ) { + $all_mime_types[] = $mime_type; + } } } + if ( ! empty( $all_mime_types ) ) { + $query_args['post_mime_type'] = array_values( array_unique( $all_mime_types ) ); + } + // Filter query clauses to include filenames. if ( isset( $query_args['s'] ) ) { add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); @@ -1342,6 +1354,7 @@ public static function get_filename_from_disposition( $disposition_header ) { * Retrieves the query params for collections of attachments. * * @since 4.7.0 + * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. * * @return array Query parameters for the attachment collection as an array. */ @@ -1349,19 +1362,25 @@ public function get_collection_params() { $params = parent::get_collection_params(); $params['status']['default'] = 'inherit'; $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' ); - $media_types = $this->get_media_types(); + $media_types = array_keys( $this->get_media_types() ); $params['media_type'] = array( 'default' => null, - 'description' => __( 'Limit result set to attachments of a particular media type.' ), - 'type' => 'string', - 'enum' => array_keys( $media_types ), + 'description' => __( 'Limit result set to attachments of a particular media type or media types.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + 'enum' => $media_types, + ), ); $params['mime_type'] = array( 'default' => null, - 'description' => __( 'Limit result set to attachments of a particular MIME type.' ), - 'type' => 'string', + 'description' => __( 'Limit result set to attachments of a particular MIME type or MIME types.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), ); return $params; diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 31acd0b56355a..bc1ca41b578ee 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -37,6 +37,21 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control */ private static $test_svg_file; + /** + * @var string The path to the test video. + */ + private static $test_video_file; + + /** + * @var string The path to the test audio. + */ + private static $test_audio_file; + + /** + * @var string The path to the test RTF file. + */ + private static $test_rtf_file; + /** * @var array The recorded posts query clauses. */ @@ -85,6 +100,15 @@ public static function wpTearDownAfterClass() { if ( file_exists( self::$test_avif_file ) ) { unlink( self::$test_avif_file ); } + if ( file_exists( self::$test_video_file ) ) { + unlink( self::$test_video_file ); + } + if ( file_exists( self::$test_audio_file ) ) { + unlink( self::$test_audio_file ); + } + if ( file_exists( self::$test_rtf_file ) ) { + unlink( self::$test_rtf_file ); + } self::delete_user( self::$editor_id ); self::delete_user( self::$author_id ); @@ -126,6 +150,24 @@ public function set_up() { copy( $test_svg_file, self::$test_svg_file ); } + $test_video_file = DIR_TESTDATA . '/uploads/small-video.mp4'; + self::$test_video_file = get_temp_dir() . 'small-video.mp4'; + if ( ! file_exists( self::$test_video_file ) ) { + copy( $test_video_file, self::$test_video_file ); + } + + $test_audio_file = DIR_TESTDATA . '/uploads/small-audio.mp3'; + self::$test_audio_file = get_temp_dir() . 'small-audio.mp3'; + if ( ! file_exists( self::$test_audio_file ) ) { + copy( $test_audio_file, self::$test_audio_file ); + } + + $test_rtf_file = DIR_TESTDATA . '/uploads/test.rtf'; + self::$test_rtf_file = get_temp_dir() . 'test.rtf'; + if ( ! file_exists( self::$test_rtf_file ) ) { + copy( $test_rtf_file, self::$test_rtf_file ); + } + add_filter( 'rest_pre_dispatch', array( $this, 'wpSetUpBeforeRequest' ), 10, 3 ); add_filter( 'posts_clauses', array( $this, 'save_posts_clauses' ), 10, 2 ); } @@ -267,7 +309,7 @@ public function test_registered_query_params() { 'audio', 'text', ); - $this->assertSameSets( $media_types, $data['endpoints'][0]['args']['media_type']['enum'] ); + $this->assertSameSets( $media_types, $data['endpoints'][0]['args']['media_type']['items']['enum'] ); } public function test_registered_get_item_params() { @@ -418,6 +460,233 @@ public function test_get_items_media_type() { $this->assertSame( $id1, $data[0]['id'] ); } + /** + * Test multiple media types support with various input formats. + * + * @ticket 63668 + */ + public function test_get_items_multiple_media_types() { + $image_id = self::factory()->attachment->create_object( + self::$test_file, + 0, + array( + 'post_mime_type' => 'image/jpeg', + ) + ); + + $video_id = self::factory()->attachment->create_object( + self::$test_video_file, + 0, + array( + 'post_mime_type' => 'video/mp4', + ) + ); + + $audio_id = self::factory()->attachment->create_object( + self::$test_audio_file, + 0, + array( + 'post_mime_type' => 'audio/mpeg', + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + + // Test single media type. + $request->set_param( 'media_type', 'image' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 1, $data, 'Response count for single media type is not 1' ); + $this->assertSame( $image_id, $data[0]['id'], 'Image ID not found in response for single media type' ); + + // Test multiple media types with comma-separated string. + $request->set_param( 'media_type', 'image,video' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 2, $data, 'Response count for multiple media types with comma-separated string is not 2' ); + $ids = wp_list_pluck( $data, 'id' ); + $this->assertContains( $image_id, $ids, 'Image ID not found in response for multiple media types with comma-separated string' ); + $this->assertContains( $video_id, $ids, 'Video ID not found in response for multiple media types with comma-separated string' ); + $this->assertNotContains( $audio_id, $ids, 'Audio ID found in response for multiple media types with comma-separated string' ); + + // Test multiple media types with array format. + $request->set_param( 'media_type', array( 'image', 'video', 'audio' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 3, $data, 'Response count for multiple media types with array format is not 3' ); + $ids = wp_list_pluck( $data, 'id' ); + $this->assertContains( $image_id, $ids, 'Image ID not found in response for multiple media types with array format' ); + $this->assertContains( $video_id, $ids, 'Video ID not found in response for multiple media types with array format' ); + $this->assertContains( $audio_id, $ids, 'Audio ID not found in response for multiple media types with array format' ); + + // Test invalid media type mixed with valid ones. + $request->set_param( 'media_type', 'image,invalid,video' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); + } + + /** + * Test multiple MIME types support and combination with media types. + * + * @ticket 63668 + */ + public function test_get_items_multiple_mime_types_and_combination() { + $jpeg_id = self::factory()->attachment->create_object( + self::$test_file, + 0, + array( + 'post_mime_type' => 'image/jpeg', + ) + ); + + $png_id = self::factory()->attachment->create_object( + self::$test_file2, + 0, + array( + 'post_mime_type' => 'image/png', + ) + ); + + $mp4_id = self::factory()->attachment->create_object( + self::$test_video_file, + 0, + array( + 'post_mime_type' => 'video/mp4', + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + + // Test single MIME type + $request->set_param( 'mime_type', 'image/jpeg' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 1, $data, 'Response count for single MIME type is not 1' ); + $this->assertSame( $jpeg_id, $data[0]['id'], 'JPEG ID not found in response for single MIME type' ); + + // Test multiple MIME types with comma-separated string. + $request->set_param( 'mime_type', 'image/jpeg,image/png' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 2, $data, 'Response count for multiple MIME types with comma-separated string is not 2' ); + $ids = wp_list_pluck( $data, 'id' ); + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response for multiple MIME types with comma-separated string' ); + $this->assertContains( $png_id, $ids, 'PNG ID not found in response for multiple MIME types with comma-separated string' ); + + // Test multiple MIME types with array format. + $request->set_param( 'mime_type', array( 'image/jpeg', 'video/mp4' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 2, $data, 'Response count for multiple MIME types with array format is not 2' ); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response for multiple MIME types with array format' ); + $this->assertContains( $mp4_id, $ids, 'MP4 ID not found in response for multiple MIME types with array format' ); + } + + /** + * Test combination of media type and mime type parameters. + * + * @ticket 63668 + */ + public function test_get_items_with_media_type_and_media_types() { + $audio_id = self::factory()->attachment->create_object( + self::$test_audio_file, + 0, + array( + 'post_mime_type' => 'audio/mpeg', + 'post_excerpt' => 'A sample caption', + ) + ); + + $jpeg_id = self::factory()->attachment->create_object( + self::$test_file, + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'post_excerpt' => 'A sample caption', + ) + ); + + $png_id = self::factory()->attachment->create_object( + self::$test_file2, + 0, + array( + 'post_mime_type' => 'image/png', + ) + ); + + $video_id = self::factory()->attachment->create_object( + self::$test_video_file, + 0, + array( + 'post_mime_type' => 'video/mp4', + ) + ); + + $rtf_id = self::factory()->attachment->create_object( + self::$test_rtf_file, + 0, + array( + 'post_mime_type' => 'application/rtf', + ) + ); + + // Test combination of single media type and single mime type parameters. + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + $request->set_param( 'media_type', 'image' ); + $request->set_param( 'mime_type', 'audio/mpeg' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertCount( 3, $data, 'Response count for combination of single media type and single mime type parameters is not 3' ); + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' ); + $this->assertContains( $png_id, $ids, 'PNG ID not found in response' ); + $this->assertContains( $audio_id, $ids, 'Audio ID found in response' ); + + // Test combination of single media type and multiple mime type parameters. + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + $request->set_param( 'media_type', 'audio' ); + $request->set_param( 'mime_type', array( 'image/jpeg', 'image/png' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertCount( 3, $data, 'Response count for combination of single media type and multiple mime type parameters is not 3' ); + $this->assertContains( $audio_id, $ids, 'Audio ID not found in response' ); + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' ); + $this->assertContains( $png_id, $ids, 'PNG ID not found in response' ); + + // Test combination of multiple media types and single mime type parameters. + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + $request->set_param( 'media_type', 'audio,video' ); + $request->set_param( 'mime_type', array( 'image/jpeg' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertCount( 3, $data, 'Response count for combination of multiple media type and multiple mime type parameters is not 3' ); + $this->assertContains( $audio_id, $ids, 'Audio ID not found in response' ); + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' ); + $this->assertContains( $video_id, $ids, 'Video ID not found in response' ); + + // Test combination of multiple media types and multiple mime type parameters. + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + $request->set_param( 'media_type', 'audio,video' ); + $request->set_param( 'mime_type', array( 'image/jpeg', 'image/png', 'application/rtf' ) ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertCount( 5, $data, 'Response count for combination of multiple media type and multiple mime type parameters is not 3' ); + $this->assertContains( $audio_id, $ids, 'Audio ID not found in response' ); + $this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' ); + $this->assertContains( $video_id, $ids, 'Video ID not found in response' ); + $this->assertContains( $png_id, $ids, 'PNG ID not found in response' ); + $this->assertContains( $rtf_id, $ids, 'RTF ID not found in response' ); + } + public function test_get_items_mime_type() { $id1 = self::factory()->attachment->create_object( self::$test_file, @@ -2685,6 +2954,7 @@ public function test_upload_svg_image() { /** * Tests that the attachment fields caption, description, and title, post and alt_text are updated correctly. + * * @ticket 64035 * @requires function imagejpeg */ diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 19405f0567924..bc9ea0a2dc424 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -2963,21 +2963,27 @@ mockedApiResponse.Schema = { }, "media_type": { "default": null, - "description": "Limit result set to attachments of a particular media type.", - "type": "string", - "enum": [ - "image", - "video", - "text", - "application", - "audio" - ], + "description": "Limit result set to attachments of a particular media type or media types.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "image", + "video", + "text", + "application", + "audio" + ] + }, "required": false }, "mime_type": { "default": null, - "description": "Limit result set to attachments of a particular MIME type.", - "type": "string", + "description": "Limit result set to attachments of a particular MIME type or MIME types.", + "type": "array", + "items": { + "type": "string" + }, "required": false } }