From 08c37fc52145f0f22be45d210c5a6f1ed65fffc0 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 13 Jul 2026 17:51:42 +1000 Subject: [PATCH 1/2] Enhancement: Apply EXIF orientation correction before image edits in REST API --- .../class-wp-rest-attachments-controller.php | 3 + .../rest-api/rest-attachments-controller.php | 83 +++++++++++++++++++ 2 files changed, 86 insertions(+) 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 4737ee747a52b..37026c7b062dd 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 @@ -1064,6 +1064,9 @@ public function edit_media_item( $request ) { ); } + // Apply any unapplied EXIF orientation so edits run in the upright frame the client previewed. + $image_editor->maybe_exif_rotate(); + foreach ( $modifiers as $modifier ) { $args = $modifier['args']; switch ( $modifier['type'] ) { diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 911676614754d..acfaa6117ec09 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -3041,6 +3041,89 @@ public function test_edit_image_crop_one_axis() { ); } + /** + * @ticket 65618 + * @requires function imagejpeg + */ + public function test_edit_image_returns_error_if_no_image_editor() { + wp_set_current_user( self::$superadmin_id ); + $attachment = self::factory()->attachment->create_upload_object( self::$test_file ); + + add_filter( 'wp_image_editors', '__return_empty_array' ); + + $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" ); + $request->set_body_params( + array( + 'rotation' => 60, + 'src' => wp_get_attachment_image_url( $attachment, 'full' ), + ) + ); + $response = rest_do_request( $request ); + $this->assertErrorResponse( 'rest_unknown_image_file_type', $response, 500 ); + } + + /** + * @ticket 65618 + * @requires function imagejpeg + */ + public function test_edit_image_applies_unbaked_exif_orientation_before_edits() { + wp_set_current_user( self::$superadmin_id ); + $attachment = self::factory()->attachment->create_upload_object( self::$test_file ); + + $this->setup_mock_editor(); + add_filter( + 'wp_image_maybe_exif_rotate', + static function () { + return 6; + } + ); + WP_Image_Editor_Mock::$edit_return['rotate'] = new WP_Error(); + + $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" ); + $request->set_body_params( + array( + 'rotation' => 60, + 'src' => wp_get_attachment_image_url( $attachment, 'full' ), + ) + ); + $response = rest_do_request( $request ); + $this->assertErrorResponse( 'rest_image_rotation_failed', $response, 500 ); + + // The EXIF orientation correction (orientation 6 => rotate 270) must run before the requested edit. + $this->assertSame( array( array( 270 ), array( -60 ) ), WP_Image_Editor_Mock::$spy['rotate'] ); + } + + /** + * @ticket 65618 + * @requires extension exif + * @requires function imagejpeg + */ + public function test_edit_image_rotate_with_unbaked_exif_orientation() { + wp_set_current_user( self::$superadmin_id ); + $attachment = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg' ); + + $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment}/edit" ); + $request->set_body_params( + array( + 'rotation' => 90, + 'src' => wp_get_attachment_image_url( $attachment, 'full' ), + ) + ); + $response = rest_do_request( $request ); + $item = $response->get_data(); + + $this->assertSame( 201, $response->get_status() ); + + /* + * The original file is 1200x1800 raw pixels with an unapplied EXIF orientation of 6, + * so clients preview it upright as 1800x1200. Rotating that upright frame 90 degrees + * must produce 1200x1800. Without the orientation correction the edit rotates the raw + * pixels instead and produces 1800x1200. + */ + $this->assertSame( 1200, $item['media_details']['width'] ); + $this->assertSame( 1800, $item['media_details']['height'] ); + } + /** * @ticket 44405 * @requires function imagejpeg From 2d8f94e2155b7637614ce94dc96adc398c3d11e6 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 21 Jul 2026 16:11:35 +1000 Subject: [PATCH 2/2] Add since annotation --- .../rest-api/endpoints/class-wp-rest-attachments-controller.php | 1 + 1 file changed, 1 insertion(+) 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 37026c7b062dd..b809085ec8124 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 @@ -959,6 +959,7 @@ public function edit_media_item_permissions_check( $request ) { * * @since 5.5.0 * @since 6.9.0 Adds flips capability and editable fields for the newly-created attachment post. + * @since 7.1.0 Applies EXIF orientation correction before image modifications. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.