From f7b13207ecdb9198513877912a1bf1fd49788908 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 5 Nov 2026 21:40:56 -0600 Subject: [PATCH 1/3] Photo Directory, CLI: Add a command to restore scaled-down originals Photos uploaded while the parent theme's 3200px big_image_size_threshold took precedence (fixed in r14386) have a "-scaled" copy as their attached file. Their "Original Size" download links to that copy and lists its dimensions, while the full-size original sits next to it. `wp photos restore-originals` points those attachments at the original file and updates the stored dimensions and filesize. Photos whose original is still above the current threshold are left alone, and `--dry-run` lists what would change. Fixes #8199. Co-Authored-By: Claude Opus 5.5 --- .../plugins/photo-directory/inc/cli.php | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php index 57acb71fa7..9295e0e73c 100644 --- a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php +++ b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php @@ -157,6 +157,112 @@ public function init( $args, $assoc_args ) { } } + /** + * Points scaled-down photos back at their original files. + * + * Photos uploaded while a lower 'big_image_size_threshold' was in effect + * have a "-scaled" copy as their attached file, so their "Original Size" + * download served the copy. The original file is still stored next to it, + * and the "-scaled" copy is left in place. + * + * ## OPTIONS + * + * [--dry-run] + * : List the photos that would be updated without changing them. + * + * ## EXAMPLES + * + * # Preview which photos would be updated. + * $ wp photos restore-originals --dry-run + * + * # Update them. + * $ wp photos restore-originals + * + * @param array $args Positional arguments. Unused. + * @param array $assoc_args Associative arguments. + * + * @subcommand restore-originals + * @when after_wp_load + */ + public function restore_originals( $args, $assoc_args ) { + $dry_run = $assoc_args['dry-run'] ?? false; + $threshold = Uploads::big_image_size_threshold( 0 ); + $updated = 0; + + $attachment_ids = get_posts( + [ + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'posts_per_page' => -1, + 'fields' => 'ids', + 'meta_query' => [ + [ + 'key' => '_wp_attachment_metadata', + 'value' => 'original_image', + 'compare' => 'LIKE', + ], + ], + ] + ); + + foreach ( $attachment_ids as $attachment_id ) { + $parent = get_post_parent( $attachment_id ); + if ( ! $parent || Registrations::get_post_type() !== $parent->post_type ) { + continue; + } + + $meta = wp_get_attachment_metadata( $attachment_id ); + if ( empty( $meta['original_image'] ) || empty( $meta['file'] ) ) { + continue; + } + + $original_path = wp_get_original_image_path( $attachment_id ); + $image_size = $original_path ? wp_getimagesize( $original_path ) : false; + + if ( ! $image_size ) { + WP_CLI::warning( "Photo {$parent->ID}: original file {$meta['original_image']} could not be read." ); + continue; + } + + // Still above the current threshold, so scaling it was intended. + if ( max( $image_size[0], $image_size[1] ) > $threshold ) { + continue; + } + + $file = path_join( dirname( $meta['file'] ), $meta['original_image'] ); + + WP_CLI::log( "Photo {$parent->ID}: {$meta['file']} ({$meta['width']}x{$meta['height']}) -> {$file} ({$image_size[0]}x{$image_size[1]})" ); + ++$updated; + + if ( $dry_run ) { + continue; + } + + update_attached_file( $attachment_id, $file ); + + $meta['file'] = $file; + $meta['width'] = $image_size[0]; + $meta['height'] = $image_size[1]; + $meta['filesize'] = wp_filesize( $original_path ); + unset( $meta['original_image'] ); + + if ( isset( $meta['gcs_name'] ) ) { + $meta['gcs_name'] = $file; + } + if ( isset( $meta['gcs_url'] ) ) { + $meta['gcs_url'] = wp_get_attachment_url( $attachment_id ); + } + + wp_update_attachment_metadata( $attachment_id, $meta ); + } + + if ( $dry_run ) { + WP_CLI::success( "{$updated} photo(s) would be updated." ); + } else { + WP_CLI::success( "{$updated} photo(s) updated." ); + } + } + } WP_CLI::add_command( 'photos', 'WordPressdotorg\Photo_Directory\CLI' ); From e355797f19307e5ddbc7386ccf9d5d5892c1d1a9 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Thu, 5 Nov 2026 21:49:27 -0600 Subject: [PATCH 2/3] Photo Directory, CLI: Skip rotated photos and track the scaled copy when restoring originals Core also sets 'original_image' for photos that were only EXIF-rotated, and stores the original unrotated, so restore-originals now only acts on "-scaled" files whose orientation matches the original's. The "-scaled" copy is kept as a 'scaled' sub-size, so deleting a restored photo still deletes it. Co-Authored-By: Claude Opus 5.5 --- .../plugins/photo-directory/inc/cli.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php index 9295e0e73c..de3640b06b 100644 --- a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php +++ b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php @@ -216,6 +216,11 @@ public function restore_originals( $args, $assoc_args ) { continue; } + // Core also sets 'original_image' for copies that were only EXIF-rotated. + if ( ! str_contains( wp_basename( $meta['file'] ), '-scaled.' ) ) { + continue; + } + $original_path = wp_get_original_image_path( $attachment_id ); $image_size = $original_path ? wp_getimagesize( $original_path ) : false; @@ -229,6 +234,12 @@ public function restore_originals( $args, $assoc_args ) { continue; } + // The original is stored unrotated, so its dimensions don't match the rotated copy. + if ( ( $meta['width'] > $meta['height'] ) !== ( $image_size[0] > $image_size[1] ) ) { + WP_CLI::warning( "Photo {$parent->ID}: {$meta['file']} was rotated from its original, skipping." ); + continue; + } + $file = path_join( dirname( $meta['file'] ), $meta['original_image'] ); WP_CLI::log( "Photo {$parent->ID}: {$meta['file']} ({$meta['width']}x{$meta['height']}) -> {$file} ({$image_size[0]}x{$image_size[1]})" ); @@ -240,6 +251,14 @@ public function restore_originals( $args, $assoc_args ) { update_attached_file( $attachment_id, $file ); + // Keep the "-scaled" copy tracked so deleting the photo deletes it too. + $meta['sizes']['scaled'] = [ + 'file' => wp_basename( $meta['file'] ), + 'width' => $meta['width'], + 'height' => $meta['height'], + 'mime-type' => get_post_mime_type( $attachment_id ), + ]; + $meta['file'] = $file; $meta['width'] = $image_size[0]; $meta['height'] = $image_size[1]; From 950a9fba124733e7741514bc3accb9a6426e8b42 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Tue, 10 Nov 2026 15:02:22 -0600 Subject: [PATCH 3/3] Photo Directory, CLI: Check EXIF orientation before restoring an original MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core rotates the "-scaled" copy per the original's EXIF orientation and stores the original unrotated. Comparing dimensions alone misses 180° rotations and 90° rotations of square photos, so restore-originals now also skips photos whose original has an EXIF orientation other than 1. Co-Authored-By: Claude Opus 5.5 --- .../wp-content/plugins/photo-directory/inc/cli.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php index de3640b06b..dd1d084f6a 100644 --- a/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php +++ b/wordpress.org/public_html/wp-content/plugins/photo-directory/inc/cli.php @@ -185,6 +185,8 @@ public function init( $args, $assoc_args ) { * @when after_wp_load */ public function restore_originals( $args, $assoc_args ) { + require_once ABSPATH . 'wp-admin/includes/image.php'; + $dry_run = $assoc_args['dry-run'] ?? false; $threshold = Uploads::big_image_size_threshold( 0 ); $updated = 0; @@ -234,8 +236,9 @@ public function restore_originals( $args, $assoc_args ) { continue; } - // The original is stored unrotated, so its dimensions don't match the rotated copy. - if ( ( $meta['width'] > $meta['height'] ) !== ( $image_size[0] > $image_size[1] ) ) { + // Core rotates the copy per the original's EXIF orientation, but stores the original unrotated. + $exif = wp_read_image_metadata( $original_path ); + if ( ( $exif['orientation'] ?? 0 ) > 1 || ( $meta['width'] > $meta['height'] ) !== ( $image_size[0] > $image_size[1] ) ) { WP_CLI::warning( "Photo {$parent->ID}: {$meta['file']} was rotated from its original, skipping." ); continue; }