Skip to content
3 changes: 3 additions & 0 deletions src/wp-admin/includes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {

if ( method_exists( $editor, 'make_subsize' ) ) {
foreach ( $new_sizes as $new_size_name => $new_size_data ) {
// Include size name in the data.
$new_size_data['name'] = $new_size_name;
Comment thread
adamsilverstein marked this conversation as resolved.

$new_size_meta = $editor->make_subsize( $new_size_data );

if ( is_wp_error( $new_size_meta ) ) {
Expand Down
21 changes: 16 additions & 5 deletions src/wp-includes/class-wp-image-editor-gd.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ protected function _resize( $max_w, $max_h, $crop = false ) {
* @since 3.5.0
*
* @param array $sizes {
* An array of image size data arrays.
* Associative array of image size names and their data.
*
* Either a height or width must be provided.
* If one of the two is set to null, the resize will
Expand All @@ -247,6 +247,9 @@ public function multi_resize( $sizes ) {
$metadata = array();

foreach ( $sizes as $size => $size_data ) {
// Include size name in the data.
$size_data['name'] = $size;

$meta = $this->make_subsize( $size_data );

if ( ! is_wp_error( $meta ) ) {
Expand All @@ -261,13 +264,15 @@ public function multi_resize( $sizes ) {
* Create an image sub-size and return the image meta data value for it.
*
* @since 5.3.0
* @since 6.1.0 The $sizes parameter may now include a $name key for each entry.
*
* @param array $size_data {
* Array of size data.
*
* @type int $width The maximum width in pixels.
* @type int $height The maximum height in pixels.
* @type bool $crop Whether to crop the image to exact dimensions.
* @type int $width The maximum width in pixels.
* @type int $height The maximum height in pixels.
* @type bool $crop Whether to crop the image to exact dimensions.
* @type string $name Image size name.
* }
* @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta,
* WP_Error object on error.
Expand All @@ -277,7 +282,8 @@ public function make_subsize( $size_data ) {
return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) );
}

$orig_size = $this->size;
$orig_size = $this->size;
$orig_size_name = $this->size_name;

if ( ! isset( $size_data['width'] ) ) {
$size_data['width'] = null;
Expand All @@ -291,6 +297,10 @@ public function make_subsize( $size_data ) {
$size_data['crop'] = false;
}

if ( isset( $size_data['name'] ) ) {
$this->update_size_name( $size_data['name'] );
}

$resized = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] );

if ( is_wp_error( $resized ) ) {
Expand All @@ -301,6 +311,7 @@ public function make_subsize( $size_data ) {
}

$this->size = $orig_size;
$this->size_name = $orig_size_name;

if ( ! is_wp_error( $saved ) ) {
unset( $saved['path'] );
Expand Down
27 changes: 19 additions & 8 deletions src/wp-includes/class-wp-image-editor-imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIAN
* @since 3.5.0
*
* @param array $sizes {
* An array of image size data arrays.
* Associative array of image size names and their data.
*
* Either a height or width must be provided.
* If one of the two is set to null, the resize will
Expand All @@ -458,6 +458,9 @@ public function multi_resize( $sizes ) {
$metadata = array();

foreach ( $sizes as $size => $size_data ) {
// Include size name in the data.
$size_data['name'] = $size;

$meta = $this->make_subsize( $size_data );

if ( ! is_wp_error( $meta ) ) {
Expand All @@ -472,13 +475,15 @@ public function multi_resize( $sizes ) {
* Create an image sub-size and return the image meta data value for it.
*
* @since 5.3.0
* @since 6.1.0 The $sizes parameter may now include a $name key for each entry.
*
* @param array $size_data {
* Array of size data.
*
* @type int $width The maximum width in pixels.
* @type int $height The maximum height in pixels.
* @type bool $crop Whether to crop the image to exact dimensions.
* @type int $width The maximum width in pixels.
* @type int $height The maximum height in pixels.
* @type bool $crop Whether to crop the image to exact dimensions.
* @type string $name Image size name.
* }
* @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta,
* WP_Error object on error.
Expand All @@ -488,8 +493,9 @@ public function make_subsize( $size_data ) {
return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) );
}

$orig_size = $this->size;
$orig_image = $this->image->getImage();
$orig_size = $this->size;
$orig_size_name = $this->size_name;
$orig_image = $this->image->getImage();

if ( ! isset( $size_data['width'] ) ) {
$size_data['width'] = null;
Expand All @@ -503,6 +509,10 @@ public function make_subsize( $size_data ) {
$size_data['crop'] = false;
}

if ( isset( $size_data['name'] ) ) {
$this->update_size_name( $size_data['name'] );
}

$resized = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );

if ( is_wp_error( $resized ) ) {
Expand All @@ -515,8 +525,9 @@ public function make_subsize( $size_data ) {
$this->image = null;
}

$this->size = $orig_size;
$this->image = $orig_image;
$this->size = $orig_size;
$this->size_name = $orig_size_name;
$this->image = $orig_image;

if ( ! is_wp_error( $saved ) ) {
unset( $saved['path'] );
Expand Down
37 changes: 31 additions & 6 deletions src/wp-includes/class-wp-image-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
abstract class WP_Image_Editor {
protected $file = null;
protected $size = null;
protected $size_name = '';
protected $mime_type = null;
protected $output_mime_type = null;
protected $default_mime_type = 'image/jpeg';
Expand Down Expand Up @@ -117,7 +118,7 @@ abstract public function resize( $max_w, $max_h, $crop = false );
* @abstract
*
* @param array $sizes {
* An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
* Associative array of image size names and their data. Default sizes are 'small', 'medium', 'large'.
*
* @type array ...$0 {
* @type int $width Image width.
Expand Down Expand Up @@ -185,7 +186,7 @@ abstract public function stream( $mime_type = null );
*
* @since 3.5.0
*
* @return int[] {
Comment thread
adamsilverstein marked this conversation as resolved.
* @return array {
* Dimensions of the image.
*
* @type int $width The image width.
Expand All @@ -201,9 +202,9 @@ public function get_size() {
*
* @since 3.5.0
*
* @param int $width
* @param int $height
* @return true
* @param int $width The image width.
* @param int $height The image height.
* @return true True on success, false on failure.
*/
protected function update_size( $width = null, $height = null ) {
$this->size = array(
Expand All @@ -213,6 +214,28 @@ protected function update_size( $width = null, $height = null ) {
return true;
}

/**
* Gets the current image size name.
*
* @since 6.1.0
*
* @return string Image size name, or empty string if none set.
*/
public function get_size_name() {
return $this->size_name;
}

/**
* Sets the current image size name.
*
* @since 6.1.0
*
* @param string $size_name The image size name.
*/
protected function update_size_name( $size_name ) {
Comment thread
adamsilverstein marked this conversation as resolved.
$this->size_name = (string) $size_name;
}

/**
* Gets the Image Compression quality on a 1-100% scale.
*
Expand Down Expand Up @@ -364,6 +387,7 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
* @see WP_Image_Editor::get_output_format()
*
* @since 5.8.0
* @since 6.1.0 The $size_name parameter was added.
*
* @param string[] $output_format {
* An array of mime type mappings. Maps a source mime type to a new
Expand All @@ -373,8 +397,9 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
* }
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
* @param string $size_name The image size name to create, or empty string if not set.
*/
$output_format = apply_filters( 'image_editor_output_format', array(), $filename, $mime_type );
$output_format = apply_filters( 'image_editor_output_format', array(), $filename, $mime_type, $this->size_name );
Comment thread
felixarntz marked this conversation as resolved.

if ( isset( $output_format[ $mime_type ] )
&& $this->supports_mime_type( $output_format[ $mime_type ] )
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@
add_filter( 'image_send_to_editor', 'image_add_caption', 20, 8 );
add_filter( 'media_send_to_editor', 'image_media_send_to_editor', 10, 3 );

add_filter( 'image_editor_output_format', 'wp_default_image_output_mapping' );
add_filter( 'image_editor_output_format', 'wp_default_image_output_mapping', 10, 4 );

// Embeds.
add_action( 'rest_api_init', 'wp_oembed_register_route' );
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2690,7 +2690,7 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null )
*/
if ( $is_image ) {
/** This filter is documented in wp-includes/class-wp-image-editor.php */
$output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type );
$output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type, '' );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call out in the dev note that there is no size when core calls wp_unique_filename because this is applied only to the upload image (not the sub-sizes). might be confusing that it is called this way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsilverstein Good point, the additional parameter for this definitely needs to be explained in the dev note. FWIW, it's not only in wp_unique_filename that this is empty, it's always empty when no particular image size is being handled. For example even within WP_Image_Editor usage, the parameter will be an empty string for the original image, or the full image, or any custom operation (e.g. cropping the site icon). Only interactions that relate to a specific registered image size will result in the parameter being not empty.

$alt_types = array();

if ( ! empty( $output_formats[ $mime_type ] ) ) {
Expand Down
34 changes: 32 additions & 2 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -3912,9 +3912,39 @@ function _wp_image_editor_choose( $args = array() ) {
* @since 6.1.0
*
* @param array $output_mapping Map of mime type to output format.
* @retun array The adjusted default output mapping.
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
* @param string $size_name Optional. The image size name to create, or empty string if not set. Default empty string.
* @return array The adjusted default output mapping.
*/
function wp_default_image_output_mapping( $output_mapping ) {
function wp_default_image_output_mapping( $output_mapping, $filename, $mime_type, $size_name = '' ) {
// If size name is specified, check whether the size supports additional MIME types like WebP.
if ( $size_name ) {
// Include only the core sizes that do not rely on add_image_size(). Additional image sizes are opt-in.
$enabled_sizes = array(
'thumbnail' => true,
'medium' => true,
'medium_large' => true,
'large' => true,
'post-thumbnail' => true,
);

/**
* Filters the sizes that support secondary mime type output. Developers can use this
* to control the generation of additional mime type sub-sized images.
*
* @since 6.1.0
*
* @param array $enabled_sizes Map of size names and whether they support secondary mime type output.
*/
$enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes );

// Bail early if the size does not support additional MIME types.
if ( empty( $enabled_sizes[ $size_name ] ) ) {
return $output_mapping;
}
}

$output_mapping['image/jpeg'] = 'image/webp';
return $output_mapping;
}
Expand Down
76 changes: 74 additions & 2 deletions tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -3626,7 +3626,7 @@ private function reset_omit_loading_attr_filter() {
* @ticket 55443
*/
public function test_wp_default_image_output_mapping() {
$mapping = wp_default_image_output_mapping( array() );
$mapping = wp_default_image_output_mapping( array(), 'test.jpg', 'image/jpeg', '' );
$this->assertSame( array( 'image/jpeg' => 'image/webp' ), $mapping );
}

Expand All @@ -3637,7 +3637,7 @@ public function test_wp_default_image_output_mapping() {
*/
public function test_wp_default_image_output_mapping_existing() {
$mapping = array( 'mime/png' => 'mime/webp' );
$mapping = wp_default_image_output_mapping( $mapping );
$mapping = wp_default_image_output_mapping( $mapping, 'test.jpg', 'image/jpeg', '' );
$this->assertSame(
array(
'mime/png' => 'mime/webp',
Expand Down Expand Up @@ -3672,6 +3672,78 @@ public function test_wp_image_editor_default_output_maps_to_webp() {
$this->assertSame( 'canola-100x75.jpg', $saved['file'] );
}
}

/**
* @ticket 56526
* @dataProvider data_wp_default_image_output_mapping_size_filter
*/
public function test_wp_default_image_output_mapping_size_filter( $size_name, $filter_callback, $expects_webp ) {
remove_all_filters( 'wp_image_sizes_with_additional_mime_type_support' );
if ( $filter_callback ) {
add_filter( 'wp_image_sizes_with_additional_mime_type_support', $filter_callback );
}

$mapping = wp_default_image_output_mapping( array(), 'test.jpg', 'image/jpeg', $size_name );
if ( $expects_webp ) {
$this->assertSame( array( 'image/jpeg' => 'image/webp' ), $mapping );
} else {
$this->assertSame( array(), $mapping );
}
}

public function data_wp_default_image_output_mapping_size_filter() {
return array(
'default size thumbnail' => array(
'thumbnail',
null,
true,
),
'default size medium' => array(
'medium',
null,
true,
),
'default size medium_large' => array(
'medium_large',
null,
true,
),
'default size large' => array(
'large',
null,
true,
),
'default size unset' => array(
'medium',
function( $enabled_sizes ) {
unset( $enabled_sizes['medium'] );
return $enabled_sizes;
},
false,
),
'default size set to false' => array(
'medium',
function( $enabled_sizes ) {
$enabled_sizes['medium'] = false;
return $enabled_sizes;
},
false,
),
'custom size' => array(
'custom',
null,
false,
),
'custom size opted in' => array(
'custom',
function( $enabled_sizes ) {
$enabled_sizes['custom'] = true;
return $enabled_sizes;
},
true,
),
);
}
}

/**
Expand Down