--- a/wp/wp-includes/media.php Fri Sep 05 18:40:08 2025 +0200
+++ b/wp/wp-includes/media.php Fri Sep 05 18:52:52 2025 +0200
@@ -6,6 +6,11 @@
* @subpackage Media
*/
+// Don't load directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ die( '-1' );
+}
+
/**
* Retrieves additional image sizes.
*
@@ -288,7 +293,7 @@
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
- * @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
+ * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
*/
@@ -350,7 +355,7 @@
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
- * @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
+ * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
*/
@@ -530,7 +535,7 @@
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
- * @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
+ * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
* @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure.
@@ -684,7 +689,7 @@
* If true, image will be cropped to the specified dimensions using center positions.
* If an array, the image will be cropped using the array to specify the crop location:
*
- * @type string $0 The x crop position. Accepts 'left' 'center', or 'right'.
+ * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'.
* @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'.
* }
* @return array|false Metadata array on success. False if no image was created.
@@ -1065,7 +1070,6 @@
list( $src, $width, $height ) = $image;
$attachment = get_post( $attachment_id );
- $hwstring = image_hwstring( $width, $height );
$size_class = $size;
if ( is_array( $size_class ) ) {
@@ -1085,15 +1089,14 @@
*
* @param string $context The context. Default 'wp_get_attachment_image'.
*/
- $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );
- $attr = wp_parse_args( $attr, $default_attr );
-
- $loading_attr = $attr;
- $loading_attr['width'] = $width;
- $loading_attr['height'] = $height;
+ $context = apply_filters( 'wp_get_attachment_image_context', 'wp_get_attachment_image' );
+ $attr = wp_parse_args( $attr, $default_attr );
+ $attr['width'] = $width;
+ $attr['height'] = $height;
+
$loading_optimization_attr = wp_get_loading_optimization_attributes(
'img',
- $loading_attr,
+ $attr,
$context
);
@@ -1137,6 +1140,20 @@
}
}
+ /** This filter is documented in wp-includes/media.php */
+ $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
+
+ // Adds 'auto' to the sizes attribute if applicable.
+ if (
+ $add_auto_sizes &&
+ isset( $attr['loading'] ) &&
+ 'lazy' === $attr['loading'] &&
+ isset( $attr['sizes'] ) &&
+ ! wp_sizes_attribute_includes_valid_auto( $attr['sizes'] )
+ ) {
+ $attr['sizes'] = 'auto, ' . $attr['sizes'];
+ }
+
/**
* Filters the list of attachment image attributes.
*
@@ -1150,8 +1167,16 @@
*/
$attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
- $attr = array_map( 'esc_attr', $attr );
- $html = rtrim( "<img $hwstring" );
+ if ( isset( $attr['height'] ) && is_numeric( $attr['height'] ) ) {
+ $height = absint( $attr['height'] );
+ }
+ if ( isset( $attr['width'] ) && is_numeric( $attr['width'] ) ) {
+ $width = absint( $attr['width'] );
+ }
+ unset( $attr['height'], $attr['width'] );
+ $attr = array_map( 'esc_attr', $attr );
+ $hwstring = image_hwstring( $width, $height );
+ $html = rtrim( "<img $hwstring" );
foreach ( $attr as $name => $value ) {
$html .= " $name=" . '"' . $value . '"';
@@ -1917,6 +1942,9 @@
// Add loading optimization attributes if applicable.
$filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context );
+ // Adds 'auto' to the sizes attribute if applicable.
+ $filtered_image = wp_img_tag_add_auto_sizes( $filtered_image );
+
/**
* Filters an img tag within the content for a given context.
*
@@ -1964,6 +1992,103 @@
}
/**
+ * Adds 'auto' to the sizes attribute to the image, if the image is lazy loaded and does not already include it.
+ *
+ * @since 6.7.0
+ *
+ * @param string $image The image tag markup being filtered.
+ * @return string The filtered image tag markup.
+ */
+function wp_img_tag_add_auto_sizes( string $image ): string {
+ /**
+ * Filters whether auto-sizes for lazy loaded images is enabled.
+ *
+ * @since 6.7.1
+ *
+ * @param boolean $enabled Whether auto-sizes for lazy loaded images is enabled.
+ */
+ if ( ! apply_filters( 'wp_img_tag_add_auto_sizes', true ) ) {
+ return $image;
+ }
+
+ $processor = new WP_HTML_Tag_Processor( $image );
+
+ // Bail if there is no IMG tag.
+ if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
+ return $image;
+ }
+
+ // Bail early if the image is not lazy-loaded.
+ $loading = $processor->get_attribute( 'loading' );
+ if ( ! is_string( $loading ) || 'lazy' !== strtolower( trim( $loading, " \t\f\r\n" ) ) ) {
+ return $image;
+ }
+
+ /*
+ * Bail early if the image doesn't have a width attribute.
+ * Per WordPress Core itself, lazy-loaded images should always have a width attribute.
+ * However, it is possible that lazy-loading could be added by a plugin, where we don't have that guarantee.
+ * As such, it still makes sense to ensure presence of a width attribute here in order to use `sizes=auto`.
+ */
+ $width = $processor->get_attribute( 'width' );
+ if ( ! is_string( $width ) || '' === $width ) {
+ return $image;
+ }
+
+ $sizes = $processor->get_attribute( 'sizes' );
+
+ // Bail early if the image is not responsive.
+ if ( ! is_string( $sizes ) ) {
+ return $image;
+ }
+
+ // Don't add 'auto' to the sizes attribute if it already exists.
+ if ( wp_sizes_attribute_includes_valid_auto( $sizes ) ) {
+ return $image;
+ }
+
+ $processor->set_attribute( 'sizes', "auto, $sizes" );
+ return $processor->get_updated_html();
+}
+
+/**
+ * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list.
+ *
+ * Per the HTML spec, if present it must be the first entry.
+ *
+ * @since 6.7.0
+ *
+ * @param string $sizes_attr The 'sizes' attribute value.
+ * @return bool True if the 'auto' keyword is present, false otherwise.
+ */
+function wp_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool {
+ list( $first_size ) = explode( ',', $sizes_attr, 2 );
+ return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) );
+}
+
+/**
+ * Prints a CSS rule to fix potential visual issues with images using `sizes=auto`.
+ *
+ * This rule overrides the similar rule in the default user agent stylesheet, to avoid images that use e.g.
+ * `width: auto` or `width: fit-content` to appear smaller.
+ *
+ * @since 6.7.1
+ * @see https://html.spec.whatwg.org/multipage/rendering.html#img-contain-size
+ * @see https://core.trac.wordpress.org/ticket/62413
+ */
+function wp_print_auto_sizes_contain_css_fix() {
+ /** This filter is documented in wp-includes/media.php */
+ $add_auto_sizes = apply_filters( 'wp_img_tag_add_auto_sizes', true );
+ if ( ! $add_auto_sizes ) {
+ return;
+ }
+
+ ?>
+ <style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style>
+ <?php
+}
+
+/**
* Adds optimization attributes to an `img` HTML tag.
*
* @since 6.3.0
@@ -1973,6 +2098,7 @@
* @return string Converted `img` tag with optimization attributes added.
*/
function wp_img_tag_add_loading_optimization_attrs( $image, $context ) {
+ $src = preg_match( '/ src=["\']?([^"\']*)/i', $image, $matche_src ) ? $matche_src[1] : null;
$width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null;
$height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null;
$loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null;
@@ -1987,6 +2113,7 @@
$optimization_attrs = wp_get_loading_optimization_attributes(
'img',
array(
+ 'src' => $src,
'width' => $width,
'height' => $height,
'loading' => $loading_val,
@@ -3224,6 +3351,7 @@
* WordPress mp3s in a post.
*
* @since 3.6.0
+ * @since 6.8.0 Added the 'muted' attribute.
*
* @param array $attr {
* Attributes of the audio shortcode.
@@ -3231,6 +3359,7 @@
* @type string $src URL to the source of the audio file. Default empty.
* @type string $loop The 'loop' attribute for the `<audio>` element. Default empty.
* @type string $autoplay The 'autoplay' attribute for the `<audio>` element. Default empty.
+ * @type string $muted The 'muted' attribute for the `<audio>` element. Default 'false'.
* @type string $preload The 'preload' attribute for the `<audio>` element. Default 'none'.
* @type string $class The 'class' attribute for the `<audio>` element. Default 'wp-audio-shortcode'.
* @type string $style The 'style' attribute for the `<audio>` element. Default 'width: 100%;'.
@@ -3269,6 +3398,7 @@
'src' => '',
'loop' => '',
'autoplay' => '',
+ 'muted' => 'false',
'preload' => 'none',
'class' => 'wp-audio-shortcode',
'style' => 'width: 100%;',
@@ -3348,12 +3478,13 @@
'id' => sprintf( 'audio-%d-%d', $post_id, $instance ),
'loop' => wp_validate_boolean( $atts['loop'] ),
'autoplay' => wp_validate_boolean( $atts['autoplay'] ),
+ 'muted' => wp_validate_boolean( $atts['muted'] ),
'preload' => $atts['preload'],
'style' => $atts['style'],
);
// These ones should just be omitted altogether if they are blank.
- foreach ( array( 'loop', 'autoplay', 'preload' ) as $a ) {
+ foreach ( array( 'loop', 'autoplay', 'preload', 'muted' ) as $a ) {
if ( empty( $html_atts[ $a ] ) ) {
unset( $html_atts[ $a ] );
}
@@ -3361,8 +3492,20 @@
$attr_strings = array();
- foreach ( $html_atts as $k => $v ) {
- $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
+ foreach ( $html_atts as $attribute_name => $attribute_value ) {
+ if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) {
+ // Add boolean attributes without a value.
+ $attr_strings[] = esc_attr( $attribute_name );
+ } elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) {
+ // Handle the preload attribute with specific allowed values.
+ $allowed_preload_values = array( 'none', 'metadata', 'auto' );
+ if ( in_array( $attribute_value, $allowed_preload_values, true ) ) {
+ $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
+ }
+ } else {
+ // For other attributes, include the value.
+ $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
+ }
}
$html = '';
@@ -3634,8 +3777,20 @@
}
$attr_strings = array();
- foreach ( $html_atts as $k => $v ) {
- $attr_strings[] = $k . '="' . esc_attr( $v ) . '"';
+ foreach ( $html_atts as $attribute_name => $attribute_value ) {
+ if ( in_array( $attribute_name, array( 'loop', 'autoplay', 'muted' ), true ) && true === $attribute_value ) {
+ // Add boolean attributes without their value for true.
+ $attr_strings[] = esc_attr( $attribute_name );
+ } elseif ( 'preload' === $attribute_name && ! empty( $attribute_value ) ) {
+ // Handle the preload attribute with specific allowed values.
+ $allowed_preload_values = array( 'none', 'metadata', 'auto' );
+ if ( in_array( $attribute_value, $allowed_preload_values, true ) ) {
+ $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
+ }
+ } elseif ( ! empty( $attribute_value ) ) {
+ // For non-boolean attributes, add them with their value.
+ $attr_strings[] = sprintf( '%s="%s"', esc_attr( $attribute_name ), esc_attr( $attribute_value ) );
+ }
}
$html = '';
@@ -4064,8 +4219,7 @@
// Check and set the output mime type mapped to the input type.
if ( isset( $args['mime_type'] ) ) {
- /** This filter is documented in wp-includes/class-wp-image-editor.php */
- $output_format = apply_filters( 'image_editor_output_format', array(), $path, $args['mime_type'] );
+ $output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] );
if ( isset( $output_format[ $args['mime_type'] ] ) ) {
$args['output_mime_type'] = $output_format[ $args['mime_type'] ];
}
@@ -4124,7 +4278,22 @@
* 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'.
*/
$implementations = apply_filters( 'wp_image_editors', array( 'WP_Image_Editor_Imagick', 'WP_Image_Editor_GD' ) );
- $supports_input = false;
+
+ $editors = wp_cache_get( 'wp_image_editor_choose', 'image_editor' );
+
+ if ( ! is_array( $editors ) ) {
+ $editors = array();
+ }
+
+ // Cache the chosen editor implementation based on specific args and available implementations.
+ $cache_key = md5( serialize( array( $args, $implementations ) ) );
+
+ if ( isset( $editors[ $cache_key ] ) ) {
+ return $editors[ $cache_key ];
+ }
+
+ // Assume no support until a capable implementation is identified.
+ $editor = false;
foreach ( $implementations as $implementation ) {
if ( ! call_user_func( array( $implementation, 'test' ), $args ) ) {
@@ -4158,15 +4327,20 @@
* This implementation supports the input type but not the output type.
* Keep looking to see if we can find an implementation that supports both.
*/
- $supports_input = $implementation;
+ $editor = $implementation;
continue;
}
// Favor the implementation that supports both input and output mime types.
- return $implementation;
- }
-
- return $supports_input;
+ $editor = $implementation;
+ break;
+ }
+
+ $editors[ $cache_key ] = $editor;
+
+ wp_cache_set( 'wp_image_editor_choose', $editors, 'image_editor', DAY_IN_SECONDS );
+
+ return $editor;
}
/**
@@ -4224,6 +4398,11 @@
$defaults['avif_upload_error'] = true;
}
+ // Check if HEIC images can be edited.
+ if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
+ $defaults['heic_upload_error'] = true;
+ }
+
/**
* Filters the Plupload default settings.
*
@@ -4315,7 +4494,6 @@
* @type string $url Direct URL to the attachment file (from wp-content).
* @type int $width If the attachment is an image, represents the width of the image in pixels.
* }
- *
*/
function wp_prepare_attachment_for_js( $attachment ) {
$attachment = get_post( $attachment );
@@ -5311,6 +5489,33 @@
function attachment_url_to_postid( $url ) {
global $wpdb;
+ /**
+ * Filters the attachment ID to allow short-circuit the function.
+ *
+ * Allows plugins to short-circuit attachment ID lookups. Plugins making
+ * use of this function should return:
+ *
+ * - 0 (integer) to indicate the attachment is not found,
+ * - attachment ID (integer) to indicate the attachment ID found,
+ * - null to indicate WordPress should proceed with the lookup.
+ *
+ * Warning: The post ID may be null or zero, both of which cast to a
+ * boolean false. For information about casting to booleans see the
+ * {@link https://www.php.net/manual/en/language.types.boolean.php PHP documentation}.
+ * Use the === operator for testing the post ID when developing filters using
+ * this hook.
+ *
+ * @since 6.7.0
+ *
+ * @param int|null $post_id The result of the post ID lookup. Null to indicate
+ * no lookup has been attempted. Default null.
+ * @param string $url The URL being looked up.
+ */
+ $post_id = apply_filters( 'pre_attachment_url_to_postid', null, $url );
+ if ( null !== $post_id ) {
+ return (int) $post_id;
+ }
+
$dir = wp_get_upload_dir();
$path = $url;
@@ -5483,12 +5688,17 @@
* Callback to enable showing of the user error when uploading .heic images.
*
* @since 5.5.0
+ * @since 6.7.0 The default behavior is to enable heic uploads as long as the server
+ * supports the format. The uploads are converted to JPEG's by default.
*
* @param array[] $plupload_settings The settings for Plupload.js.
* @return array[] Modified settings for Plupload.js.
*/
function wp_show_heic_upload_error( $plupload_settings ) {
- $plupload_settings['heic_upload_error'] = true;
+ // Check if HEIC images can be edited.
+ if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
+ $plupload_init['heic_upload_error'] = true;
+ }
return $plupload_settings;
}
@@ -5505,9 +5715,7 @@
*/
function wp_getimagesize( $filename, ?array &$image_info = null ) {
// Don't silence errors when in debug mode, unless running unit tests.
- if ( defined( 'WP_DEBUG' ) && WP_DEBUG
- && ! defined( 'WP_RUN_CORE_TESTS' )
- ) {
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! defined( 'WP_RUN_CORE_TESTS' ) ) {
if ( 2 === func_num_args() ) {
$info = getimagesize( $filename, $image_info );
} else {
@@ -5538,11 +5746,18 @@
return $info;
}
+ $image_mime_type = wp_get_image_mime( $filename );
+
+ // Not an image?
+ if ( false === $image_mime_type ) {
+ return false;
+ }
+
/*
* For PHP versions that don't support WebP images,
* extract the image size info from the file headers.
*/
- if ( 'image/webp' === wp_get_image_mime( $filename ) ) {
+ if ( 'image/webp' === $image_mime_type ) {
$webp_info = wp_get_webp_info( $filename );
$width = $webp_info['width'];
$height = $webp_info['height'];
@@ -5564,7 +5779,7 @@
}
// For PHP versions that don't support AVIF images, extract the image size info from the file headers.
- if ( 'image/avif' === wp_get_image_mime( $filename ) ) {
+ if ( 'image/avif' === $image_mime_type ) {
$avif_info = wp_get_avif_info( $filename );
$width = $avif_info['width'];
@@ -5586,6 +5801,31 @@
}
}
+ // For PHP versions that don't support HEIC images, extract the size info using Imagick when available.
+ if ( wp_is_heic_image_mime_type( $image_mime_type ) ) {
+ $editor = wp_get_image_editor( $filename );
+
+ if ( is_wp_error( $editor ) ) {
+ return false;
+ }
+
+ // If the editor for HEICs is Imagick, use it to get the image size.
+ if ( $editor instanceof WP_Image_Editor_Imagick ) {
+ $size = $editor->get_size();
+ return array(
+ $size['width'],
+ $size['height'],
+ IMAGETYPE_HEIC,
+ sprintf(
+ 'width="%d" height="%d"',
+ $size['width'],
+ $size['height']
+ ),
+ 'mime' => 'image/heic',
+ );
+ }
+ }
+
// The image could not be parsed.
return false;
}
@@ -6069,3 +6309,45 @@
return $high_priority_element;
}
+
+/**
+ * Determines the output format for the image editor.
+ *
+ * @since 6.7.0
+ * @access private
+ *
+ * @param string $filename Path to the image.
+ * @param string $mime_type The source image mime type.
+ * @return string[] An array of mime type mappings.
+ */
+function wp_get_image_editor_output_format( $filename, $mime_type ) {
+ $output_format = array(
+ 'image/heic' => 'image/jpeg',
+ 'image/heif' => 'image/jpeg',
+ 'image/heic-sequence' => 'image/jpeg',
+ 'image/heif-sequence' => 'image/jpeg',
+ );
+
+ /**
+ * Filters the image editor output format mapping.
+ *
+ * Enables filtering the mime type used to save images. By default HEIC/HEIF images
+ * are converted to JPEGs.
+ *
+ * @see WP_Image_Editor::get_output_format()
+ *
+ * @since 5.8.0
+ * @since 6.7.0 The default was changed from an empty array to an array
+ * containing the HEIC/HEIF images mime types.
+ *
+ * @param string[] $output_format {
+ * An array of mime type mappings. Maps a source mime type to a new
+ * destination mime type. By default maps HEIC/HEIF input to JPEG output.
+ *
+ * @type string ...$0 The new mime type.
+ * }
+ * @param string $filename Path to the image.
+ * @param string $mime_type The source image mime type.
+ */
+ return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type );
+}