From bb8bdca560044f3ebd91e209d6dee48a35e3aec5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:03:03 +0000 Subject: [PATCH 1/8] Initial plan From 2dcadec139e550e18c4372b4f0f3daa04b1fd071 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:14:04 +0000 Subject: [PATCH 2/8] Allow multiple image sizes for regeneration via comma-separated --image_size Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/media-regenerate.feature | 91 ++++++++++++++++ src/Media_Command.php | 169 +++++++++++++++++++----------- 2 files changed, 196 insertions(+), 64 deletions(-) diff --git a/features/media-regenerate.feature b/features/media-regenerate.feature index 6b3412ce..a10b6250 100644 --- a/features/media-regenerate.feature +++ b/features/media-regenerate.feature @@ -984,6 +984,97 @@ Feature: Regenerate WordPress attachments """ And the return code should be 1 + Scenario: Provide error message when one of the multiple image sizes is non-existent + When I try `wp media regenerate --image_size=medium,test1` + Then STDERR should be: + """ + Error: Unknown image size "test1". + """ + And the return code should be 1 + + Scenario: Regenerate multiple specific image sizes + Given download: + | path | url | + | {CACHE_DIR}/canola.jpg | http://wp-cli.org/behat-data/canola.jpg | + And a wp-content/mu-plugins/media-settings.php file: + """ + ] - * : Name of the image size to regenerate. Only thumbnails of this image size will be regenerated, thumbnails of other image sizes will not. + * : Name of the image size (or comma-separated list of image sizes) to regenerate. Only thumbnails of specified image size(s) will be regenerated, thumbnails of other image sizes will not. * * [--skip-delete] * : Skip deletion of the original thumbnails. If your thumbnails are linked from sources outside your control, it's likely best to leave them around. Defaults to false. @@ -113,6 +113,15 @@ class Media_Command extends WP_CLI_Command { * 3/3 Regenerated "large" thumbnail for "Sunburst Over River" (ID 756). * Success: Regenerated 3 of 3 images. * + * # Re-generate only the thumbnails of "large" and "medium" image sizes for all images. + * $ wp media regenerate --image_size=large,medium + * Do you really want to regenerate the "large", "medium" image sizes for all images? [y/n] y + * Found 3 images to regenerate. + * 1/3 Regenerated "large", "medium" thumbnails for "Sydney Harbor Bridge" (ID 760). + * 2/3 No "large", "medium" thumbnail regeneration needed for "Boardwalk" (ID 757). + * 3/3 Regenerated "large", "medium" thumbnails for "Sunburst Over River" (ID 756). + * Success: Regenerated 3 of 3 images. + * * @param string[] $args Positional arguments. * @param array{image_size?: string, 'skip-delete'?: bool, 'only-missing'?: bool, 'delete-unknown'?: bool, yes?: bool} $assoc_args Associative arguments. * @return void @@ -124,13 +133,29 @@ public function regenerate( $args, $assoc_args = array() ) { ); $image_size = $assoc_args['image_size']; - if ( $image_size && ! in_array( $image_size, get_intermediate_image_sizes(), true ) ) { - WP_CLI::error( sprintf( 'Unknown image size "%s".', $image_size ) ); + + // Support comma-separated list of image sizes. + $image_sizes = array(); + if ( $image_size ) { + $image_sizes = array_values( array_filter( array_map( 'trim', explode( ',', $image_size ) ) ) ); + } + + if ( $image_sizes ) { + $registered_sizes = get_intermediate_image_sizes(); + foreach ( $image_sizes as $size ) { + if ( ! in_array( $size, $registered_sizes, true ) ) { + WP_CLI::error( sprintf( 'Unknown image size "%s".', $size ) ); + } + } } if ( empty( $args ) ) { - if ( $image_size ) { - WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image size for all images?', $image_size ), $assoc_args ); + if ( $image_sizes ) { + if ( 1 === count( $image_sizes ) ) { + WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image size for all images?', reset( $image_sizes ) ), $assoc_args ); + } else { + WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image sizes for all images?', implode( '", "', $image_sizes ) ), $assoc_args ); + } } else { WP_CLI::confirm( 'Do you really want to regenerate all images?', $assoc_args ); } @@ -169,8 +194,8 @@ public function regenerate( $args, $assoc_args = array() ) { ) ); - if ( $image_size ) { - $image_size_filters = $this->add_image_size_filters( $image_size ); + if ( $image_sizes ) { + $image_size_filters = $this->add_image_size_filters( $image_sizes ); } $number = 0; @@ -187,7 +212,7 @@ public function regenerate( $args, $assoc_args = array() ) { // @phpstan-ignore function.deprecated Utils\wp_clear_object_cache(); } - $this->process_regeneration( $post_id, $skip_delete, $only_missing, $delete_unknown, $image_size, $number . '/' . $count, $successes, $errors, $skips ); + $this->process_regeneration( $post_id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $number . '/' . $count, $successes, $errors, $skips ); } if ( isset( $image_size_filters ) ) { @@ -674,7 +699,7 @@ private function make_copy( $path ) { * @param bool $skip_delete * @param bool $only_missing * @param bool $delete_unknown - * @param string $image_size + * @param string[] $image_sizes * @param string $progress * @param int $successes * @param int $errors @@ -684,7 +709,7 @@ private function make_copy( $path ) { * @param-out int $skips * @return void */ - private function process_regeneration( $id, $skip_delete, $only_missing, $delete_unknown, $image_size, $progress, &$successes, &$errors, &$skips ) { + private function process_regeneration( $id, $skip_delete, $only_missing, $delete_unknown, $image_sizes, $progress, &$successes, &$errors, &$skips ) { $title = get_the_title( $id ); if ( '' === $title ) { @@ -698,7 +723,13 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } else { $att_desc = sprintf( '"%1$s" (ID %2$d)', $title, $id ); } - $thumbnail_desc = $image_size ? sprintf( '"%s" thumbnail', $image_size ) : 'thumbnail'; + if ( count( $image_sizes ) === 1 ) { + $thumbnail_desc = sprintf( '"%s" thumbnail', reset( $image_sizes ) ); + } elseif ( count( $image_sizes ) > 1 ) { + $thumbnail_desc = sprintf( '"%s" thumbnails', implode( '", "', $image_sizes ) ); + } else { + $thumbnail_desc = 'thumbnail'; + } $fullsizepath = $this->get_attached_file( $id ); @@ -720,7 +751,7 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete return; } - $needs_regeneration = $this->needs_regeneration( $id, $fullsizepath, $is_pdf, $image_size, $skip_delete, $skip_it ); + $needs_regeneration = $this->needs_regeneration( $id, $fullsizepath, $is_pdf, $image_sizes, $skip_delete, $skip_it ); if ( $skip_it ) { WP_CLI::log( "$progress Skipped $thumbnail_desc regeneration for $att_desc." ); @@ -735,12 +766,12 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } $site_icon_filter = $this->add_site_icon_filter( $id ); - // When regenerating a specific image size, use the file that WordPress normally + // When regenerating specific image size(s), use the file that WordPress normally // serves (the scaled version for big images), not the original pre-scaled file. // This prevents wp_generate_attachment_metadata() from re-creating the scaled // version or auto-rotating the original during specific-size regeneration. $generate_file = $fullsizepath; - if ( $image_size && ! $is_pdf ) { + if ( $image_sizes && ! $is_pdf ) { $wp_attached_file = \get_attached_file( $id ); if ( $wp_attached_file && file_exists( $wp_attached_file ) ) { $generate_file = $wp_attached_file; @@ -753,8 +784,8 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete remove_filter( 'intermediate_image_sizes_advanced', $site_icon_filter ); } - // Note it's possible for no metadata to be generated for PDFs if restricted to a specific image size. - if ( empty( $metadata ) && ! ( $is_pdf && $image_size ) ) { + // Note it's possible for no metadata to be generated for PDFs if restricted to specific image size(s). + if ( empty( $metadata ) && ! ( $is_pdf && $image_sizes ) ) { WP_CLI::warning( sprintf( 'No metadata. (ID %d)', $id ) ); WP_CLI::log( "$progress Couldn't regenerate thumbnails for $att_desc." ); ++$errors; @@ -762,15 +793,15 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } // On read error, we might only get the filesize returned and nothing else. - if ( 1 === count( $metadata ) && array_key_exists( 'filesize', $metadata ) && ! ( $is_pdf && $image_size ) ) { + if ( 1 === count( $metadata ) && array_key_exists( 'filesize', $metadata ) && ! ( $is_pdf && $image_sizes ) ) { WP_CLI::warning( sprintf( 'Read error while retrieving metadata. (ID %d)', $id ) ); WP_CLI::log( "$progress Couldn't regenerate thumbnails for $att_desc." ); ++$errors; return; } - if ( $image_size ) { - if ( $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_size, $original_meta ) ) { + if ( $image_sizes ) { + if ( $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_sizes, $original_meta ) ) { WP_CLI::log( "$progress Regenerated $thumbnail_desc for $att_desc." ); } else { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); @@ -788,20 +819,20 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete * * @param array $metadata * @param string $fullsizepath - * @param string $image_size + * @param string[] $image_sizes * @return void */ - private function remove_old_images( $metadata, $fullsizepath, $image_size ) { + private function remove_old_images( $metadata, $fullsizepath, $image_sizes ) { if ( empty( $metadata['sizes'] ) ) { return; } - if ( $image_size ) { - if ( empty( $metadata['sizes'][ $image_size ] ) ) { + if ( $image_sizes ) { + $metadata['sizes'] = array_intersect_key( $metadata['sizes'], array_flip( $image_sizes ) ); + if ( empty( $metadata['sizes'] ) ) { return; } - $metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] ); } $dir_path = dirname( $fullsizepath ) . '/'; @@ -825,13 +856,13 @@ private function remove_old_images( $metadata, $fullsizepath, $image_size ) { * @param int $att_id * @param string $fullsizepath * @param bool $is_pdf - * @param string $image_size + * @param string[] $image_sizes * @param bool $skip_delete * @param bool $skip_it * @param-out bool $skip_it * @return bool */ - private function needs_regeneration( $att_id, $fullsizepath, $is_pdf, $image_size, $skip_delete, &$skip_it ) { + private function needs_regeneration( $att_id, $fullsizepath, $is_pdf, $image_sizes, $skip_delete, &$skip_it ) { // Assume not skipping. $skip_it = false; @@ -839,13 +870,13 @@ private function needs_regeneration( $att_id, $fullsizepath, $is_pdf, $image_siz // Note: zero-length string returned if no metadata, for instance if PDF or non-standard image (eg an SVG). $metadata = wp_get_attachment_metadata( $att_id ); - $image_sizes = $this->get_intermediate_image_sizes_for_attachment( $fullsizepath, $is_pdf, $metadata, $att_id ); + $attachment_sizes = $this->get_intermediate_image_sizes_for_attachment( $fullsizepath, $is_pdf, $metadata, $att_id ); // First check if no applicable editor currently available (non-destructive - ie old thumbnails not removed). - if ( is_wp_error( $image_sizes ) && 'image_no_editor' === $image_sizes->get_error_code() ) { + if ( is_wp_error( $attachment_sizes ) && 'image_no_editor' === $attachment_sizes->get_error_code() ) { // Warn unless PDF or non-standard image. if ( ! $is_pdf && is_array( $metadata ) && ! empty( $metadata['sizes'] ) ) { - WP_CLI::warning( sprintf( '%s (ID %d)', $image_sizes->get_error_message(), $att_id ) ); + WP_CLI::warning( sprintf( '%s (ID %d)', $attachment_sizes->get_error_message(), $att_id ) ); } $skip_it = true; return false; @@ -862,34 +893,43 @@ private function needs_regeneration( $att_id, $fullsizepath, $is_pdf, $image_siz // Remove any old thumbnails (so now destructive). if ( ! $skip_delete ) { - $this->remove_old_images( $metadata, $fullsizepath, $image_size ); + $this->remove_old_images( $metadata, $fullsizepath, $image_sizes ); } // Check for any other error (such as load error) apart from no editor available. - if ( is_wp_error( $image_sizes ) ) { + if ( is_wp_error( $attachment_sizes ) ) { // Warn but assume it may be possible to regenerate and allow processing to continue and possibly fail. - WP_CLI::warning( sprintf( '%s (ID %d)', $image_sizes->get_error_message(), $att_id ) ); + WP_CLI::warning( sprintf( '%s (ID %d)', $attachment_sizes->get_error_message(), $att_id ) ); return true; } // Have sizes - check whether they're new ones or they've changed. Note that an attachment can have no sizes if it's on or below the thumbnail threshold. - if ( $image_size ) { - if ( empty( $image_sizes[ $image_size ] ) ) { + if ( $image_sizes ) { + // Filter to only the requested sizes that are applicable to this attachment. + $filtered_sizes = array_intersect_key( $attachment_sizes, array_flip( $image_sizes ) ); + + if ( empty( $filtered_sizes ) ) { return false; } - if ( empty( $metadata['sizes'][ $image_size ] ) ) { - return true; + + // Check if any applicable requested size is missing from metadata. + foreach ( array_keys( $filtered_sizes ) as $size ) { + if ( empty( $metadata['sizes'][ $size ] ) ) { + return true; + } } /** * @var array{sizes: array>} $metadata */ - $metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] ); + // Filter metadata and attachment_sizes to only the applicable requested sizes. + $metadata['sizes'] = array_intersect_key( $metadata['sizes'], $filtered_sizes ); + $attachment_sizes = $filtered_sizes; } - if ( $this->image_sizes_differ( $image_sizes, $metadata['sizes'] ) ) { + if ( $this->image_sizes_differ( $attachment_sizes, $metadata['sizes'] ) ) { return true; } @@ -1054,30 +1094,24 @@ private function get_intermediate_sizes( $is_pdf, $metadata, $att_id ) { } /** - * Add filters to only process a particular intermediate image size in wp_generate_attachment_metadata(). + * Add filters to only process particular intermediate image sizes in wp_generate_attachment_metadata(). * - * @param string $image_size + * @param string[] $image_sizes * @return array */ - private function add_image_size_filters( $image_size ) { + private function add_image_size_filters( $image_sizes ) { $image_size_filters = array(); // For images. - $image_size_filters['intermediate_image_sizes_advanced'] = function ( $sizes ) use ( $image_size ) { + $image_size_filters['intermediate_image_sizes_advanced'] = function ( $sizes ) use ( $image_sizes ) { // $sizes is associative array of name => size info entries. - if ( isset( $sizes[ $image_size ] ) ) { - return array( $image_size => $sizes[ $image_size ] ); - } - return array(); + return array_intersect_key( $sizes, array_flip( $image_sizes ) ); }; // For PDF previews. - $image_size_filters['fallback_intermediate_image_sizes'] = function ( $fallback_sizes ) use ( $image_size ) { + $image_size_filters['fallback_intermediate_image_sizes'] = function ( $fallback_sizes ) use ( $image_sizes ) { // $fallback_sizes is indexed array of size names. - if ( in_array( $image_size, $fallback_sizes, true ) ) { - return array( $image_size ); - } - return array(); + return array_values( array_intersect( $fallback_sizes, $image_sizes ) ); }; foreach ( $image_size_filters as $name => $filter ) { @@ -1158,34 +1192,41 @@ private function filter_upload_dir( $uploads ) { } /** - * Update attachment sizes metadata just for a particular intermediate image size. + * Update attachment sizes metadata just for particular intermediate image sizes. * * @param int $id * @param array $new_metadata - * @param string $image_size + * @param string[] $image_sizes * @param array{sizes: array}|false $metadata * @return bool */ - private function update_attachment_metadata_for_image_size( $id, $new_metadata, $image_size, $metadata ) { + private function update_attachment_metadata_for_image_size( $id, $new_metadata, $image_sizes, $metadata ) { if ( ! is_array( $metadata ) ) { return false; } - // If have metadata for image_size. - if ( ! empty( $new_metadata['sizes'][ $image_size ] ) ) { - $metadata['sizes'][ $image_size ] = $new_metadata['sizes'][ $image_size ]; - wp_update_attachment_metadata( $id, $metadata ); - return true; + $regenerated = false; + $changed = false; + + foreach ( $image_sizes as $image_size ) { + // If have metadata for image_size. + if ( ! empty( $new_metadata['sizes'][ $image_size ] ) ) { + $metadata['sizes'][ $image_size ] = $new_metadata['sizes'][ $image_size ]; + $regenerated = true; + $changed = true; + } elseif ( ! empty( $metadata['sizes'][ $image_size ] ) ) { + // Else remove unused metadata if any. + unset( $metadata['sizes'][ $image_size ] ); + $changed = true; + // Treat removing unused metadata as no change (don't set $regenerated). + } } - // Else remove unused metadata if any. - if ( ! empty( $metadata['sizes'][ $image_size ] ) ) { - unset( $metadata['sizes'][ $image_size ] ); + if ( $changed ) { wp_update_attachment_metadata( $id, $metadata ); - // Treat removing unused metadata as no change. } - return false; + return $regenerated; } /** From 935f0b0874b6c4414e7c803362d162a155378742 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 03:54:11 +0000 Subject: [PATCH 3/8] Switch --image_size to repeating flag syntax per wp-cli/wp-cli#6198 Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/media-regenerate.feature | 6 +++--- src/Media_Command.php | 24 +++++++++++------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/features/media-regenerate.feature b/features/media-regenerate.feature index a10b6250..04a96c0a 100644 --- a/features/media-regenerate.feature +++ b/features/media-regenerate.feature @@ -985,7 +985,7 @@ Feature: Regenerate WordPress attachments And the return code should be 1 Scenario: Provide error message when one of the multiple image sizes is non-existent - When I try `wp media regenerate --image_size=medium,test1` + When I try `wp media regenerate --image_size=medium --image_size=test1` Then STDERR should be: """ Error: Unknown image size "test1". @@ -1024,7 +1024,7 @@ Feature: Regenerate WordPress attachments """ # Regenerate both "test1" and "test2" sizes only if missing - both should be generated. - When I run `wp media regenerate --image_size=test1,test2 --only-missing --yes` + When I run `wp media regenerate --image_size=test1 --image_size=test2 --only-missing --yes` Then STDOUT should contain: """ Found 1 image to regenerate @@ -1065,7 +1065,7 @@ Feature: Regenerate WordPress attachments """ # Run again for already-generated sizes - nothing should happen. - When I run `wp media regenerate --image_size=test1,test2 --only-missing --yes` + When I run `wp media regenerate --image_size=test1 --image_size=test2 --only-missing --yes` Then STDOUT should contain: """ 1/1 No "test1", "test2" thumbnail regeneration needed for "My imported attachment" diff --git a/src/Media_Command.php b/src/Media_Command.php index 2e9bbc53..84fb29e7 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -62,8 +62,8 @@ class Media_Command extends WP_CLI_Command { * [...] * : One or more IDs of the attachments to regenerate. * - * [--image_size=] - * : Name of the image size (or comma-separated list of image sizes) to regenerate. Only thumbnails of specified image size(s) will be regenerated, thumbnails of other image sizes will not. + * [--image_size=...] + * : Name of the image size to regenerate. Repeat the flag to specify multiple. Only thumbnails of specified image size(s) will be regenerated, thumbnails of other image sizes will not. * * [--skip-delete] * : Skip deletion of the original thumbnails. If your thumbnails are linked from sources outside your control, it's likely best to leave them around. Defaults to false. @@ -114,7 +114,7 @@ class Media_Command extends WP_CLI_Command { * Success: Regenerated 3 of 3 images. * * # Re-generate only the thumbnails of "large" and "medium" image sizes for all images. - * $ wp media regenerate --image_size=large,medium + * $ wp media regenerate --image_size=large --image_size=medium * Do you really want to regenerate the "large", "medium" image sizes for all images? [y/n] y * Found 3 images to regenerate. * 1/3 Regenerated "large", "medium" thumbnails for "Sydney Harbor Bridge" (ID 760). @@ -123,21 +123,19 @@ class Media_Command extends WP_CLI_Command { * Success: Regenerated 3 of 3 images. * * @param string[] $args Positional arguments. - * @param array{image_size?: string, 'skip-delete'?: bool, 'only-missing'?: bool, 'delete-unknown'?: bool, yes?: bool} $assoc_args Associative arguments. + * @param array{image_size?: string|string[], 'skip-delete'?: bool, 'only-missing'?: bool, 'delete-unknown'?: bool, yes?: bool} $assoc_args Associative arguments. * @return void */ public function regenerate( $args, $assoc_args = array() ) { - $assoc_args = wp_parse_args( - $assoc_args, - [ 'image_size' => '' ] - ); - - $image_size = $assoc_args['image_size']; + // Extract image_size separately as it may be a string or an array of strings. + $image_size_raw = $assoc_args['image_size'] ?? null; + unset( $assoc_args['image_size'] ); - // Support comma-separated list of image sizes. + // Normalize to an array: with WP-CLI 3.x and the ellipsis syntax, repeated flags yield an array. + // With earlier versions a single string is returned. $image_sizes = array(); - if ( $image_size ) { - $image_sizes = array_values( array_filter( array_map( 'trim', explode( ',', $image_size ) ) ) ); + if ( null !== $image_size_raw ) { + $image_sizes = is_array( $image_size_raw ) ? $image_size_raw : [ $image_size_raw ]; } if ( $image_sizes ) { From 81ee92092b168a59b7c2a8727695fcfc63c1f2de Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 10 Mar 2026 10:04:27 +0100 Subject: [PATCH 4/8] Require wp-cli v2.13 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 18dca436..c5a3779b 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ } ], "require": { - "wp-cli/wp-cli": "^2.12" + "wp-cli/wp-cli": "^2.13" }, "require-dev": { "wp-cli/entity-command": "^1.3 || ^2", From 751c03eb70670df7ab2aa2ec7d7037e8454ab0ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 09:25:08 +0000 Subject: [PATCH 5/8] Fix test: use plural "thumbnails" for multiple image sizes Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/media-regenerate.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/media-regenerate.feature b/features/media-regenerate.feature index 04a96c0a..2890d49c 100644 --- a/features/media-regenerate.feature +++ b/features/media-regenerate.feature @@ -1068,7 +1068,7 @@ Feature: Regenerate WordPress attachments When I run `wp media regenerate --image_size=test1 --image_size=test2 --only-missing --yes` Then STDOUT should contain: """ - 1/1 No "test1", "test2" thumbnail regeneration needed for "My imported attachment" + 1/1 No "test1", "test2" thumbnails regeneration needed for "My imported attachment" """ And STDOUT should contain: """ From 21eb213bb9988c916f8f1a1aeeb481d10068a01b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 11:48:18 +0000 Subject: [PATCH 6/8] Derive log thumbnail_desc from actually-regenerated sizes, not requested sizes Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index 84fb29e7..d33685bc 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -799,8 +799,14 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } if ( $image_sizes ) { - if ( $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_sizes, $original_meta ) ) { - WP_CLI::log( "$progress Regenerated $thumbnail_desc for $att_desc." ); + $regenerated_sizes = $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_sizes, $original_meta ); + if ( $regenerated_sizes ) { + if ( count( $regenerated_sizes ) === 1 ) { + $regenerated_desc = sprintf( '"%s" thumbnail', reset( $regenerated_sizes ) ); + } else { + $regenerated_desc = sprintf( '"%s" thumbnails', implode( '", "', $regenerated_sizes ) ); + } + WP_CLI::log( "$progress Regenerated $regenerated_desc for $att_desc." ); } else { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); } @@ -1196,35 +1202,35 @@ private function filter_upload_dir( $uploads ) { * @param array $new_metadata * @param string[] $image_sizes * @param array{sizes: array}|false $metadata - * @return bool + * @return string[] The sizes that were actually regenerated. */ private function update_attachment_metadata_for_image_size( $id, $new_metadata, $image_sizes, $metadata ) { if ( ! is_array( $metadata ) ) { - return false; + return array(); } - $regenerated = false; - $changed = false; + $regenerated_sizes = array(); + $changed = false; foreach ( $image_sizes as $image_size ) { // If have metadata for image_size. if ( ! empty( $new_metadata['sizes'][ $image_size ] ) ) { $metadata['sizes'][ $image_size ] = $new_metadata['sizes'][ $image_size ]; - $regenerated = true; + $regenerated_sizes[] = $image_size; $changed = true; } elseif ( ! empty( $metadata['sizes'][ $image_size ] ) ) { // Else remove unused metadata if any. unset( $metadata['sizes'][ $image_size ] ); $changed = true; - // Treat removing unused metadata as no change (don't set $regenerated). + // Treat removing unused metadata as no change (don't add to $regenerated_sizes). } } if ( $changed ) { wp_update_attachment_metadata( $id, $metadata ); } - return $regenerated; + return $regenerated_sizes; } /** From 741d4d5e639b807706c97b577c41484b63f50092 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:02:43 +0000 Subject: [PATCH 7/8] Extract get_image_sizes_description() helper to remove duplicated string-building logic Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 47 ++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index d33685bc..78ed8816 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -149,11 +149,13 @@ public function regenerate( $args, $assoc_args = array() ) { if ( empty( $args ) ) { if ( $image_sizes ) { - if ( 1 === count( $image_sizes ) ) { - WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image size for all images?', reset( $image_sizes ) ), $assoc_args ); - } else { - WP_CLI::confirm( sprintf( 'Do you really want to regenerate the "%s" image sizes for all images?', implode( '", "', $image_sizes ) ), $assoc_args ); - } + WP_CLI::confirm( + sprintf( + 'Do you really want to regenerate the %s for all images?', + $this->get_image_sizes_description( $image_sizes, 'image size', 'image sizes' ) + ), + $assoc_args + ); } else { WP_CLI::confirm( 'Do you really want to regenerate all images?', $assoc_args ); } @@ -690,6 +692,26 @@ private function make_copy( $path ) { return $filename; } + /** + * Returns a human-readable description for one or more image size names. + * + * @param string[] $sizes The size names. + * @param string $singular_noun Noun to use when exactly one size is given (e.g. 'thumbnail'). + * @param string $plural_noun Noun to use when more than one size is given (e.g. 'thumbnails'). + * @param string $default_if_empty String to return when $sizes is empty. + * @return string + */ + private function get_image_sizes_description( array $sizes, $singular_noun, $plural_noun, $default_if_empty = '' ) { + $count = count( $sizes ); + if ( 0 === $count ) { + return $default_if_empty; + } + if ( 1 === $count ) { + return sprintf( '"%s" %s', reset( $sizes ), $singular_noun ); + } + return sprintf( '"%s" %s', implode( '", "', $sizes ), $plural_noun ); + } + /** * Process media regeneration * @@ -721,13 +743,7 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } else { $att_desc = sprintf( '"%1$s" (ID %2$d)', $title, $id ); } - if ( count( $image_sizes ) === 1 ) { - $thumbnail_desc = sprintf( '"%s" thumbnail', reset( $image_sizes ) ); - } elseif ( count( $image_sizes ) > 1 ) { - $thumbnail_desc = sprintf( '"%s" thumbnails', implode( '", "', $image_sizes ) ); - } else { - $thumbnail_desc = 'thumbnail'; - } + $thumbnail_desc = $this->get_image_sizes_description( $image_sizes, 'thumbnail', 'thumbnails', 'thumbnail' ); $fullsizepath = $this->get_attached_file( $id ); @@ -801,12 +817,7 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete if ( $image_sizes ) { $regenerated_sizes = $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_sizes, $original_meta ); if ( $regenerated_sizes ) { - if ( count( $regenerated_sizes ) === 1 ) { - $regenerated_desc = sprintf( '"%s" thumbnail', reset( $regenerated_sizes ) ); - } else { - $regenerated_desc = sprintf( '"%s" thumbnails', implode( '", "', $regenerated_sizes ) ); - } - WP_CLI::log( "$progress Regenerated $regenerated_desc for $att_desc." ); + WP_CLI::log( "$progress Regenerated {$this->get_image_sizes_description( $regenerated_sizes, 'thumbnail', 'thumbnails' )} for $att_desc." ); } else { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); } From 2251e5228c64af1a80922a26888b89dec771110c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:11:13 +0000 Subject: [PATCH 8/8] Use Utils\pluralize() in get_image_sizes_description() instead of manual plural_noun parameter Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- src/Media_Command.php | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Media_Command.php b/src/Media_Command.php index 78ed8816..eaa49ccf 100644 --- a/src/Media_Command.php +++ b/src/Media_Command.php @@ -152,7 +152,7 @@ public function regenerate( $args, $assoc_args = array() ) { WP_CLI::confirm( sprintf( 'Do you really want to regenerate the %s for all images?', - $this->get_image_sizes_description( $image_sizes, 'image size', 'image sizes' ) + $this->get_image_sizes_description( $image_sizes, 'image size' ) ), $assoc_args ); @@ -696,20 +696,15 @@ private function make_copy( $path ) { * Returns a human-readable description for one or more image size names. * * @param string[] $sizes The size names. - * @param string $singular_noun Noun to use when exactly one size is given (e.g. 'thumbnail'). - * @param string $plural_noun Noun to use when more than one size is given (e.g. 'thumbnails'). + * @param string $noun Noun in singular form (e.g. 'thumbnail'); pluralized automatically. * @param string $default_if_empty String to return when $sizes is empty. * @return string */ - private function get_image_sizes_description( array $sizes, $singular_noun, $plural_noun, $default_if_empty = '' ) { - $count = count( $sizes ); - if ( 0 === $count ) { + private function get_image_sizes_description( array $sizes, $noun, $default_if_empty = '' ) { + if ( empty( $sizes ) ) { return $default_if_empty; } - if ( 1 === $count ) { - return sprintf( '"%s" %s', reset( $sizes ), $singular_noun ); - } - return sprintf( '"%s" %s', implode( '", "', $sizes ), $plural_noun ); + return sprintf( '"%s" %s', implode( '", "', $sizes ), Utils\pluralize( $noun, count( $sizes ) ) ); } /** @@ -743,7 +738,7 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete } else { $att_desc = sprintf( '"%1$s" (ID %2$d)', $title, $id ); } - $thumbnail_desc = $this->get_image_sizes_description( $image_sizes, 'thumbnail', 'thumbnails', 'thumbnail' ); + $thumbnail_desc = $this->get_image_sizes_description( $image_sizes, 'thumbnail', 'thumbnail' ); $fullsizepath = $this->get_attached_file( $id ); @@ -817,7 +812,7 @@ private function process_regeneration( $id, $skip_delete, $only_missing, $delete if ( $image_sizes ) { $regenerated_sizes = $this->update_attachment_metadata_for_image_size( $id, $metadata, $image_sizes, $original_meta ); if ( $regenerated_sizes ) { - WP_CLI::log( "$progress Regenerated {$this->get_image_sizes_description( $regenerated_sizes, 'thumbnail', 'thumbnails' )} for $att_desc." ); + WP_CLI::log( "$progress Regenerated {$this->get_image_sizes_description( $regenerated_sizes, 'thumbnail' )} for $att_desc." ); } else { WP_CLI::log( "$progress No $thumbnail_desc regeneration needed for $att_desc." ); }