diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 122764d6f9bb2..3082d49a2e9d2 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -1679,6 +1679,7 @@ protected function parse_search_order( &$query_vars ) { * Converts the given orderby alias (if allowed) to a properly-prefixed value. * * @since 4.0.0 + * @since 6.9.0 Extends allowed_keys to support ordering by `post_mime_type`. * * @global wpdb $wpdb WordPress database abstraction object. * @@ -1695,6 +1696,7 @@ protected function parse_orderby( $orderby ) { 'post_date', 'post_title', 'post_modified', + 'post_mime_type', 'post_parent', 'post_type', 'name', @@ -1748,6 +1750,7 @@ protected function parse_orderby( $orderby ) { case 'post_author': case 'post_date': case 'post_title': + case 'post_mime_type': case 'post_modified': case 'post_parent': case 'post_type': 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 faeb01c93904a..e25e415a25b66 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 @@ -71,6 +71,7 @@ public function register_routes() { * * @since 4.7.0 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. + * @since 6.9.0 Extends the `orderby` request argument to support `mime_type`. * * @param array $prepared_args Optional. Array of prepared arguments. Default empty array. * @param WP_REST_Request $request Optional. Request to prepare items for. @@ -112,6 +113,17 @@ protected function prepare_items_query( $prepared_args = array(), $request = nul add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); } + // Map to proper WP_Query orderby param. + if ( isset( $query_args['orderby'], $request['orderby'] ) ) { + $orderby_mappings = array( + 'mime_type' => 'post_mime_type', + ); + + if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { + $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; + } + } + return $query_args; } @@ -1355,6 +1367,7 @@ public static function get_filename_from_disposition( $disposition_header ) { * * @since 4.7.0 * @since 6.9.0 Extends the `media_type` and `mime_type` request arguments to support array values. + * @since 6.9.0 Extends the `orderby` request argument to support `mime_type`. * * @return array Query parameters for the attachment collection as an array. */ @@ -1383,6 +1396,8 @@ public function get_collection_params() { ), ); + $params['orderby']['enum'][] = 'mime_type'; + return $params; } diff --git a/tests/phpunit/tests/query/results.php b/tests/phpunit/tests/query/results.php index 412d02fe83be6..abd2b7abb13d9 100644 --- a/tests/phpunit/tests/query/results.php +++ b/tests/phpunit/tests/query/results.php @@ -20,6 +20,11 @@ class Tests_Query_Results extends WP_UnitTestCase { public static $child_two; public static $child_three; public static $child_four; + public static $image1_jpg; + public static $image2_jpg; + public static $image3_png; + public static $audio1_mp3; + public static $video1_mp4; public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { $cat_a = $factory->term->create( @@ -292,6 +297,47 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { ) ); self::$post_ids[] = self::$child_four; + + self::$image1_jpg = $factory->attachment->create_object( + 'image1.jpg', + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'post_date' => '2025-09-29 06:22:49', + ) + ); + self::$image2_jpg = $factory->attachment->create_object( + 'image2.jpg', + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'post_date' => '2025-09-29 06:22:49', + ) + ); + self::$image3_png = $factory->attachment->create_object( + 'image3.png', + 0, + array( + 'post_mime_type' => 'image/png', + 'post_date' => '2025-09-29 06:22:49', + ) + ); + self::$audio1_mp3 = $factory->attachment->create_object( + 'audio1.mp3', + 0, + array( + 'post_mime_type' => 'audio/mpeg', + 'post_date' => '2025-09-29 06:22:49', + ) + ); + self::$video1_mp4 = $factory->attachment->create_object( + 'video1.mp4', + 0, + array( + 'post_mime_type' => 'video/mp4', + 'post_date' => '2025-09-29 06:22:49', + ) + ); } public function set_up() { @@ -1264,4 +1310,53 @@ public function test_main_comments_feed_includes_attachment_comments() { $feed_comment = $this->q->next_comment(); $this->assertEquals( $comment_id, $feed_comment->comment_ID ); } + + /** + * @ticket 64073 + */ + public function test_attachment_orderby_post_mime_type() { + $all_attachments_desc = $this->q->query( + array( + 'post_type' => 'attachment', + 'orderby' => 'post_mime_type', + 'post_status' => 'inherit', + 'order' => 'desc', + ) + ); + + $this->assertCount( 5, $all_attachments_desc ); + + $this->assertSame( + array( + self::$video1_mp4, + self::$image3_png, + self::$image1_jpg, + self::$image2_jpg, + self::$audio1_mp3, + ), + wp_list_pluck( $all_attachments_desc, 'ID' ), + 'Order by post_mime_type order desc returns incorrect order' + ); + + $all_attachments_asc = $this->q->query( + array( + 'post_type' => 'attachment', + 'orderby' => 'post_mime_type', + 'post_status' => 'inherit', + 'order' => 'asc', + ) + ); + + $this->assertSame( + array( + self::$audio1_mp3, + self::$image1_jpg, + self::$image2_jpg, + self::$image3_png, + self::$video1_mp4, + ), + wp_list_pluck( $all_attachments_asc, 'ID' ), + 'Order by post_mime_type order asc returns incorrect order' + ); + } } diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index bc1ca41b578ee..f8c32c85d4b15 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -3097,4 +3097,69 @@ public function test_edit_image_vertical_flip_only() { // The controller converts the integer values to booleans: 0 !== (int) 1 = true. $this->assertSame( array( true, false ), WP_Image_Editor_Mock::$spy['flip'][0], 'Vertical flip of the image is not identical.' ); } + + /** + * Test that the `orderby` parameter works with the `mime_type` parameter. + * + * @ticket 64073 + */ + public function test_get_items_orderby_mime_type() { + $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', + 'post_excerpt' => 'A sample caption', + ) + ); + + $avif_id = self::factory()->attachment->create_object( + self::$test_avif_file, + 0, + array( + 'post_mime_type' => 'video/avif', + 'post_excerpt' => 'A sample caption', + ) + ); + + $svg_id = self::factory()->attachment->create_object( + self::$test_svg_file, + 0, + array( + 'post_mime_type' => 'image/svg+xml', + 'post_excerpt' => 'A sample caption', + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/media' ); + $request->set_param( '_fields', 'id,mime_type' ); + + // Check ordering. Default ORDER is DESC. + $request->set_param( 'orderby', 'mime_type' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertCount( 4, $data, 'Response count for orderby DESC mime_type is not 4' ); + // Check that ordering is working by verifying the mime types are in order + $mime_types = array_column( $data, 'mime_type' ); + $expected_desc = array( 'video/avif', 'image/svg+xml', 'image/png', 'image/jpeg' ); + $this->assertSame( $expected_desc, $mime_types, 'MIME types not in expected DESC order' ); + + // ASC order. + $request->set_param( 'order', 'asc' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $mime_types = array_column( $data, 'mime_type' ); + $expected_asc = array( 'image/jpeg', 'image/png', 'image/svg+xml', 'video/avif' ); + $this->assertSame( $expected_asc, $mime_types, 'MIME types not in expected ASC order' ); + } } diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index bc9ea0a2dc424..fceb635aca10c 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -2903,7 +2903,8 @@ mockedApiResponse.Schema = { "relevance", "slug", "include_slugs", - "title" + "title", + "mime_type" ], "required": false },