Skip to content

Commit 2a01f8f

Browse files
committed
Attachments REST API endpoint: add support for filtering attachments by multiple media types
This patch enhances the REST API media endpoint to allow filtering by multiple values for the media_type and mime_type parameters. String, comma-separated values and array are supported. Props abcd95, ramonopoly, andrewserong, mukesh27, adamsilverstein, timothyblynjacobs, swissspidy. Fixes #63668. git-svn-id: https://develop.svn.wordpress.org/trunk@60917 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 692a5b1 commit 2a01f8f

File tree

3 files changed

+320
-25
lines changed

3 files changed

+320
-25
lines changed

src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public function register_routes() {
7070
* prepares for WP_Query.
7171
*
7272
* @since 4.7.0
73+
* @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values.
7374
*
7475
* @param array $prepared_args Optional. Array of prepared arguments. Default empty array.
7576
* @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
8283
$query_args['post_status'] = 'inherit';
8384
}
8485

85-
$media_types = $this->get_media_types();
86+
$all_mime_types = array();
87+
$media_types = $this->get_media_types();
8688

87-
if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) {
88-
$query_args['post_mime_type'] = $media_types[ $request['media_type'] ];
89+
if ( ! empty( $request['media_type'] ) && is_array( $request['media_type'] ) ) {
90+
foreach ( $request['media_type'] as $type ) {
91+
if ( isset( $media_types[ $type ] ) ) {
92+
$all_mime_types = array_merge( $all_mime_types, $media_types[ $type ] );
93+
}
94+
}
8995
}
9096

91-
if ( ! empty( $request['mime_type'] ) ) {
92-
$parts = explode( '/', $request['mime_type'] );
93-
if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) {
94-
$query_args['post_mime_type'] = $request['mime_type'];
97+
if ( ! empty( $request['mime_type'] ) && is_array( $request['mime_type'] ) ) {
98+
foreach ( $request['mime_type'] as $mime_type ) {
99+
$parts = explode( '/', $mime_type );
100+
if ( isset( $media_types[ $parts[0] ] ) && in_array( $mime_type, $media_types[ $parts[0] ], true ) ) {
101+
$all_mime_types[] = $mime_type;
102+
}
95103
}
96104
}
97105

106+
if ( ! empty( $all_mime_types ) ) {
107+
$query_args['post_mime_type'] = array_values( array_unique( $all_mime_types ) );
108+
}
109+
98110
// Filter query clauses to include filenames.
99111
if ( isset( $query_args['s'] ) ) {
100112
add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
@@ -1342,26 +1354,33 @@ public static function get_filename_from_disposition( $disposition_header ) {
13421354
* Retrieves the query params for collections of attachments.
13431355
*
13441356
* @since 4.7.0
1357+
* @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values.
13451358
*
13461359
* @return array Query parameters for the attachment collection as an array.
13471360
*/
13481361
public function get_collection_params() {
13491362
$params = parent::get_collection_params();
13501363
$params['status']['default'] = 'inherit';
13511364
$params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' );
1352-
$media_types = $this->get_media_types();
1365+
$media_types = array_keys( $this->get_media_types() );
13531366

13541367
$params['media_type'] = array(
13551368
'default' => null,
1356-
'description' => __( 'Limit result set to attachments of a particular media type.' ),
1357-
'type' => 'string',
1358-
'enum' => array_keys( $media_types ),
1369+
'description' => __( 'Limit result set to attachments of a particular media type or media types.' ),
1370+
'type' => 'array',
1371+
'items' => array(
1372+
'type' => 'string',
1373+
'enum' => $media_types,
1374+
),
13591375
);
13601376

13611377
$params['mime_type'] = array(
13621378
'default' => null,
1363-
'description' => __( 'Limit result set to attachments of a particular MIME type.' ),
1364-
'type' => 'string',
1379+
'description' => __( 'Limit result set to attachments of a particular MIME type or MIME types.' ),
1380+
'type' => 'array',
1381+
'items' => array(
1382+
'type' => 'string',
1383+
),
13651384
);
13661385

13671386
return $params;

tests/phpunit/tests/rest-api/rest-attachments-controller.php

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control
3737
*/
3838
private static $test_svg_file;
3939

40+
/**
41+
* @var string The path to the test video.
42+
*/
43+
private static $test_video_file;
44+
45+
/**
46+
* @var string The path to the test audio.
47+
*/
48+
private static $test_audio_file;
49+
50+
/**
51+
* @var string The path to the test RTF file.
52+
*/
53+
private static $test_rtf_file;
54+
4055
/**
4156
* @var array The recorded posts query clauses.
4257
*/
@@ -85,6 +100,15 @@ public static function wpTearDownAfterClass() {
85100
if ( file_exists( self::$test_avif_file ) ) {
86101
unlink( self::$test_avif_file );
87102
}
103+
if ( file_exists( self::$test_video_file ) ) {
104+
unlink( self::$test_video_file );
105+
}
106+
if ( file_exists( self::$test_audio_file ) ) {
107+
unlink( self::$test_audio_file );
108+
}
109+
if ( file_exists( self::$test_rtf_file ) ) {
110+
unlink( self::$test_rtf_file );
111+
}
88112

89113
self::delete_user( self::$editor_id );
90114
self::delete_user( self::$author_id );
@@ -126,6 +150,24 @@ public function set_up() {
126150
copy( $test_svg_file, self::$test_svg_file );
127151
}
128152

153+
$test_video_file = DIR_TESTDATA . '/uploads/small-video.mp4';
154+
self::$test_video_file = get_temp_dir() . 'small-video.mp4';
155+
if ( ! file_exists( self::$test_video_file ) ) {
156+
copy( $test_video_file, self::$test_video_file );
157+
}
158+
159+
$test_audio_file = DIR_TESTDATA . '/uploads/small-audio.mp3';
160+
self::$test_audio_file = get_temp_dir() . 'small-audio.mp3';
161+
if ( ! file_exists( self::$test_audio_file ) ) {
162+
copy( $test_audio_file, self::$test_audio_file );
163+
}
164+
165+
$test_rtf_file = DIR_TESTDATA . '/uploads/test.rtf';
166+
self::$test_rtf_file = get_temp_dir() . 'test.rtf';
167+
if ( ! file_exists( self::$test_rtf_file ) ) {
168+
copy( $test_rtf_file, self::$test_rtf_file );
169+
}
170+
129171
add_filter( 'rest_pre_dispatch', array( $this, 'wpSetUpBeforeRequest' ), 10, 3 );
130172
add_filter( 'posts_clauses', array( $this, 'save_posts_clauses' ), 10, 2 );
131173
}
@@ -267,7 +309,7 @@ public function test_registered_query_params() {
267309
'audio',
268310
'text',
269311
);
270-
$this->assertSameSets( $media_types, $data['endpoints'][0]['args']['media_type']['enum'] );
312+
$this->assertSameSets( $media_types, $data['endpoints'][0]['args']['media_type']['items']['enum'] );
271313
}
272314

273315
public function test_registered_get_item_params() {
@@ -418,6 +460,233 @@ public function test_get_items_media_type() {
418460
$this->assertSame( $id1, $data[0]['id'] );
419461
}
420462

463+
/**
464+
* Test multiple media types support with various input formats.
465+
*
466+
* @ticket 63668
467+
*/
468+
public function test_get_items_multiple_media_types() {
469+
$image_id = self::factory()->attachment->create_object(
470+
self::$test_file,
471+
0,
472+
array(
473+
'post_mime_type' => 'image/jpeg',
474+
)
475+
);
476+
477+
$video_id = self::factory()->attachment->create_object(
478+
self::$test_video_file,
479+
0,
480+
array(
481+
'post_mime_type' => 'video/mp4',
482+
)
483+
);
484+
485+
$audio_id = self::factory()->attachment->create_object(
486+
self::$test_audio_file,
487+
0,
488+
array(
489+
'post_mime_type' => 'audio/mpeg',
490+
)
491+
);
492+
493+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
494+
495+
// Test single media type.
496+
$request->set_param( 'media_type', 'image' );
497+
$response = rest_get_server()->dispatch( $request );
498+
$data = $response->get_data();
499+
$this->assertCount( 1, $data, 'Response count for single media type is not 1' );
500+
$this->assertSame( $image_id, $data[0]['id'], 'Image ID not found in response for single media type' );
501+
502+
// Test multiple media types with comma-separated string.
503+
$request->set_param( 'media_type', 'image,video' );
504+
$response = rest_get_server()->dispatch( $request );
505+
$data = $response->get_data();
506+
$this->assertCount( 2, $data, 'Response count for multiple media types with comma-separated string is not 2' );
507+
$ids = wp_list_pluck( $data, 'id' );
508+
$this->assertContains( $image_id, $ids, 'Image ID not found in response for multiple media types with comma-separated string' );
509+
$this->assertContains( $video_id, $ids, 'Video ID not found in response for multiple media types with comma-separated string' );
510+
$this->assertNotContains( $audio_id, $ids, 'Audio ID found in response for multiple media types with comma-separated string' );
511+
512+
// Test multiple media types with array format.
513+
$request->set_param( 'media_type', array( 'image', 'video', 'audio' ) );
514+
$response = rest_get_server()->dispatch( $request );
515+
$data = $response->get_data();
516+
$this->assertCount( 3, $data, 'Response count for multiple media types with array format is not 3' );
517+
$ids = wp_list_pluck( $data, 'id' );
518+
$this->assertContains( $image_id, $ids, 'Image ID not found in response for multiple media types with array format' );
519+
$this->assertContains( $video_id, $ids, 'Video ID not found in response for multiple media types with array format' );
520+
$this->assertContains( $audio_id, $ids, 'Audio ID not found in response for multiple media types with array format' );
521+
522+
// Test invalid media type mixed with valid ones.
523+
$request->set_param( 'media_type', 'image,invalid,video' );
524+
$response = rest_get_server()->dispatch( $request );
525+
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
526+
}
527+
528+
/**
529+
* Test multiple MIME types support and combination with media types.
530+
*
531+
* @ticket 63668
532+
*/
533+
public function test_get_items_multiple_mime_types_and_combination() {
534+
$jpeg_id = self::factory()->attachment->create_object(
535+
self::$test_file,
536+
0,
537+
array(
538+
'post_mime_type' => 'image/jpeg',
539+
)
540+
);
541+
542+
$png_id = self::factory()->attachment->create_object(
543+
self::$test_file2,
544+
0,
545+
array(
546+
'post_mime_type' => 'image/png',
547+
)
548+
);
549+
550+
$mp4_id = self::factory()->attachment->create_object(
551+
self::$test_video_file,
552+
0,
553+
array(
554+
'post_mime_type' => 'video/mp4',
555+
)
556+
);
557+
558+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
559+
560+
// Test single MIME type
561+
$request->set_param( 'mime_type', 'image/jpeg' );
562+
$response = rest_get_server()->dispatch( $request );
563+
$data = $response->get_data();
564+
$this->assertCount( 1, $data, 'Response count for single MIME type is not 1' );
565+
$this->assertSame( $jpeg_id, $data[0]['id'], 'JPEG ID not found in response for single MIME type' );
566+
567+
// Test multiple MIME types with comma-separated string.
568+
$request->set_param( 'mime_type', 'image/jpeg,image/png' );
569+
$response = rest_get_server()->dispatch( $request );
570+
$data = $response->get_data();
571+
$this->assertCount( 2, $data, 'Response count for multiple MIME types with comma-separated string is not 2' );
572+
$ids = wp_list_pluck( $data, 'id' );
573+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response for multiple MIME types with comma-separated string' );
574+
$this->assertContains( $png_id, $ids, 'PNG ID not found in response for multiple MIME types with comma-separated string' );
575+
576+
// Test multiple MIME types with array format.
577+
$request->set_param( 'mime_type', array( 'image/jpeg', 'video/mp4' ) );
578+
$response = rest_get_server()->dispatch( $request );
579+
$data = $response->get_data();
580+
$this->assertCount( 2, $data, 'Response count for multiple MIME types with array format is not 2' );
581+
$ids = wp_list_pluck( $data, 'id' );
582+
583+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response for multiple MIME types with array format' );
584+
$this->assertContains( $mp4_id, $ids, 'MP4 ID not found in response for multiple MIME types with array format' );
585+
}
586+
587+
/**
588+
* Test combination of media type and mime type parameters.
589+
*
590+
* @ticket 63668
591+
*/
592+
public function test_get_items_with_media_type_and_media_types() {
593+
$audio_id = self::factory()->attachment->create_object(
594+
self::$test_audio_file,
595+
0,
596+
array(
597+
'post_mime_type' => 'audio/mpeg',
598+
'post_excerpt' => 'A sample caption',
599+
)
600+
);
601+
602+
$jpeg_id = self::factory()->attachment->create_object(
603+
self::$test_file,
604+
0,
605+
array(
606+
'post_mime_type' => 'image/jpeg',
607+
'post_excerpt' => 'A sample caption',
608+
)
609+
);
610+
611+
$png_id = self::factory()->attachment->create_object(
612+
self::$test_file2,
613+
0,
614+
array(
615+
'post_mime_type' => 'image/png',
616+
)
617+
);
618+
619+
$video_id = self::factory()->attachment->create_object(
620+
self::$test_video_file,
621+
0,
622+
array(
623+
'post_mime_type' => 'video/mp4',
624+
)
625+
);
626+
627+
$rtf_id = self::factory()->attachment->create_object(
628+
self::$test_rtf_file,
629+
0,
630+
array(
631+
'post_mime_type' => 'application/rtf',
632+
)
633+
);
634+
635+
// Test combination of single media type and single mime type parameters.
636+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
637+
$request->set_param( 'media_type', 'image' );
638+
$request->set_param( 'mime_type', 'audio/mpeg' );
639+
$response = rest_get_server()->dispatch( $request );
640+
$data = $response->get_data();
641+
$ids = wp_list_pluck( $data, 'id' );
642+
643+
$this->assertCount( 3, $data, 'Response count for combination of single media type and single mime type parameters is not 3' );
644+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' );
645+
$this->assertContains( $png_id, $ids, 'PNG ID not found in response' );
646+
$this->assertContains( $audio_id, $ids, 'Audio ID found in response' );
647+
648+
// Test combination of single media type and multiple mime type parameters.
649+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
650+
$request->set_param( 'media_type', 'audio' );
651+
$request->set_param( 'mime_type', array( 'image/jpeg', 'image/png' ) );
652+
$response = rest_get_server()->dispatch( $request );
653+
$data = $response->get_data();
654+
$ids = wp_list_pluck( $data, 'id' );
655+
656+
$this->assertCount( 3, $data, 'Response count for combination of single media type and multiple mime type parameters is not 3' );
657+
$this->assertContains( $audio_id, $ids, 'Audio ID not found in response' );
658+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' );
659+
$this->assertContains( $png_id, $ids, 'PNG ID not found in response' );
660+
661+
// Test combination of multiple media types and single mime type parameters.
662+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
663+
$request->set_param( 'media_type', 'audio,video' );
664+
$request->set_param( 'mime_type', array( 'image/jpeg' ) );
665+
$response = rest_get_server()->dispatch( $request );
666+
$data = $response->get_data();
667+
$ids = wp_list_pluck( $data, 'id' );
668+
669+
$this->assertCount( 3, $data, 'Response count for combination of multiple media type and multiple mime type parameters is not 3' );
670+
$this->assertContains( $audio_id, $ids, 'Audio ID not found in response' );
671+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' );
672+
$this->assertContains( $video_id, $ids, 'Video ID not found in response' );
673+
674+
// Test combination of multiple media types and multiple mime type parameters.
675+
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
676+
$request->set_param( 'media_type', 'audio,video' );
677+
$request->set_param( 'mime_type', array( 'image/jpeg', 'image/png', 'application/rtf' ) );
678+
$response = rest_get_server()->dispatch( $request );
679+
$data = $response->get_data();
680+
$ids = wp_list_pluck( $data, 'id' );
681+
682+
$this->assertCount( 5, $data, 'Response count for combination of multiple media type and multiple mime type parameters is not 3' );
683+
$this->assertContains( $audio_id, $ids, 'Audio ID not found in response' );
684+
$this->assertContains( $jpeg_id, $ids, 'JPEG ID not found in response' );
685+
$this->assertContains( $video_id, $ids, 'Video ID not found in response' );
686+
$this->assertContains( $png_id, $ids, 'PNG ID not found in response' );
687+
$this->assertContains( $rtf_id, $ids, 'RTF ID not found in response' );
688+
}
689+
421690
public function test_get_items_mime_type() {
422691
$id1 = self::factory()->attachment->create_object(
423692
self::$test_file,
@@ -2685,6 +2954,7 @@ public function test_upload_svg_image() {
26852954

26862955
/**
26872956
* Tests that the attachment fields caption, description, and title, post and alt_text are updated correctly.
2957+
*
26882958
* @ticket 64035
26892959
* @requires function imagejpeg
26902960
*/

0 commit comments

Comments
 (0)