Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion src/wp-admin/includes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,66 @@ function wp_create_image_subsizes( $file, $attachment_id ) {
return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
}

/**
* Adds explicit filename suffixes for sub-size requests that would otherwise collide.
*
* When multiple requested sizes share the same target dimensions, cropped variants
* can produce different images while still resolving to the same default filename
* based on those width and height values. In that case, add a crop-specific suffix
* to the cropped variants only.
*
* @since 7.1.0
* @access private
*
* @param array $new_sizes Registered image sub-sizes keyed by size name.
* @return array
*/
function _wp_add_subsize_suffixes( $new_sizes ) {
$sizes_by_dimensions = array();

foreach ( $new_sizes as $size_name => $size_data ) {
$width = isset( $size_data['width'] ) ? (int) $size_data['width'] : 0;
$height = isset( $size_data['height'] ) ? (int) $size_data['height'] : 0;

$sizes_by_dimensions[ "{$width}x{$height}" ][] = $size_name;
}

foreach ( $sizes_by_dimensions as $dimensions => $size_names ) {
if ( count( $size_names ) < 2 ) {
continue;
}

$used_suffixes = array();

foreach ( $size_names as $size_name ) {
$crop = $new_sizes[ $size_name ]['crop'] ?? false;

if ( false === $crop ) {
continue;
}

$crop_suffix = 'crop';

if ( is_array( $crop ) ) {
$crop_suffix .= '-' . implode( '-', array_map( 'sanitize_key', $crop ) );
}

$suffix = "{$dimensions}-{$crop_suffix}";
$index = 2;

while ( in_array( $suffix, $used_suffixes, true ) ) {
$suffix = "{$dimensions}-{$crop_suffix}-{$index}";
++$index;
}

$new_sizes[ $size_name ]['suffix'] = $suffix;
$used_suffixes[] = $suffix;
}
}

return $new_sizes;
}

/**
* Low-level function to create image sub-sizes.
*
Expand Down Expand Up @@ -467,9 +527,9 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
);

$new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
$new_sizes = _wp_add_subsize_suffixes( $new_sizes );

$editor = wp_get_image_editor( $file );

if ( is_wp_error( $editor ) ) {
// The image cannot be edited.
return $image_meta;
Expand Down
12 changes: 9 additions & 3 deletions src/wp-includes/class-wp-image-editor-gd.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,19 @@ public function make_subsize( $size_data ) {
if ( is_wp_error( $resized ) ) {
$saved = $resized;
} else {
$saved = $this->_save( $resized );
$filename = null;

if ( PHP_VERSION_ID < 80000 ) { // imagedestroy() has no effect as of PHP 8.0.
if ( ! empty( $size_data['suffix'] ) ) {
$filename = $this->generate_filename( $size_data['suffix'] );
}

$saved = $this->_save( $resized, $filename );

if ( PHP_VERSION_ID < 80000 ) {
// imagedestroy() has no effect as of PHP 8.0.
imagedestroy( $resized );
}
}

$this->size = $orig_size;

if ( ! is_wp_error( $saved ) ) {
Expand Down
8 changes: 7 additions & 1 deletion src/wp-includes/class-wp-image-editor-imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,13 @@ public function make_subsize( $size_data ) {
if ( is_wp_error( $resized ) ) {
$saved = $resized;
} else {
$saved = $this->_save( $this->image );
$filename = null;

if ( ! empty( $size_data['suffix'] ) ) {
$filename = $this->generate_filename( $size_data['suffix'] );
}

$saved = $this->_save( $this->image, $filename );

$this->image->clear();
$this->image->destroy();
Expand Down
53 changes: 52 additions & 1 deletion tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -5798,7 +5798,6 @@ public function test_wp_generate_attachment_metadata_doesnt_generate_sizes_for_1
$temp_dir = get_temp_dir();
$file = $temp_dir . '/test-square-150.jpg';
copy( DIR_TESTDATA . '/images/test-square-150.jpg', $file );

$attachment_id = self::factory()->attachment->create_object(
array(
'post_mime_type' => 'image/jpeg',
Expand Down Expand Up @@ -5829,6 +5828,58 @@ public function test_wp_generate_attachment_metadata_doesnt_generate_sizes_for_1
);
}

/**
* Tests that sizes with matching dimensions but different crop modes get distinct filenames.
*
* @ticket 62388
*/
public function test_wp_generate_attachment_metadata_adds_unique_suffixes_for_duplicate_cropped_sizes() {
$temp_dir = get_temp_dir();
$file = $temp_dir . '/test-image-62388.jpg';
copy( DIR_TESTDATA . '/images/33772.jpg', $file );

$attachment_id = self::factory()->attachment->create_object(
array(
'post_mime_type' => 'image/jpeg',
'file' => $file,
)
);

add_filter( 'intermediate_image_sizes_advanced', array( $this, 'filter_duplicate_cropped_subsizes' ) );

$metadata = wp_generate_attachment_metadata( $attachment_id, $file );

remove_filter( 'intermediate_image_sizes_advanced', array( $this, 'filter_duplicate_cropped_subsizes' ) );

$this->assertArrayHasKey( 'cropped_default', $metadata['sizes'] );
$this->assertArrayHasKey( 'cropped_right_bottom', $metadata['sizes'] );
$this->assertNotSame( $metadata['sizes']['cropped_default']['file'], $metadata['sizes']['cropped_right_bottom']['file'] );
$this->assertStringContainsString( '150x150-crop', $metadata['sizes']['cropped_default']['file'] );
$this->assertStringContainsString( '150x150-crop-right-bottom', $metadata['sizes']['cropped_right_bottom']['file'] );
$this->assertFileExists( dirname( $file ) . '/' . $metadata['sizes']['cropped_default']['file'] );
$this->assertFileExists( dirname( $file ) . '/' . $metadata['sizes']['cropped_right_bottom']['file'] );
}

/**
* Filters generated sub-sizes for testing duplicate dimension crops.
*
* @return array[]
*/
public function filter_duplicate_cropped_subsizes() {
return array(
'cropped_default' => array(
'width' => 150,
'height' => 150,
'crop' => true,
),
'cropped_right_bottom' => array(
'width' => 150,
'height' => 150,
'crop' => array( 'right', 'bottom' ),
),
);
}

/**
* Tests that `wp_get_attachment_image()` uses the correct default context.
*
Expand Down
Loading