From f3e6358b7384a5657f6747e0c9d652649600c333 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:54:11 +0000 Subject: [PATCH 1/8] Initial plan From 0a8e1d88d587f283f6d012a8e6d551c1ab42a6fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:04:25 +0000 Subject: [PATCH 2/8] Fix flip_rotate_image orientation check and update feature tests for WP 5.3+ Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/media-fix-orientation.feature | 65 +++++++++++++++++++++++--- src/Media_Command.php | 2 +- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/features/media-fix-orientation.feature b/features/media-fix-orientation.feature index ce3cbed8..48f9ac10 100644 --- a/features/media-fix-orientation.feature +++ b/features/media-fix-orientation.feature @@ -11,9 +11,8 @@ Feature: Fix WordPress attachments orientation Error: No images found. """ - # On WP 4.9 tests this results in "Couldn't fix orientation". - # Todo: Revisit this test and improve or potentially remove it if useless. - @require-extension-exif @require-wp-4.0 @less-than-wp-4.9 + # On WP 5.3+, images are auto-rotated by WordPress during import, so fix-orientation reports them as already fixed. + @require-extension-exif @require-wp-4.0 @less-than-wp-5.3 Scenario: Fix orientation for all images Given download: | path | url | @@ -96,10 +95,8 @@ Feature: Fix WordPress attachments orientation Success: Images already fixed. """ - # On newer versions (5.3+) the image is already considered fixed. - # On WP 4.9 tests this results in "Couldn't fix orientation". - # Todo: Revisit this test and improve or potentially remove it if useless. - @require-extension-exif @require-wp-4.0 @less-than-wp-4.9 + # On WP 5.3+, images are auto-rotated by WordPress during import, so fix-orientation reports them as already fixed. + @require-extension-exif @require-wp-4.0 @less-than-wp-5.3 Scenario: Fix orientation for single image Given download: | path | url | @@ -129,3 +126,57 @@ Feature: Fix WordPress attachments orientation """ Error: No images found. """ + + @require-extension-exif @require-wp-5.3 + Scenario: Fix orientation for all images already auto-rotated by WordPress + Given download: + | path | url | + | {CACHE_DIR}/landscape-2.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Landscape_2.jpg | + | {CACHE_DIR}/landscape-5.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Landscape_5.jpg | + | {CACHE_DIR}/portrait-4.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Portrait_4.jpg | + And I run `wp option update uploads_use_yearmonth_folders 0` + + When I run `wp media import {CACHE_DIR}/landscape-2.jpg --title="Landscape Two" --porcelain` + Then save STDOUT as {LANDSCAPE_TWO} + + When I run `wp media import {CACHE_DIR}/landscape-5.jpg --title="Landscape Five" --porcelain` + Then save STDOUT as {LANDSCAPE_FIVE} + + When I run `wp media import {CACHE_DIR}/portrait-4.jpg --title="Portrait Four" --porcelain` + Then save STDOUT as {PORTRAIT_FOUR} + + When I run `wp media fix-orientation` + Then STDOUT should contain: + """ + No orientation fix required for "Portrait Four" (ID {PORTRAIT_FOUR}). + """ + + And STDOUT should contain: + """ + No orientation fix required for "Landscape Five" (ID {LANDSCAPE_FIVE}). + """ + + And STDOUT should contain: + """ + No orientation fix required for "Landscape Two" (ID {LANDSCAPE_TWO}). + """ + + And STDOUT should contain: + """ + Success: Images already fixed. + """ + + @require-extension-exif @require-wp-5.3 + Scenario: Fix orientation for single image already auto-rotated by WordPress + Given download: + | path | url | + | {CACHE_DIR}/portrait-6.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Portrait_6.jpg | + When I run `wp media import {CACHE_DIR}/portrait-6.jpg --title="Portrait Six" --porcelain` + Then save STDOUT as {PORTRAIT_SIX} + + When I run `wp media fix-orientation {PORTRAIT_SIX}` + Then STDOUT should be: + """ + 1/1 No orientation fix required for "Portrait Six" (ID {PORTRAIT_SIX}). + Success: Image already fixed. + """ diff --git a/src/Media_Command.php b/src/Media_Command.php index eaa49ccf..f74671fc 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1518,7 +1518,7 @@ private function flip_rotate_image( $id, $metadata, $image_meta, $full_size_path // Update attachment metadata with newly generated data. wp_update_attachment_metadata( $id, $metadata ); - if ( isset( $image_meta['orientation'] ) && absint( $image_meta['orientation'] ) === 0 ) { + if ( ! isset( $image_meta['orientation'] ) || absint( $image_meta['orientation'] ) <= 1 ) { return true; } } From fa22e34946fc7ea39d1b360c321d481417402243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:31:13 +0000 Subject: [PATCH 3/8] Fix Imagick flip-only orientation failure: use DB metadata for detection, normalize after save Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index f74671fc..6be8215d 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1462,14 +1462,14 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, return; } - // Get current metadata of the attachment. - $metadata = wp_generate_attachment_metadata( $id, $full_size_path ); - $image_meta = ! empty( $metadata['image_meta'] ) ? $metadata['image_meta'] : []; + // Get current metadata of the attachment from the database. + $metadata = wp_get_attachment_metadata( $id ); + $image_meta = is_array( $metadata ) && ! empty( $metadata['image_meta'] ) ? $metadata['image_meta'] : []; if ( isset( $image_meta['orientation'] ) && absint( $image_meta['orientation'] ) > 1 ) { if ( ! $dry_run ) { WP_CLI::log( "{$progress} Fixing orientation for {$att_desc}." ); - if ( false !== $this->flip_rotate_image( $id, $metadata, $image_meta, $full_size_path ) ) { + if ( false !== $this->flip_rotate_image( $id, is_array( $metadata ) ? $metadata : [], $image_meta, $full_size_path ) ) { ++$successes; } else { ++$errors; @@ -1511,14 +1511,23 @@ private function flip_rotate_image( $id, $metadata, $image_meta, $full_size_path } // Save the image and generate metadata. - $editor->save( $full_size_path ); - $metadata = wp_generate_attachment_metadata( $id, $full_size_path ); - $image_meta = empty( $metadata['image_meta'] ) ? [] : $metadata['image_meta']; + $saved = $editor->save( $full_size_path ); + $metadata = wp_generate_attachment_metadata( $id, $full_size_path ); + + // After a successful save, normalize the stored orientation to prevent + // re-detection on subsequent runs. WP_Image_Editor_Imagick::flip() does not + // reset the EXIF orientation tag, so the file may still report a non-normal + // orientation even though the pixels have been corrected. Because $editor->save() + // succeeded, the transformation was applied to the image; forcing orientation to 0 + // in the stored metadata ensures the next run reports "No orientation fix required". + if ( ! is_wp_error( $saved ) && isset( $metadata['image_meta']['orientation'] ) ) { + $metadata['image_meta']['orientation'] = 0; + } // Update attachment metadata with newly generated data. wp_update_attachment_metadata( $id, $metadata ); - if ( ! isset( $image_meta['orientation'] ) || absint( $image_meta['orientation'] ) <= 1 ) { + if ( ! is_wp_error( $saved ) ) { return true; } } From d958c83c24b84c92ca3e0ee3f2978f7fe425145b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 11 Mar 2026 11:43:06 +0100 Subject: [PATCH 4/8] Update src/Media_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Media_Command.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index 6be8215d..6dd48675 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1466,7 +1466,23 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, $metadata = wp_get_attachment_metadata( $id ); $image_meta = is_array( $metadata ) && ! empty( $metadata['image_meta'] ) ? $metadata['image_meta'] : []; - if ( isset( $image_meta['orientation'] ) && absint( $image_meta['orientation'] ) > 1 ) { + // Determine orientation from DB metadata first. + $orientation = isset( $image_meta['orientation'] ) ? absint( $image_meta['orientation'] ) : 0; + + // If DB metadata is missing/incomplete, fall back to reading from the file's EXIF data. + if ( $orientation <= 1 ) { + $file_image_meta = wp_read_image_metadata( $full_size_path ); + if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { + $file_orientation = absint( $file_image_meta['orientation'] ); + if ( $file_orientation > 1 ) { + // Merge file-based metadata so flip_rotate_image() has the orientation. + $image_meta = array_merge( $image_meta, $file_image_meta ); + $orientation = $file_orientation; + } + } + } + + if ( $orientation > 1 ) { if ( ! $dry_run ) { WP_CLI::log( "{$progress} Fixing orientation for {$att_desc}." ); if ( false !== $this->flip_rotate_image( $id, is_array( $metadata ) ? $metadata : [], $image_meta, $full_size_path ) ) { From c4c14ed63d7180524491fbdef5c6fe472db29dee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:50:23 +0000 Subject: [PATCH 5/8] Add @require-extension-imagick scenario for flip-only orientations and fix PHPStan type error Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/media-fix-orientation.feature | 46 ++++++++++++++++++++++++++ src/Media_Command.php | 3 +- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/features/media-fix-orientation.feature b/features/media-fix-orientation.feature index 48f9ac10..d02fd3d3 100644 --- a/features/media-fix-orientation.feature +++ b/features/media-fix-orientation.feature @@ -119,6 +119,52 @@ Feature: Fix WordPress attachments orientation Success: Image already fixed. """ + # This specifically tests the Imagick flip-only path (orientations 2, 4) where + # WP_Image_Editor_Imagick::flip() does not update the EXIF orientation tag, requiring + # explicit metadata normalization after the fix. + @require-extension-exif @require-extension-imagick @require-wp-4.0 @less-than-wp-5.3 + Scenario: Fix flip-only orientation with Imagick + Given download: + | path | url | + | {CACHE_DIR}/landscape-2.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Landscape_2.jpg | + | {CACHE_DIR}/portrait-4.jpg | https://raw.githubusercontent.com/thrijith/test-images/master/Portrait_4.jpg | + And I run `wp option update uploads_use_yearmonth_folders 0` + + When I run `wp media import {CACHE_DIR}/landscape-2.jpg --title="Landscape Two" --porcelain` + Then save STDOUT as {LANDSCAPE_TWO} + + When I run `wp media import {CACHE_DIR}/portrait-4.jpg --title="Portrait Four" --porcelain` + Then save STDOUT as {PORTRAIT_FOUR} + + When I run `wp media fix-orientation` + Then STDOUT should contain: + """ + Fixing orientation for "Landscape Two" (ID {LANDSCAPE_TWO}). + """ + And STDOUT should contain: + """ + Fixing orientation for "Portrait Four" (ID {PORTRAIT_FOUR}). + """ + And STDOUT should contain: + """ + Success: Fixed 2 of 2 images. + """ + + # Verify that a second run reports no fix required (metadata normalized after save). + When I run `wp media fix-orientation` + Then STDOUT should contain: + """ + No orientation fix required for "Landscape Two" (ID {LANDSCAPE_TWO}). + """ + And STDOUT should contain: + """ + No orientation fix required for "Portrait Four" (ID {PORTRAIT_FOUR}). + """ + And STDOUT should contain: + """ + Success: Images already fixed. + """ + @require-wp-4.0 Scenario: Fix orientation for non existent image When I try `wp media fix-orientation 9999` diff --git a/src/Media_Command.php b/src/Media_Command.php index 6dd48675..5eae0c4d 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1473,7 +1473,8 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, if ( $orientation <= 1 ) { $file_image_meta = wp_read_image_metadata( $full_size_path ); if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { - $file_orientation = absint( $file_image_meta['orientation'] ); + $raw_orientation = $file_image_meta['orientation']; + $file_orientation = is_scalar( $raw_orientation ) ? absint( $raw_orientation ) : 0; if ( $file_orientation > 1 ) { // Merge file-based metadata so flip_rotate_image() has the orientation. $image_meta = array_merge( $image_meta, $file_image_meta ); From 66bd97ce218206875581cd94dfbf797ff2e391eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:59:26 +0000 Subject: [PATCH 6/8] Fix flip_rotate_image: guard metadata regeneration behind successful save, validate non-empty metadata Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index 5eae0c4d..781659a1 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1486,7 +1486,7 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, if ( $orientation > 1 ) { if ( ! $dry_run ) { WP_CLI::log( "{$progress} Fixing orientation for {$att_desc}." ); - if ( false !== $this->flip_rotate_image( $id, is_array( $metadata ) ? $metadata : [], $image_meta, $full_size_path ) ) { + if ( false !== $this->flip_rotate_image( $id, $image_meta, $full_size_path ) ) { ++$successes; } else { ++$errors; @@ -1505,13 +1505,12 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, * Perform image rotate operations on the image. * * @param int $id Attachment Id. - * @param array $metadata Attachment Metadata. * @param array $image_meta `image_meta` information for the attachment. * @param string $full_size_path Path to original image. * * @return bool Whether the image rotation operation succeeded. */ - private function flip_rotate_image( $id, $metadata, $image_meta, $full_size_path ) { + private function flip_rotate_image( $id, $image_meta, $full_size_path ) { $editor = wp_get_image_editor( $full_size_path ); if ( ! is_wp_error( $editor ) ) { @@ -1527,26 +1526,31 @@ private function flip_rotate_image( $id, $metadata, $image_meta, $full_size_path $editor->flip( $operations['flip'][0], $operations['flip'][1] ); } - // Save the image and generate metadata. - $saved = $editor->save( $full_size_path ); + $saved = $editor->save( $full_size_path ); + + if ( is_wp_error( $saved ) ) { + return false; + } + + // Regenerate attachment metadata after the corrected image is saved. $metadata = wp_generate_attachment_metadata( $id, $full_size_path ); - // After a successful save, normalize the stored orientation to prevent - // re-detection on subsequent runs. WP_Image_Editor_Imagick::flip() does not - // reset the EXIF orientation tag, so the file may still report a non-normal - // orientation even though the pixels have been corrected. Because $editor->save() - // succeeded, the transformation was applied to the image; forcing orientation to 0 - // in the stored metadata ensures the next run reports "No orientation fix required". - if ( ! is_wp_error( $saved ) && isset( $metadata['image_meta']['orientation'] ) ) { + if ( empty( $metadata ) ) { + return false; + } + + // Normalize the stored orientation to prevent re-detection on subsequent runs. + // WP_Image_Editor_Imagick::flip() does not reset the EXIF orientation tag in the + // file, so the file may still report a non-normal orientation even though the pixels + // have been corrected. Forcing orientation to 0 in the stored metadata ensures the + // next run reports "No orientation fix required". + if ( isset( $metadata['image_meta']['orientation'] ) ) { $metadata['image_meta']['orientation'] = 0; } - // Update attachment metadata with newly generated data. wp_update_attachment_metadata( $id, $metadata ); - if ( ! is_wp_error( $saved ) ) { - return true; - } + return true; } return false; From c76c1f428287c50b8df3e65bfdaa164c2fc03188 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:28:29 +0000 Subject: [PATCH 7/8] Fix WP 5.3+ false-positive: verify file EXIF when DB orientation > 1 to detect already-auto-rotated images Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index 781659a1..b2d96799 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1469,8 +1469,22 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, // Determine orientation from DB metadata first. $orientation = isset( $image_meta['orientation'] ) ? absint( $image_meta['orientation'] ) : 0; - // If DB metadata is missing/incomplete, fall back to reading from the file's EXIF data. - if ( $orientation <= 1 ) { + if ( $orientation > 1 ) { + // DB shows orientation > 1, but WP 5.3+ may have already auto-rotated the image + // on import (via wp_maybe_exif_rotate()), storing the original EXIF value before + // rotating. Verify against the file's current EXIF: if it is <= 1 the image is + // already correctly oriented and no fix is needed. + $file_image_meta = wp_read_image_metadata( $full_size_path ); + if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { + $raw_orientation = $file_image_meta['orientation']; + $file_orientation = is_scalar( $raw_orientation ) ? absint( $raw_orientation ) : 0; + if ( $file_orientation <= 1 ) { + $orientation = $file_orientation; + } + } + } elseif ( empty( $image_meta ) || ! isset( $image_meta['orientation'] ) ) { + // DB has no orientation data at all (stale/absent metadata). Fall back to reading + // from the file's EXIF so the command still works for such attachments. $file_image_meta = wp_read_image_metadata( $full_size_path ); if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { $raw_orientation = $file_image_meta['orientation']; From c389f538ef297821f4ee2df39f8ba67d8a6f0949 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 11 Mar 2026 21:53:12 +0100 Subject: [PATCH 8/8] Update src/Media_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Media_Command.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index b2d96799..6fe25421 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -1472,14 +1472,17 @@ private function process_orientation_fix( $id, $progress, &$successes, &$errors, if ( $orientation > 1 ) { // DB shows orientation > 1, but WP 5.3+ may have already auto-rotated the image // on import (via wp_maybe_exif_rotate()), storing the original EXIF value before - // rotating. Verify against the file's current EXIF: if it is <= 1 the image is - // already correctly oriented and no fix is needed. - $file_image_meta = wp_read_image_metadata( $full_size_path ); - if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { - $raw_orientation = $file_image_meta['orientation']; - $file_orientation = is_scalar( $raw_orientation ) ? absint( $raw_orientation ) : 0; - if ( $file_orientation <= 1 ) { - $orientation = $file_orientation; + // rotating. On WP < 5.3 this behavior does not occur, so skip the extra EXIF read. + if ( Utils\wp_version_compare( '5.3', '>=' ) ) { + // Verify against the file's current EXIF: if it is <= 1 the image is already + // correctly oriented and no fix is needed. + $file_image_meta = wp_read_image_metadata( $full_size_path ); + if ( is_array( $file_image_meta ) && isset( $file_image_meta['orientation'] ) ) { + $raw_orientation = $file_image_meta['orientation']; + $file_orientation = is_scalar( $raw_orientation ) ? absint( $raw_orientation ) : 0; + if ( $file_orientation <= 1 ) { + $orientation = $file_orientation; + } } } } elseif ( empty( $image_meta ) || ! isset( $image_meta['orientation'] ) ) {