diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/media.php --- a/wp/wp-includes/media.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/media.php Fri Sep 05 18:40:08 2025 +0200 @@ -7,7 +7,7 @@ */ /** - * Retrieve additional image sizes. + * Retrieves additional image sizes. * * @since 4.7.0 * @@ -26,7 +26,7 @@ } /** - * Scale down the default size of an image. + * Scales down the default size of an image. * * This is so that the image is a better fit for the editor and theme. * @@ -136,7 +136,7 @@ } /** - * Retrieve width and height attributes using given width and height values. + * Retrieves width and height attributes using given width and height values. * * Both attributes are required in the sense that both parameters must have a * value, but are optional in that if you set them to false or null, then they @@ -164,7 +164,7 @@ } /** - * Scale an image to fit a particular size (such as 'thumb' or 'medium'). + * Scales an image to fit a particular size (such as 'thumb' or 'medium'). * * The URL might be the original image, or it might be a resized version. This * function won't create a new resized copy, it will just return an already @@ -217,8 +217,10 @@ $is_intermediate = false; $img_url_basename = wp_basename( $img_url ); - // If the file isn't an image, attempt to replace its URL with a rendered image from its meta. - // Otherwise, a non-image type could be returned. + /* + * If the file isn't an image, attempt to replace its URL with a rendered image from its meta. + * Otherwise, a non-image type could be returned. + */ if ( ! $is_image ) { if ( ! empty( $meta['sizes']['full'] ) ) { $img_url = str_replace( $img_url_basename, $meta['sizes']['full']['file'], $img_url ); @@ -238,20 +240,20 @@ $width = $intermediate['width']; $height = $intermediate['height']; $is_intermediate = true; - } elseif ( 'thumbnail' === $size ) { + } elseif ( 'thumbnail' === $size && ! empty( $meta['thumb'] ) && is_string( $meta['thumb'] ) ) { // Fall back to the old thumbnail. - $thumb_file = wp_get_attachment_thumb_file( $id ); - $info = null; - - if ( $thumb_file ) { - $info = wp_getimagesize( $thumb_file ); - } - - if ( $thumb_file && $info ) { - $img_url = str_replace( $img_url_basename, wp_basename( $thumb_file ), $img_url ); - $width = $info[0]; - $height = $info[1]; - $is_intermediate = true; + $imagefile = get_attached_file( $id ); + $thumbfile = str_replace( wp_basename( $imagefile ), wp_basename( $meta['thumb'] ), $imagefile ); + + if ( file_exists( $thumbfile ) ) { + $info = wp_getimagesize( $thumbfile ); + + if ( $info ) { + $img_url = str_replace( $img_url_basename, wp_basename( $thumbfile ), $img_url ); + $width = $info[0]; + $height = $info[1]; + $is_intermediate = true; + } } } @@ -272,7 +274,7 @@ } /** - * Register a new image size. + * Registers a new image size. * * @since 2.9.0 * @@ -281,12 +283,14 @@ * @param string $name Image size identifier. * @param int $width Optional. Image width in pixels. Default 0. * @param int $height Optional. Image height in pixels. Default 0. - * @param bool|array $crop Optional. Image cropping behavior. If false, the image will be scaled (default), - * 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. - * Array values must be in the format: array( x_crop_position, y_crop_position ) where: - * - x_crop_position accepts: 'left', 'center', or 'right'. - * - y_crop_position accepts: 'top', 'center', or 'bottom'. + * @param bool|array $crop { + * Optional. Image cropping behavior. If false, the image will be scaled (default). + * 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 $1 The y crop position. Accepts 'top', 'center', or 'bottom'. + * } */ function add_image_size( $name, $width = 0, $height = 0, $crop = false ) { global $_wp_additional_image_sizes; @@ -299,7 +303,7 @@ } /** - * Check if an image size exists. + * Checks if an image size exists. * * @since 3.9.0 * @@ -312,7 +316,7 @@ } /** - * Remove a new image size. + * Removes a new image size. * * @since 3.9.0 * @@ -341,8 +345,14 @@ * * @param int $width Image width in pixels. * @param int $height Image height in pixels. - * @param bool|array $crop Optional. Whether to crop images to specified width and height or resize. - * An array can specify positioning of the crop area. Default false. + * @param bool|array $crop { + * Optional. Image cropping behavior. If false, the image will be scaled (default). + * 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 $1 The y crop position. Accepts 'top', 'center', or 'bottom'. + * } */ function set_post_thumbnail_size( $width = 0, $height = 0, $crop = false ) { add_image_size( 'post-thumbnail', $width, $height, $crop ); @@ -368,7 +378,7 @@ * @param string $align Part of the class name for aligning the image. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. - * @return string HTML IMG element for given image attachment + * @return string HTML IMG element for given image attachment. */ function get_image_tag( $id, $alt, $title, $align, $size = 'medium' ) { @@ -509,22 +519,20 @@ * Calculates dimensions and coordinates for a resized image that fits * within a specified width and height. * - * Cropping behavior is dependent on the value of $crop: - * 1. If false (default), images will not be cropped. - * 2. If an array in the form of array( x_crop_position, y_crop_position ): - * - x_crop_position accepts 'left' 'center', or 'right'. - * - y_crop_position accepts 'top', 'center', or 'bottom'. - * Images will be cropped to the specified dimensions within the defined crop area. - * 3. If true, images will be cropped to the specified dimensions using center positions. - * * @since 2.5.0 * * @param int $orig_w Original width in pixels. * @param int $orig_h Original height in pixels. * @param int $dest_w New width in pixels. * @param int $dest_h New height in pixels. - * @param bool|array $crop Optional. Whether to crop image to specified width and height or resize. - * An array can specify positioning of the crop area. Default false. + * @param bool|array $crop { + * Optional. Image cropping behavior. If false, the image will be scaled (default). + * 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 $1 The y crop position. Accepts 'top', 'center', or 'bottom'. + * } * @return array|false Returned array matches parameters for `imagecopyresampled()`. False on failure. */ function image_resize_dimensions( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) { @@ -652,8 +660,10 @@ } } - // The return array matches the parameters to imagecopyresampled(). - // int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h + /* + * The return array matches the parameters to imagecopyresampled(). + * int dst_x, int dst_y, int src_x, int src_y, int dst_w, int dst_h, int src_w, int src_h + */ return array( 0, 0, (int) $s_x, (int) $s_y, (int) $new_w, (int) $new_h, (int) $crop_w, (int) $crop_h ); } @@ -666,11 +676,17 @@ * * @since 2.5.0 * - * @param string $file File path. - * @param int $width Image width. - * @param int $height Image height. - * @param bool $crop Optional. Whether to crop image to specified width and height or resize. - * Default false. + * @param string $file File path. + * @param int $width Image width. + * @param int $height Image height. + * @param bool|array $crop { + * Optional. Image cropping behavior. If false, the image will be scaled (default). + * 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 $1 The y crop position. Accepts 'top', 'center', or 'bottom'. + * } * @return array|false Metadata array on success. False if no image was created. */ function image_make_intermediate_size( $file, $width, $height, $crop = false ) { @@ -749,10 +765,10 @@ * Array of file relative path, width, and height on success. Additionally includes absolute * path and URL if registered size is passed to `$size` parameter. False on failure. * - * @type string $file Path of image relative to uploads directory. + * @type string $file Filename of image. * @type int $width Width of image in pixels. * @type int $height Height of image in pixels. - * @type string $path Absolute filesystem path of image. + * @type string $path Path of image relative to uploads directory. * @type string $url URL of image. * } */ @@ -956,14 +972,25 @@ $src = false; if ( $icon ) { - $src = wp_mime_type_icon( $attachment_id ); + $src = wp_mime_type_icon( $attachment_id, '.svg' ); if ( $src ) { /** This filter is documented in wp-includes/post.php */ $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' ); - $src_file = $icon_dir . '/' . wp_basename( $src ); + $src_file = $icon_dir . '/' . wp_basename( $src ); + list( $width, $height ) = wp_getimagesize( $src_file ); + + $ext = strtolower( substr( $src_file, -4 ) ); + + if ( '.svg' === $ext ) { + // SVG does not have true dimensions, so this assigns width and height directly. + $width = 48; + $height = 64; + } else { + list( $width, $height ) = wp_getimagesize( $src_file ); + } } } @@ -993,7 +1020,7 @@ } /** - * Get an HTML img element representing an image attachment. + * Gets an HTML img element representing an image attachment. * * While `$size` will accept an array, it is better to register a size with * add_image_size() so that a cropped version is generated. It's much more @@ -1003,6 +1030,7 @@ * @since 2.5.0 * @since 4.4.0 The `$srcset` and `$sizes` attributes were added. * @since 5.5.0 The `$loading` attribute was added. + * @since 6.1.0 The `$decoding` attribute was added. * * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array @@ -1011,16 +1039,21 @@ * @param string|array $attr { * Optional. Attributes for the image markup. * - * @type string $src Image attachment URL. - * @type string $class CSS class name or space-separated list of classes. - * Default `attachment-$size_class size-$size_class`, - * where `$size_class` is the image size being requested. - * @type string $alt Image description for the alt attribute. - * @type string $srcset The 'srcset' attribute value. - * @type string $sizes The 'sizes' attribute value. - * @type string|false $loading The 'loading' attribute value. Passing a value of false - * will result in the attribute being omitted for the image. - * Defaults to 'lazy', depending on wp_lazy_loading_enabled(). + * @type string $src Image attachment URL. + * @type string $class CSS class name or space-separated list of classes. + * Default `attachment-$size_class size-$size_class`, + * where `$size_class` is the image size being requested. + * @type string $alt Image description for the alt attribute. + * @type string $srcset The 'srcset' attribute value. + * @type string $sizes The 'sizes' attribute value. + * @type string|false $loading The 'loading' attribute value. Passing a value of false + * will result in the attribute being omitted for the image. + * Default determined by {@see wp_get_loading_optimization_attributes()}. + * @type string $decoding The 'decoding' attribute value. Possible values are + * 'async' (default), 'sync', or 'auto'. Passing false or an empty + * string will result in the attribute being omitted. + * @type string $fetchpriority The 'fetchpriority' attribute value, whether `high`, `low`, or `auto`. + * Default determined by {@see wp_get_loading_optimization_attributes()}. * } * @return string HTML img element or empty string on failure. */ @@ -1045,19 +1078,46 @@ 'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), ); - // Add `loading` attribute. - if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) { - $default_attr['loading'] = wp_get_loading_attr_default( 'wp_get_attachment_image' ); + /** + * Filters the context in which wp_get_attachment_image() is used. + * + * @since 6.3.0 + * + * @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; + $loading_optimization_attr = wp_get_loading_optimization_attributes( + 'img', + $loading_attr, + $context + ); + + // Add loading optimization attributes if not available. + $attr = array_merge( $attr, $loading_optimization_attr ); + + // Omit the `decoding` attribute if the value is invalid according to the spec. + if ( empty( $attr['decoding'] ) || ! in_array( $attr['decoding'], array( 'async', 'sync', 'auto' ), true ) ) { + unset( $attr['decoding'] ); } - $attr = wp_parse_args( $attr, $default_attr ); - - // If the default value of `lazy` for the `loading` attribute is overridden - // to omit the attribute for this image, ensure it is not included. - if ( array_key_exists( 'loading', $attr ) && ! $attr['loading'] ) { + /* + * If the default value of `lazy` for the `loading` attribute is overridden + * to omit the attribute for this image, ensure it is not included. + */ + if ( isset( $attr['loading'] ) && ! $attr['loading'] ) { unset( $attr['loading'] ); } + // If the `fetchpriority` attribute is overridden and set to false or an empty string. + if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) { + unset( $attr['fetchpriority'] ); + } + // Generate 'srcset' and 'sizes' if not already present. if ( empty( $attr['srcset'] ) ) { $image_meta = wp_get_attachment_metadata( $attachment_id ); @@ -1101,7 +1161,7 @@ } /** - * HTML img element representing an image attachment. + * Filters the HTML img element representing an image attachment. * * @since 5.6.0 * @@ -1117,7 +1177,7 @@ } /** - * Get the URL of an image attachment. + * Gets the URL of an image attachment. * * @since 4.4.0 * @@ -1134,7 +1194,7 @@ } /** - * Get the attachment path relative to the upload directory. + * Gets the attachment path relative to the upload directory. * * @since 4.4.1 * @access private @@ -1149,7 +1209,7 @@ return ''; } - if ( false !== strpos( $dirname, 'wp-content/uploads' ) ) { + if ( str_contains( $dirname, 'wp-content/uploads' ) ) { // Get the directory name relative to the upload directory (back compat for pre-2.7 uploads). $dirname = substr( $dirname, strpos( $dirname, 'wp-content/uploads' ) + 18 ); $dirname = ltrim( $dirname, '/' ); @@ -1159,7 +1219,7 @@ } /** - * Get the image size as array from its meta data. + * Gets the image size as array from its meta data. * * Used for responsive images. * @@ -1201,7 +1261,7 @@ * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. - * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. + * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @return string|false A 'srcset' value string or false. */ @@ -1243,7 +1303,7 @@ */ function wp_calculate_image_srcset( $size_array, $image_src, $image_meta, $attachment_id = 0 ) { /** - * Let plugins pre-filter the image meta to be able to fix inconsistencies in the stored data. + * Pre-filters the image meta to be able to fix inconsistencies in the stored data. * * @since 4.5.0 * @@ -1287,7 +1347,7 @@ 'height' => $image_meta['height'], 'file' => $image_basename, ); - } elseif ( strpos( $image_src, $image_meta['file'] ) ) { + } elseif ( str_contains( $image_src, $image_meta['file'] ) ) { return false; } @@ -1305,8 +1365,21 @@ * If currently on HTTPS, prefer HTTPS URLs when we know they're supported by the domain * (which is to say, when they share the domain name of the current request). */ - if ( is_ssl() && 'https' !== substr( $image_baseurl, 0, 5 ) && parse_url( $image_baseurl, PHP_URL_HOST ) === $_SERVER['HTTP_HOST'] ) { - $image_baseurl = set_url_scheme( $image_baseurl, 'https' ); + if ( is_ssl() && ! str_starts_with( $image_baseurl, 'https' ) ) { + /* + * Since the `Host:` header might contain a port, it should + * be compared against the image URL using the same port. + */ + $parsed = parse_url( $image_baseurl ); + $domain = isset( $parsed['host'] ) ? $parsed['host'] : ''; + + if ( isset( $parsed['port'] ) ) { + $domain .= ':' . $parsed['port']; + } + + if ( $_SERVER['HTTP_HOST'] === $domain ) { + $image_baseurl = set_url_scheme( $image_baseurl, 'https' ); + } } /* @@ -1354,7 +1427,7 @@ } // If the file name is part of the `src`, we've confirmed a match. - if ( ! $src_matched && false !== strpos( $image_src, $dirname . $image['file'] ) ) { + if ( ! $src_matched && str_contains( $image_src, $dirname . $image['file'] ) ) { $src_matched = true; $is_src = true; } @@ -1442,7 +1515,7 @@ * @param int $attachment_id Image attachment ID. * @param string|int[] $size Optional. Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). Default 'medium'. - * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. + * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @return string|false A valid source size value for use in a 'sizes' attribute or false. */ @@ -1473,8 +1546,8 @@ * * @param string|int[] $size Image size. Accepts any registered image size name, or an array of * width and height values in pixels (in that order). - * @param string $image_src Optional. The URL to the image file. Default null. - * @param array $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. + * @param string|null $image_src Optional. The URL to the image file. Default null. + * @param array|null $image_meta Optional. The image meta data as returned by 'wp_get_attachment_metadata()'. * Default null. * @param int $attachment_id Optional. Image attachment ID. Either `$image_meta` or `$attachment_id` * is needed when using the image size name as argument for `$size`. Default 0. @@ -1540,7 +1613,7 @@ // Ensure the $image_meta is valid. if ( isset( $image_meta['file'] ) && strlen( $image_meta['file'] ) > 4 ) { - // Remove quiery args if image URI. + // Remove query args in image URI. list( $image_location ) = explode( '?', $image_location ); // Check if the relative image path from the image meta is at the end of $image_location. @@ -1606,7 +1679,7 @@ // Is it a full size image? if ( isset( $image_meta['file'] ) && - strpos( $image_src, wp_basename( $image_meta['file'] ) ) !== false + str_contains( $image_src, wp_basename( $image_meta['file'] ) ) ) { $dimensions = array( (int) $image_meta['width'], @@ -1673,9 +1746,9 @@ } // Bail early if an image has been inserted and later edited. - if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) && - strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) { - + if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) + && ! str_contains( wp_basename( $image_src ), $img_edit_hash[0] ) + ) { return $image; } @@ -1729,9 +1802,11 @@ * @return bool Whether to add the attribute. */ function wp_lazy_loading_enabled( $tag_name, $context ) { - // By default add to all 'img' and 'iframe' tags. - // See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading - // See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading + /* + * By default add to all 'img' and 'iframe' tags. + * See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading + * See https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-loading + */ $default = ( 'img' === $tag_name || 'iframe' === $tag_name ); /** @@ -1761,7 +1836,7 @@ * * @see wp_img_tag_add_width_and_height_attr() * @see wp_img_tag_add_srcset_and_sizes_attr() - * @see wp_img_tag_add_loading_attr() + * @see wp_img_tag_add_loading_optimization_attrs() * @see wp_iframe_tag_add_loading_attr() * * @param string $content The HTML content to be filtered. @@ -1774,7 +1849,6 @@ $context = current_filter(); } - $add_img_loading_attr = wp_lazy_loading_enabled( 'img', $context ); $add_iframe_loading_attr = wp_lazy_loading_enabled( 'iframe', $context ); if ( ! preg_match_all( '/<(img|iframe)\s[^>]+>/', $content, $matches, PREG_SET_ORDER ) ) { @@ -1796,8 +1870,10 @@ $attachment_id = absint( $class_id[1] ); if ( $attachment_id ) { - // If exactly the same image tag is used more than once, overwrite it. - // All identical tags will be replaced later with 'str_replace()'. + /* + * If exactly the same image tag is used more than once, overwrite it. + * All identical tags will be replaced later with 'str_replace()'. + */ $images[ $tag ] = $attachment_id; break; } @@ -1829,19 +1905,17 @@ $attachment_id = $images[ $match[0] ]; // Add 'width' and 'height' attributes if applicable. - if ( $attachment_id > 0 && false === strpos( $filtered_image, ' width=' ) && false === strpos( $filtered_image, ' height=' ) ) { + if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' width=' ) && ! str_contains( $filtered_image, ' height=' ) ) { $filtered_image = wp_img_tag_add_width_and_height_attr( $filtered_image, $context, $attachment_id ); } // Add 'srcset' and 'sizes' attributes if applicable. - if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) { + if ( $attachment_id > 0 && ! str_contains( $filtered_image, ' srcset=' ) ) { $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id ); } - // Add 'loading' attribute if applicable. - if ( $add_img_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) { - $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context ); - } + // Add loading optimization attributes if applicable. + $filtered_image = wp_img_tag_add_loading_optimization_attrs( $filtered_image, $context ); /** * Filters an img tag within the content for a given context. @@ -1870,7 +1944,7 @@ $filtered_iframe = $match[0]; // Add 'loading' attribute if applicable. - if ( $add_iframe_loading_attr && false === strpos( $filtered_iframe, ' loading=' ) ) { + if ( $add_iframe_loading_attr && ! str_contains( $filtered_iframe, ' loading=' ) ) { $filtered_iframe = wp_iframe_tag_add_loading_attr( $filtered_iframe, $context ); } @@ -1890,45 +1964,142 @@ } /** - * Adds `loading` attribute to an `img` HTML tag. - * - * @since 5.5.0 + * Adds optimization attributes to an `img` HTML tag. + * + * @since 6.3.0 * * @param string $image The HTML `img` tag where the attribute should be added. * @param string $context Additional context to pass to the filters. - * @return string Converted `img` tag with `loading` attribute added. + * @return string Converted `img` tag with optimization attributes added. */ -function wp_img_tag_add_loading_attr( $image, $context ) { - // Get loading attribute value to use. This must occur before the conditional check below so that even images that - // are ineligible for being lazy-loaded are considered. - $value = wp_get_loading_attr_default( $context ); - - // Images should have source and dimension attributes for the `loading` attribute to be added. - if ( false === strpos( $image, ' src="' ) || false === strpos( $image, ' width="' ) || false === strpos( $image, ' height="' ) ) { +function wp_img_tag_add_loading_optimization_attrs( $image, $context ) { + $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; + $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null; + $decoding_val = preg_match( '/ decoding=["\']([A-Za-z]+)["\']/', $image, $match_decoding ) ? $match_decoding[1] : null; + + /* + * Get loading optimization attributes to use. + * This must occur before the conditional check below so that even images + * that are ineligible for being lazy-loaded are considered. + */ + $optimization_attrs = wp_get_loading_optimization_attributes( + 'img', + array( + 'width' => $width, + 'height' => $height, + 'loading' => $loading_val, + 'fetchpriority' => $fetchpriority_val, + 'decoding' => $decoding_val, + ), + $context + ); + + // Images should have source for the loading optimization attributes to be added. + if ( ! str_contains( $image, ' src="' ) ) { return $image; } - /** - * Filters the `loading` attribute value to add to an image. Default `lazy`. - * - * Returning `false` or an empty string will not add the attribute. - * Returning `true` will add the default value. - * - * @since 5.5.0 - * - * @param string|bool $value The `loading` attribute value. Returning a falsey value will result in - * the attribute being omitted for the image. - * @param string $image The HTML `img` tag to be filtered. - * @param string $context Additional context about how the function was called or where the img tag is. - */ - $value = apply_filters( 'wp_img_tag_add_loading_attr', $value, $image, $context ); - - if ( $value ) { - if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { - $value = 'lazy'; + if ( empty( $decoding_val ) ) { + /** + * Filters the `decoding` attribute value to add to an image. Default `async`. + * + * Returning a falsey value will omit the attribute. + * + * @since 6.1.0 + * + * @param string|false|null $value The `decoding` attribute value. Returning a falsey value + * will result in the attribute being omitted for the image. + * Otherwise, it may be: 'async', 'sync', or 'auto'. Defaults to false. + * @param string $image The HTML `img` tag to be filtered. + * @param string $context Additional context about how the function was called + * or where the img tag is. + */ + $filtered_decoding_attr = apply_filters( + 'wp_img_tag_add_decoding_attr', + isset( $optimization_attrs['decoding'] ) ? $optimization_attrs['decoding'] : false, + $image, + $context + ); + + // Validate the values after filtering. + if ( isset( $optimization_attrs['decoding'] ) && ! $filtered_decoding_attr ) { + // Unset `decoding` attribute if `$filtered_decoding_attr` is set to `false`. + unset( $optimization_attrs['decoding'] ); + } elseif ( in_array( $filtered_decoding_attr, array( 'async', 'sync', 'auto' ), true ) ) { + $optimization_attrs['decoding'] = $filtered_decoding_attr; + } + + if ( ! empty( $optimization_attrs['decoding'] ) ) { + $image = str_replace( '