<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[53751] trunk: Media: enable generating multiple mime types for image uploads; specifically WebP versions for JPEG images by default.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/53751">53751</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/53751","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>adamsilverstein</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-07-21 18:01:01 +0000 (Thu, 21 Jul 2022)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Media: enable generating multiple mime types for image uploads; specifically WebP versions for JPEG images by default.

This changeset adds the capability for core media uploads to generate sub sized images in more than a single mime type. The output formats for each mime type can be controlled through a filter. WebP is used as an additional output format for JPEG images by default to improve front end performance.

When generating additional mime types, only images which are smaller than the respective original are retained. By default, additional mime type images are only generated for the built-in core image sizes and any custom sizes that have opted in.

Image meta is updated with a new 'sources' array containing file details for each mime type. Each image size in the 'sizes' array also gets a new 'sources' array that contains the image file details for each mime type.

This change also increases image upload retries to accommodate additional image sizes. It also adds a `$mime_type` parameter to the `wp_get_missing_image_subsizes` function and filter.

This change adds three new filters to enable full control of secondary mime image generation and output:

* A new filter `wp_image_sizes_with_additional_mime_type_support` that filters the sizes that support secondary mime type output. Developers can use this to control the output of additional mime type sub-sized images on a per size basis.
* A new filter `wp_upload_image_mime_transforms` that filters the output mime types for a given input mime type. Developers can use this to control generation of additional mime types for a given input mime type or even override the original mime type.
* A new filter `wp_content_image_mimes` which controls image mime type output selection and order for frontend content. Developers can use this to control the mime type output preference order for content images. Content images inserted from the media library will use the available image versions based on the order from this filter.

Thanks to the many contributors who helped develop, test and give feedback on this feature.

A haiku to summarize:

Upload a JPEG
Images of all sizes
Output as WebPs

Props flixos90, MatthiasReinholz, studiolxv, markhowellsmead, eatingrules, pbiron, mukesh27, joegrainger, mehulkaklotar, tweetythierry, akshitsethi, peterwilsoncc, eugenemanuilov, mitogh, shetheliving, clarkeemily, codekraft, mikeschroder, clorith, kasparsd, spacedmonkey, trevorpfromsandee, jb510, scofennellgmailcom, seedsca, cagsmith, karinclimber, dainemawer, baxbridge, grapplerulrich, sobatkras, chynnabenton, tonylocalword, barneydavey, kwillmorth, garymatthews919, olliejones, imarkinteractive, jeffpaul, feastdesignco, webbeetle, masteradhoc.

See <a href="https://core.trac.wordpress.org/ticket/55443">#55443</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcjs_enqueuesvendorpluploadhandlersjs">trunk/src/js/_enqueues/vendor/plupload/handlers.js</a></li>
<li><a href="#trunksrcjs_enqueuesvendorpluploadwppluploadjs">trunk/src/js/_enqueues/vendor/plupload/wp-plupload.js</a></li>
<li><a href="#trunksrcwpadminincludesimagephp">trunk/src/wp-admin/includes/image.php</a></li>
<li><a href="#trunksrcwpincludesclasswpimageeditorphp">trunk/src/wp-includes/class-wp-image-editor.php</a></li>
<li><a href="#trunksrcwpincludesmediaphp">trunk/src/wp-includes/media.php</a></li>
<li><a href="#trunksrcwpincludespostphp">trunk/src/wp-includes/post.php</a></li>
<li><a href="#trunktestsphpunittestsimageeditorphp">trunk/tests/phpunit/tests/image/editor.php</a></li>
<li><a href="#trunktestsphpunittestsimagefunctionsphp">trunk/tests/phpunit/tests/image/functions.php</a></li>
<li><a href="#trunktestsphpunittestsmediaphp">trunk/tests/phpunit/tests/media.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunitdataimagestestimagerotated90ccwjpg">trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg</a></li>
<li><a href="#trunktestsphpunitdataimagestestimagerotated90cwwebp">trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp</a></li>
<li><a href="#trunktestsphpunitdataimagestestimagejpeg">trunk/tests/phpunit/data/images/test-image.jpeg</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcjs_enqueuesvendorpluploadhandlersjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/_enqueues/vendor/plupload/handlers.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/_enqueues/vendor/plupload/handlers.js        2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/js/_enqueues/vendor/plupload/handlers.js  2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -486,7 +486,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                times = tryAgainCount[ file.id ];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( times && times > 4 ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( times && times > 8 ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         /*
</span><span class="cx" style="display: block; padding: 0 10px">                         * The file may have been uploaded and attachment post created,
</span><span class="cx" style="display: block; padding: 0 10px">                         * but post-processing and resizing failed...
</span></span></pre></div>
<a id="trunksrcjs_enqueuesvendorpluploadwppluploadjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/_enqueues/vendor/plupload/wp-plupload.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/_enqueues/vendor/plupload/wp-plupload.js     2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/js/_enqueues/vendor/plupload/wp-plupload.js       2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -138,7 +138,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        times = tryAgainCount[ file.id ];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( times && times > 4 ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( times && times > 8 ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 /*
</span><span class="cx" style="display: block; padding: 0 10px">                                 * The file may have been uploaded and attachment post created,
</span><span class="cx" style="display: block; padding: 0 10px">                                 * but post-processing and resizing failed...
</span></span></pre></div>
<a id="trunksrcwpadminincludesimagephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/image.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/image.php     2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/wp-admin/includes/image.php       2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -77,16 +77,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Registered sub-sizes that are larger than the image are skipped.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.1.0 The $mime_type parameter was added.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param int $attachment_id The image attachment post ID.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param int    $attachment_id The image attachment post ID.
+ * @param string $mime_type     Optional. The mime type to check for missing sizes. Default is the primary image mime.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return array[] Associative array of arrays of image sub-size information for
</span><span class="cx" style="display: block; padding: 0 10px">  *                 missing image sizes, keyed by image size name.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function wp_get_missing_image_subsizes( $attachment_id ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function wp_get_missing_image_subsizes( $attachment_id, $mime_type = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( ! wp_attachment_is_image( $attachment_id ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return array();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $primary_mime_type = get_post_mime_type( get_post( $attachment_id ) );
+       if ( ! $mime_type ) {
+               $mime_type = $primary_mime_type;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $registered_sizes = wp_get_registered_image_subsizes();
</span><span class="cx" style="display: block; padding: 0 10px">        $image_meta       = wp_get_attachment_metadata( $attachment_id );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -129,19 +136,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * However we keep the old sub-sizes with the previous dimensions
</span><span class="cx" style="display: block; padding: 0 10px">         * as the image may have been used in an older post.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $missing_sizes = array_diff_key( $possible_sizes, $image_meta['sizes'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $missing_sizes = array();
+       foreach ( $possible_sizes as $size_name => $size_data ) {
+               if ( ! isset( $image_meta['sizes'][ $size_name ] ) ) {
+                       $missing_sizes[ $size_name ] = $size_data;
+                       continue;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ( isset( $size_data['mime-type'] ) && $size_data['mime-type'] === $mime_type ) || isset( $size_data['sources'][ $mime_type ] ) ) {
+                       continue;
+               }
+
+               $missing_sizes[ $size_name ] = $size_data;
+       }
+
+       // Filter secondary mime types to those sizes that are enabled.
+       if ( $primary_mime_type !== $mime_type ) {
+               $missing_sizes = _wp_filter_image_sizes_additional_mime_type_support( $missing_sizes, $attachment_id );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Filters the array of missing image sub-sizes for an uploaded image.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.1.0 The $mime_type filter parameter was added.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array[] $missing_sizes Associative array of arrays of image sub-size information for
</span><span class="cx" style="display: block; padding: 0 10px">         *                               missing image sizes, keyed by image size name.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param array   $image_meta    The image meta data.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int     $attachment_id The image attachment post ID.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @param string  $mime_type     The image mime type to get missing sizes for.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id, $mime_type );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -149,6 +175,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * create them and update the image meta data.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.1.0 Now supports additional mime types, creating the additional sub-sizes and 'full' sized images.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param int $attachment_id The image attachment post ID.
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array|WP_Error The updated image meta data array or WP_Error object
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -167,14 +194,33 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Get the primary and additional mime types to generate.
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $image_file, $attachment_id );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( empty( $missing_sizes ) ) {
-                       return $image_meta;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Generate missing 'full' image files for additional mime types.
+               if ( ! empty( $additional_mime_types ) ) {
+                       if ( isset( $image_meta['sources'] ) ) {
+                               $missing_mime_types = array_diff( $additional_mime_types, array_keys( $image_meta['sources'] ) );
+                       } else {
+                               $missing_mime_types = $additional_mime_types;
+                       }
+                       if ( ! empty( $missing_mime_types ) ) {
+                               $image_meta = _wp_make_additional_mime_types( $missing_mime_types, $image_file, $image_meta, $attachment_id );
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // This also updates the image meta.
-               $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Generate missing image sub-sizes for each mime type.
+               $all_mime_types = array_merge( array( $primary_mime_type ), $additional_mime_types );
+               foreach ( $all_mime_types as $mime_type ) {
+                       $missing_sizes = wp_get_missing_image_subsizes( $attachment_id, $mime_type );
+
+                       if ( empty( $missing_sizes ) ) {
+                               continue;
+                       }
+
+                       // This also updates the image meta.
+                       $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id, $mime_type );
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /** This filter is documented in wp-admin/includes/image.php */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -222,12 +268,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Creates image mime variations and sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * Intended for use after an image is uploaded. Saves/updates the image metadata after each
</span><span class="cx" style="display: block; padding: 0 10px">  * sub-size is created. If there was an error, it is added to the returned image metadata array.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.1.0 Generates sub-sizes in alternate mime types based on the `wp_image_mime_transforms` filter.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $file          Full path to the image file.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param int    $attachment_id Attachment ID to process.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -248,6 +295,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                'file'     => _wp_relative_upload_path( $file ),
</span><span class="cx" style="display: block; padding: 0 10px">                'filesize' => wp_filesize( $file ),
</span><span class="cx" style="display: block; padding: 0 10px">                'sizes'    => array(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                'sources'  => array(),
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Fetch additional metadata from EXIF/IPTC.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -257,9 +305,112 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $image_meta['image_meta'] = $exif_meta;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Get the primary and additional mime types to generate.
+       list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
+
+       list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $primary_mime_type );
+       if ( is_wp_error( $editor ) ) {
+               return $image_meta;
+       }
+       $suffix = _wp_get_image_suffix( $resized, $rotated );
+
+       // Save image only if either it was modified or if the primary mime type is different from the original.
+       if ( ! empty( $suffix ) || $primary_mime_type !== $imagesize['mime'] ) {
+               $saved = $editor->save( $editor->generate_filename( $suffix ) );
+
+               if ( ! is_wp_error( $saved ) ) {
+                       $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
+
+                       // If the image was rotated update the stored EXIF data.
+                       if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
+                               $image_meta['image_meta']['orientation'] = 1;
+                       }
+               } else {
+                       // TODO: Log errors.
+               }
+       }
+
+       // Set 'sources' for the primary mime type.
+       $image_meta['sources'][ $primary_mime_type ] = _wp_get_sources_from_meta( $image_meta );
+
+       /*
+        * Initial save of the new metadata.
+        * At this point the file was uploaded and moved to the uploads directory
+        * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
+        */
+       wp_update_attachment_metadata( $attachment_id, $image_meta );
+
+       if ( ! empty( $additional_mime_types ) ) {
+               // Use the original file's exif_meta orientation information for secondary mime generation.
+               $saved_orientation                       = $image_meta['image_meta']['orientation'];
+               $image_meta['image_meta']['orientation'] = $exif_meta['orientation'];
+               $image_meta                              = _wp_make_additional_mime_types( $additional_mime_types, $file, $image_meta, $attachment_id );
+               $image_meta['image_meta']['orientation'] = $saved_orientation;
+
+       }
+
+       $new_sizes = wp_get_registered_image_subsizes();
+
+       /**
+        * Filters the image sizes automatically generated when uploading an image.
+        *
+        * @since 2.9.0
+        * @since 4.4.0 Added the `$image_meta` argument.
+        * @since 5.3.0 Added the `$attachment_id` argument.
+        *
+        * @param array $new_sizes     Associative array of image sizes to be created.
+        * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
+        * @param int   $attachment_id The attachment post ID for the image.
+        */
+       $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
+
+       $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $primary_mime_type );
+
+       // Filter secondary mime types to those sizes that are enabled.
+       $new_sizes = _wp_filter_image_sizes_additional_mime_type_support( $new_sizes, $attachment_id );
+
+       foreach ( $additional_mime_types as $additional_mime_type ) {
+               $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $additional_mime_type );
+       }
+
+       return $image_meta;
+}
+
+/**
+ * Returns a WP_Image_Editor instance where the image file has been scaled and rotated as necessary.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string     $file          Full path to the image file.
+ * @param int        $attachment_id Attachment ID.
+ * @param array      $imagesize     {
+ *     Indexed array of the image width and height in pixels.
+ *
+ *     @type int $0 The image width.
+ *     @type int $1 The image height.
+ * }
+ * @param array|null $exif_meta EXIF metadata if extracted from the image file.
+ * @param string     $mime_type Output mime type.
+ * @return array Array with three entries: The WP_Image_Editor instance, whether the image was resized, and whether the
+ *               image was rotated (booleans). Each entry can alternatively be a WP_Error in case something went wrong.
+ */
+function _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type ) {
+       $resized = false;
+       $rotated = false;
+
+       $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
+       if ( is_wp_error( $editor ) ) {
+               // This image cannot be edited.
+               return array( $editor, $resized, $rotated );
+       }
+
+       if ( ! empty( $mime_type ) ) {
+               $editor->set_output_mime_type( $mime_type );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( 'image/png' !== $imagesize['mime'] ) {
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( 'image/png' !== $mime_type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 /**
</span><span class="cx" style="display: block; padding: 0 10px">                 * Filters the "BIG image" threshold value.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -285,99 +436,68 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // If the original image's dimensions are over the threshold,
</span><span class="cx" style="display: block; padding: 0 10px">                // scale the image and use it as the "full" size.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) {
-                       $editor = wp_get_image_editor( $file );
-
-                       if ( is_wp_error( $editor ) ) {
-                               // This image cannot be edited.
-                               return $image_meta;
-                       }
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( $threshold && ( $imagesize[0] > $threshold || $imagesize[1] > $threshold ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         // Resize the image.
</span><span class="cx" style="display: block; padding: 0 10px">                        $resized = $editor->resize( $threshold, $threshold );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $rotated = null;
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        // If there is EXIF data, rotate according to EXIF Orientation.
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $resized = $editor->maybe_exif_rotate();
-                               $rotated = $resized;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $rotated = $editor->maybe_exif_rotate();
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-                       if ( ! is_wp_error( $resized ) ) {
-                               // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
-                               // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
-                               $saved = $editor->save( $editor->generate_filename( 'scaled' ) );
-
-                               if ( ! is_wp_error( $saved ) ) {
-                                       $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
-
-                                       // If the image was rotated update the stored EXIF data.
-                                       if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
-                                               $image_meta['image_meta']['orientation'] = 1;
-                                       }
-                               } else {
-                                       // TODO: Log errors.
-                               }
-                       } else {
-                               // TODO: Log errors.
-                       }
</del><span class="cx" style="display: block; padding: 0 10px">                 } elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-                       $editor = wp_get_image_editor( $file );
-
-                       if ( is_wp_error( $editor ) ) {
-                               // This image cannot be edited.
-                               return $image_meta;
-                       }
-
-                       // Rotate the image.
</del><span class="cx" style="display: block; padding: 0 10px">                         $rotated = $editor->maybe_exif_rotate();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
-                       if ( true === $rotated ) {
-                               // Append `-rotated` to the image file name.
-                               $saved = $editor->save( $editor->generate_filename( 'rotated' ) );
-
-                               if ( ! is_wp_error( $saved ) ) {
-                                       $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
-
-                                       // Update the stored EXIF data.
-                                       if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
-                                               $image_meta['image_meta']['orientation'] = 1;
-                                       }
-                               } else {
-                                       // TODO: Log errors.
-                               }
-                       }
</del><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /*
-        * Initial save of the new metadata.
-        * At this point the file was uploaded and moved to the uploads directory
-        * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
-        */
-       wp_update_attachment_metadata( $attachment_id, $image_meta );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return array( $editor, $resized, $rotated );
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $new_sizes = wp_get_registered_image_subsizes();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Gets the suffix to use for image files based on resizing and rotating.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param bool|WP_Error Whether the image was resized, or an error if resizing failed.
+ * @param bool|WP_Error Whether the image was rotated, or an error if rotating failed.
+ * @return string The suffix to use for the file name, or empty string if none.
+ */
+function _wp_get_image_suffix( $resized, $rotated ) {
+       if ( $resized && ! is_wp_error( $resized ) ) {
+               // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
+               // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
+               return 'scaled';
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Filters the image sizes automatically generated when uploading an image.
-        *
-        * @since 2.9.0
-        * @since 4.4.0 Added the `$image_meta` argument.
-        * @since 5.3.0 Added the `$attachment_id` argument.
-        *
-        * @param array $new_sizes     Associative array of image sizes to be created.
-        * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
-        * @param int   $attachment_id The attachment post ID for the image.
-        */
-       $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( true === $rotated ) {
+               // Append `-rotated` to the image file name.
+               return 'rotated';
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
+               // TODO: Log errors.
+       }
+       return '';
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Gets a sources array element from a meta.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param array $meta The meta to get the source from.
+ * @return array The source array element.
+ */
+function _wp_get_sources_from_meta( $meta ) {
+       return array(
+               'file'     => isset( $meta['file'] ) ? wp_basename( $meta['file'] ) : '',
+               'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
+       );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Low-level function to create image sub-sizes.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * Updates the image meta after each sub-size is created.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -384,20 +504,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Errors are stored in the returned image metadata array.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.3.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.1.0 The $mime_type parameter was added.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param array  $new_sizes     Array defining what sizes to create.
- * @param string $file          Full path to the image file.
- * @param array  $image_meta    The attachment meta data array.
- * @param int    $attachment_id Attachment ID to process.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param array  $new_sizes       Array defining what sizes to create.
+ * @param string $file            Full path to the image file.
+ * @param array  $image_meta      The attachment meta data array.
+ * @param int    $attachment_id   Attachment ID to process.
+ * @param string $mime_type       Optional. The mime type to check for missing sizes. Default is the image mime of $file.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $mime_type = '' ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // Not an image attachment.
</span><span class="cx" style="display: block; padding: 0 10px">                return array();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        if ( ! $mime_type ) {
+               $mime_type = wp_get_image_mime( $file );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // Check if any of the new sizes already exist.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -407,7 +533,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                         * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
</span><span class="cx" style="display: block; padding: 0 10px">                         */
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( array_key_exists( $size_name, $new_sizes ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                unset( $new_sizes[ $size_name ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         // Unset the size if it is either the required mime type already exists either as main mime type or
+                               // within sources.
+                               if ( $size_meta['mime-type'] === $mime_type || isset( $size_meta['sources'][ $mime_type ] ) ) {
+                                       unset( $new_sizes[ $size_name ] );
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        } else {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -433,7 +563,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $editor = wp_get_image_editor( $file );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( is_wp_error( $editor ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // The image cannot be edited.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -440,6 +570,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $image_meta;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $editor->set_output_mime_type( $mime_type );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // If stored EXIF data exists, rotate the source image before creating sub-sizes.
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! empty( $image_meta['image_meta'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $rotated = $editor->maybe_exif_rotate();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -457,7 +589,22 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                // TODO: Log errors.
</span><span class="cx" style="display: block; padding: 0 10px">                        } else {
</span><span class="cx" style="display: block; padding: 0 10px">                                // Save the size meta value.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! isset( $image_meta['sizes'][ $new_size_name ] ) ) {
+                                       $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
+                               } else {
+                                       // Remove any newly generated images that are larger than the primary mime type.
+                                       $new_size     = isset( $new_size_meta['filesize'] ) ? $new_size_meta['filesize'] : 0;
+                                       $primary_size = isset( $image_meta['sizes'][ $new_size_name ]['filesize'] ) ? $image_meta['sizes'][ $new_size_name ]['filesize'] : 0;
+
+                                       if ( $new_size && $primary_size && $new_size >= $primary_size ) {
+                                               wp_delete_file( dirname( $file ) . '/' . $new_size_meta['file'] );
+                                               continue;
+                                       }
+                               }
+                               if ( ! isset( $image_meta['sizes'][ $new_size_name ]['sources'] ) ) {
+                                       $image_meta['sizes'][ $new_size_name ]['sources'] = array();
+                               }
+                               $image_meta['sizes'][ $new_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 wp_update_attachment_metadata( $attachment_id, $image_meta );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -466,7 +613,26 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $created_sizes = $editor->multi_resize( $new_sizes );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! empty( $created_sizes ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 foreach ( $created_sizes as $created_size_name => $created_size_meta ) {
+
+                               // Primary mime type is set in 'sizes' array.
+                               if ( ! isset( $image_meta['sizes'][ $created_size_name ] ) ) {
+                                       $image_meta['sizes'][ $created_size_name ] = $created_size_meta;
+                               } else {
+                                       // Remove any newly generated images that are larger than the primary mime type.
+                                       $new_size     = isset( $created_size_meta['filesize'] ) ? $created_size_meta['filesize'] : 0;
+                                       $primary_size = isset( $image_meta['sizes'][ $created_size_name ]['filesize'] ) ? $image_meta['sizes'][ $created_size_name ]['filesize'] : 0;
+
+                                       if ( $new_size && $primary_size && $new_size >= $primary_size ) {
+                                               wp_delete_file( dirname( $file ) . '/' . $created_size_meta['file'] );
+                                               continue;
+                                       }
+                               }
+                               if ( ! isset( $image_meta['sizes'][ $created_size_name ]['sources'] ) ) {
+                                       $image_meta['sizes'][ $created_size_name ]['sources'] = array();
+                               }
+                               $image_meta['sizes'][ $created_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
+                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                         wp_update_attachment_metadata( $attachment_id, $image_meta );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -475,6 +641,125 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Filters the list of image size objects that support secondary mime type output.
+ *
+ * @since 6.1.0
+ *
+ * @param array $sizes         Associative array of image sizes.
+ * @param int   $attachment_id Attachment ID.
+ * @return array $sizes Filtered $sizes with only those that support secondary mime type output.
+ */
+function _wp_filter_image_sizes_additional_mime_type_support( $sizes, $attachment_id ) {
+
+       // 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,
+       );
+
+       /**
+        * Filter the sizes that support secondary mime type output. Developers can use this
+        * to control the output 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.
+        * @param int   $attachment_id Attachment ID.
+        */
+       $enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes, $attachment_id );
+
+       // Filter supported sizes to only include enabled sizes.
+       return array_intersect_key( $sizes, array_filter( $enabled_sizes ) );
+}
+
+/**
+ * Low-level function to create full-size images in additional mime types.
+ *
+ * Updates the image meta after each mime type image is created.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param array  $new_mime_types Array defining what mime types to create.
+ * @param string $file           Full path to the image file.
+ * @param array  $image_meta     The attachment meta data array.
+ * @param int    $attachment_id  Attachment ID to process.
+ * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
+ */
+function _wp_make_additional_mime_types( $new_mime_types, $file, $image_meta, $attachment_id ) {
+       $imagesize          = array(
+               $image_meta['width'],
+               $image_meta['height'],
+       );
+       $exif_meta          = isset( $image_meta['image_meta'] ) ? $image_meta['image_meta'] : null;
+       $original_file_size = isset( $image_meta['filesize'] ) ? $image_meta['filesize'] : wp_filesize( $file );
+
+       foreach ( $new_mime_types as $mime_type ) {
+               list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
+               if ( is_wp_error( $editor ) ) {
+                       // The image cannot be edited.
+                       continue;
+               }
+
+               $suffix    = _wp_get_image_suffix( $resized, $rotated );
+               $extension = wp_get_default_extension_for_mime_type( $mime_type );
+
+               $saved = $editor->save( $editor->generate_filename( $suffix, null, $extension ) );
+
+               if ( is_wp_error( $saved ) ) {
+                       // TODO: Log errors.
+               } else {
+                       // If the saved image is larger than the original, discard it.
+                       $filesize = isset( $saved['filesize'] ) ? $saved['filesize'] : wp_filesize( $saved['path'] );
+                       if ( $filesize && $original_file_size && $filesize > $original_file_size ) {
+                               wp_delete_file( $saved['path'] );
+                               continue;
+                       }
+                       $image_meta['sources'][ $mime_type ] = _wp_get_sources_from_meta( $saved );
+                       wp_update_attachment_metadata( $attachment_id, $image_meta );
+               }
+       }
+
+       return $image_meta;
+}
+
+
+/**
+ * Check if an image belongs to an attachment.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string $filename     Full path to the image file.
+ * @param int   $attachment_id Attachment ID to check.
+ * @return bool True if the image belongs to the attachment, false otherwise.
+ */
+function _wp_image_belongs_to_attachment( $filename, $attachment_id ) {
+       $meta_data = wp_get_attachment_metadata( $attachment_id );
+
+       if ( ! isset( $image_meta['sizes'] ) ) {
+               return false;
+       }
+       $sizes = $image_meta['sizes'];
+       foreach ( $sizes as $size ) {
+               if ( $size['file'] === $filename ) {
+                       return true;
+               }
+               if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
+                       foreach ( $size['sources'] as $source ) {
+                               if ( $source['file'] === $filename ) {
+                                       return true;
+                               }
+                       }
+               }
+       }
+       return false;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Generate attachment meta data and create image sub-sizes for images.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.1.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -630,7 +915,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        wp_update_attachment_metadata( $attachment_id, $metadata );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                        // Create sub-sizes saving the image meta after each.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id, '' );
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1157,3 +1442,97 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        return $dst_file;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Returns an array with the list of valid mime types that a specific mime type should be converted into.
+ * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
+ * is considered the primary output type for this image.
+ *
+ * Called for each uploaded image to determine the list of mime types that should be converted into. Then,
+ * called again for each image size as they are generated to check if the image should be converted into the mime type
+ * for that size.
+ *
+ * @since 6.1.0
+ *
+ * @param int    $attachment_id  The attachment ID.
+ * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
+ *               generate.
+ */
+function wp_upload_image_mime_transforms( $attachment_id ) {
+       $default_image_mime_transforms = array(
+               'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
+               'image/webp' => array( 'image/webp', 'image/jpeg' ),
+       );
+       $image_mime_transforms         = $default_image_mime_transforms;
+
+       /**
+        * Filter the output mime types for a given input mime type and image size.
+        *
+        * @since 6.1.0
+        *
+        * @param array  $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
+        *                                      and the value is one or more mime file types to generate.
+        * @param int    $attachment_id         The ID of the attachment where the hook was dispatched.
+        */
+       $image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
+
+       if ( ! is_array( $image_mime_transforms ) ) {
+               return $default_image_mime_transforms;
+       }
+
+       return array_map(
+               function( $transforms_list ) {
+                       return (array) $transforms_list;
+               },
+               $image_mime_transforms
+       );
+}
+
+/**
+ * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @param string $file          Full path to the image file.
+ * @param int    $attachment_id Attachment ID to process.
+ * @return array An array with two entries, the primary mime type and the list of additional mime types.
+ */
+function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
+       $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
+       $original_mime_type    = wp_get_image_mime( $file );
+       $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
+
+       // Exclude any output mime types that the system doesn't support.
+       $output_mime_types = array_values(
+               array_filter(
+                       $output_mime_types,
+                       function( $mime_type ) {
+                               return wp_image_editor_supports(
+                                       array(
+                                               'mime_type' => $mime_type,
+                                       )
+                               );
+                       }
+               )
+       );
+
+       // Handle an empty value for $output_mime_types: only output the original type.
+       if ( empty( $output_mime_types ) ) {
+               return array( $original_mime_type, array() );
+       }
+
+       // Use original mime type as primary mime type, or alternatively the first one.
+       $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
+       if ( false === $primary_mime_type_key ) {
+               $primary_mime_type_key = 0;
+       }
+       // Split output mime types into primary mime type and additional mime types.
+       $additional_mime_types     = $output_mime_types;
+       list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
+
+       return array(
+               $primary_mime_type,
+               $additional_mime_types,
+       );
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesclasswpimageeditorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-image-editor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-image-editor.php   2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/wp-includes/class-wp-image-editor.php     2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -334,6 +334,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">        protected function get_output_format( $filename = null, $mime_type = null ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $new_ext = null;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // If no mime type is passed but output mime type is set, use that.
+               if ( ! $mime_type && ! empty( $this->output_mime_type ) ) {
+                       $mime_type = $this->output_mime_type;
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 // By default, assume specified type takes priority.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $mime_type ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $new_ext = $this->get_extension( $mime_type );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -425,18 +430,25 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Builds an output filename based on current file, and adding proper suffix
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Builds an output filename based on current file, and adding proper suffix.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 3.5.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @since 6.1.0 Skips adding a suffix when set to an empty string. When the
+        *              file extension being generated doesn't match the image file extension,
+        *              add the extension to the suffix
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param string $suffix
-        * @param string $dest_path
-        * @param string $extension
-        * @return string filename
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param string $suffix    Optional. Suffix to add to the filename. The default null
+        *                          will result in a 'widthxheight' suffix. Passing
+        *                          an empty string will result in no suffix.
+        * @param string $dest_path Optional. The path to save the file to. The default null
+        *                          will use the image file path.
+        * @param string $extension Optional. The file extension to use. The default null
+        *                          will use the image file extension.
+        * @return string filename The generated file name.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
</span><span class="cx" style="display: block; padding: 0 10px">                // $suffix will be appended to the destination filename, just before the extension.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! $suffix ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( null === $suffix ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $suffix = $this->get_suffix();
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -457,7 +469,21 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( empty( $suffix ) ) {
+                       $suffix = '';
+               } else {
+                       $suffix = "-{$suffix}";
+               }
+
+               // When the file extension being generated doesn't match the image file extension,
+               // add the extension to the suffix to ensure a unique file name. Prevents
+               // name conflicts when a single image type can have multiple extensions,
+               // eg. .jpg, .jpeg and .jpe are all valid JPEG extensions.
+               if ( ! empty( $extension ) && $extension !== $ext ) {
+                       $suffix .= "-{$ext}";
+               }
+
+               return trailingslashit( $dir ) . "{$name}{$suffix}.{$new_ext}";
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -637,5 +663,28 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return wp_get_default_extension_for_mime_type( $mime_type );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Set the editor output mime type, useful when outputting alternate mime types.
+        *
+        * Track that the mime type is set with the mime type set flag.
+        *
+        * @since 6.1.0
+        *
+        * @param string $output_mime_type The mime type to set.
+        */
+       public function set_output_mime_type( $output_mime_type ) {
+               $this->output_mime_type = $output_mime_type;
+       }
+
+       /**
+        * Reset the mime type to the original file mime type.
+        *
+        * Reset the mime type set flag.
+        *
+        * @since 6.1.0
+        */
+       public function reset_output_mime_type() {
+               $this->output_mime_type = $this->mime_type;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del></span></pre></div>
<a id="trunksrcwpincludesmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/media.php   2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/wp-includes/media.php     2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1852,6 +1852,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                $filtered_image = wp_img_tag_add_decoding_attr( $filtered_image, $context );
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        // Use alternate mime types when specified and available.
+                       if ( $attachment_id > 0 && _wp_in_front_end_context() ) {
+                               $filtered_image = wp_image_use_alternate_mime_types( $filtered_image, $context, $attachment_id );
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         /**
</span><span class="cx" style="display: block; padding: 0 10px">                         * Filters an img tag within the content for a given context.
</span><span class="cx" style="display: block; padding: 0 10px">                         *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1899,6 +1904,117 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Use alternate mime type images in the front end content output when available.
+ *
+ * @since 6.1.0
+ *
+ * @param string $image         The HTML `img` tag where the attribute should be added.
+ * @param string $context       Additional context to pass to the filters.
+ * @param int    $attachment_id The attachment ID.
+ * @return string Converted `img` tag with `loading` attribute added.
+ */
+function wp_image_use_alternate_mime_types( $image, $context, $attachment_id ) {
+       $metadata = wp_get_attachment_metadata( $attachment_id );
+       if ( empty( $metadata['file'] ) ) {
+               return $image;
+       }
+
+       // Only alter images with a `sources` attribute
+       if ( empty( $metadata['sources'] ) ) {
+               return $image;
+       };
+
+       $target_mimes = array( 'image/webp', 'image/jpeg' );
+
+       /**
+        * Filter the content image mime type output selection and order.
+        *
+        * When outputting images in the content, the first mime type available will be used.
+        *
+        * @since 6.1.0
+        *
+        * @param array  $target_mimes  The image output mime type and order. Default is array( 'image/webp', 'image/jpeg' ).
+        * @param int    $attachment_id The attachment ID.
+        * @param string $context       Additional context to pass to the filters.
+        * @return array The filtered output mime type and order. Return an empty array to skip mime type substitution.
+        */
+       $target_mimes = apply_filters( 'wp_content_image_mimes', $target_mimes, $attachment_id, $context );
+
+       if ( false === $target_mimes ) {
+               return $image;
+       }
+
+       // Find the appropriate size for the provided URL in the first available mime type.
+       foreach ( $target_mimes as $target_mime ) {
+               // Handle full size image replacement.
+               if ( ! empty( $metadata['sources'][ $target_mime ]['file'] ) ) {
+                       $src_filename = wp_basename( $metadata['file'] );
+
+                       // This is the same MIME type as the original, so the entire $target_mime can be skipped.
+                       // Since it is already the preferred MIME type, the entire loop can be cancelled.
+                       if ( $metadata['sources'][ $target_mime ]['file'] === $src_filename ) {
+                               break;
+                       }
+
+                       $image = str_replace( $src_filename, $metadata['sources'][ $target_mime ]['file'], $image );
+
+                       // The full size was replaced, so unset this entirely here so that in the next iteration it is no longer
+                       // considered, simply for a small performance optimization.
+                       unset( $metadata['sources'] );
+               }
+
+               // Go through each image size and replace with the first available mime type version.
+               foreach ( $metadata['sizes'] as $name => $size_data ) {
+                       // Check if size has an original file.
+                       if ( empty( $size_data['file'] ) ) {
+                               continue;
+                       }
+
+                       // Check if size has a source in the desired mime type.
+                       if ( empty( $size_data['sources'][ $target_mime ]['file'] ) ) {
+                               continue;
+                       }
+
+                       $src_filename = wp_basename( $size_data['file'] );
+
+                       // This is the same MIME type as the original, so the entire $target_mime can be skipped.
+                       // Since it is already the preferred MIME type, the entire loop can be cancelled.
+                       if ( $size_data['sources'][ $target_mime ]['file'] === $src_filename ) {
+                               break 2;
+                       }
+
+                       // Found a match, replace with the new filename.
+                       $image = str_replace( $src_filename, $size_data['sources'][ $target_mime ]['file'], $image );
+
+                       // This size was replaced, so unset this entirely here so that in the next iteration it is no longer
+                       // considered, simply for a small performance optimization.
+                       unset( $metadata['sizes'][ $name ] );
+               }
+       }
+       return $image;
+}
+
+/**
+ * Check if execution is currently in the front end content context, outside of <head>.
+ *
+ * @since 6.1.0
+ * @access private
+ *
+ * @return bool True if in the front end content context, false otherwise.
+ */
+function _wp_in_front_end_context() {
+       global $wp_query;
+
+       // Check if this request is generally outside (or before) any frontend context.
+       if ( ! isset( $wp_query ) || defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || is_feed() ) {
+               return false;
+       }
+
+       // Check if we're anywhere before the 'wp_head' action has completed.
+       return did_action( 'template_redirect' ) && ! doing_action( 'wp_head' );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Adds `loading` attribute to an `img` HTML tag.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.5.0
</span></span></pre></div>
<a id="trunksrcwpincludespostphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/post.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/post.php    2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/src/wp-includes/post.php      2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6481,13 +6481,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $meta['sizes'] as $size => $sizeinfo ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! empty( $intermediate_file ) ) {
-                               $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Check for alternate size mime types in the sizeinfo['sources'] array to delete.
+                       if ( isset( $sizeinfo['sources'] ) && is_array( $sizeinfo['sources'] ) ) {
+                               foreach ( $sizeinfo['sources'] as $mime => $properties ) {
+                                       $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
+                                       if ( ! empty( $intermediate_file ) ) {
+                                               $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
+                                               if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+                                                       $deleted = false;
+                                               }
+                                       }
+                               }
+                       } else {
+                               // Otherwise, delete files from the sizeinfo data.
+                               $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
-                                       $deleted = false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! empty( $intermediate_file ) ) {
+                                       $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
+
+                                       if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+                                               $deleted = false;
+                                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6509,26 +6524,60 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Delete the full size images from 'sources' if available, or the root file.
+       if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
+               $sources          = $meta['sources'];
+               $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
+               foreach ( $sources as $mime => $properties ) {
+                       if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
+                               continue;
+                       }
+                       $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
+                       if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
+                               $deleted = false;
+                       }
+               }
+       } else {
+               if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
+                       $deleted = false;
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( is_array( $backup_sizes ) ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Delete the root (edited) file which was not deleted above.
+               if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
+                       $deleted = false;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $backup_sizes as $size ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Delete files from 'sources' data if available, otherwise from 'sizes' data.
+                       if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
+                               // Delete any backup images stored in the 'sources' array.
+                               if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
+                                       foreach ( $size['sources'] as $mime => $properties ) {
+                                               $del_file = path_join( dirname( $meta['file'] ), $properties['file'] );
+                                               if ( ! empty( $del_file ) ) {
+                                                       $del_file = path_join( $uploadpath['basedir'], $del_file );
+                                                       if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
+                                                               $deleted = false;
+                                                       }
+                                               }
+                                       }
+                               }
+                       } else {
+                               $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! empty( $del_file ) ) {
-                               $del_file = path_join( $uploadpath['basedir'], $del_file );
-
-                               if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
-                                       $deleted = false;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! empty( $del_file ) ) {
+                                       $del_file = path_join( $uploadpath['basedir'], $del_file );
+                                       if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
+                                               $deleted = false;
+                                       }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
-               $deleted = false;
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         return $deleted;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunktestsphpunitdataimagestestimagerotated90ccwjpg"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg 2022-07-21 16:07:15 UTC (rev 53750)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg  2022-07-21 18:01:01 UTC (rev 53751)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/data/images/test-image-rotated-90ccw.jpg
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitdataimagestestimagerotated90cwwebp"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp 2022-07-21 16:07:15 UTC (rev 53750)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp  2022-07-21 18:01:01 UTC (rev 53751)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/data/images/test-image-rotated-90cw.webp
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitdataimagestestimagejpeg"></a>
<div class="binary"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/data/images/test-image.jpeg</h4>
<pre class="diff"><span>
<span class="cx">(Binary files differ)
</span></span></pre></div>
<span class="cx" style="display: block; padding: 0 10px">Index: trunk/tests/phpunit/data/images/test-image.jpeg
</span><span class="cx" style="display: block; padding: 0 10px">===================================================================
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">--- trunk/tests/phpunit/data/images/test-image.jpeg      2022-07-21 16:07:15 UTC (rev 53750)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+++ trunk/tests/phpunit/data/images/test-image.jpeg       2022-07-21 18:01:01 UTC (rev 53751)
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/data/images/test-image.jpeg
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span><a id="svnmimetype"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:mime-type</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+application/octet-stream
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsimageeditorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/image/editor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/image/editor.php        2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/tests/phpunit/tests/image/editor.php  2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -131,6 +131,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( 86, $editor->get_quality(), 'Output image format is WEBP. Quality setting for it should be 86.' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Removing PNG to WEBP conversion on save. Quality setting should reset to the default.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $editor->reset_output_mime_type();
</ins><span class="cx" style="display: block; padding: 0 10px">                 remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $editor->save();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( 82, $editor->get_quality(), 'After removing image conversion quality setting should reset to the default of 82.' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -154,6 +155,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( 42, $editor->get_quality(), 'Image conversion from JPEG to WEBP. Filtered WEBP quality shoild be 42.' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // After removing the conversion the quality setting should reset to the filtered value for the original image type, JPEG.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $editor->reset_output_mime_type();
</ins><span class="cx" style="display: block; padding: 0 10px">                 remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $editor->save();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -226,10 +228,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Test with a suffix only.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertSame( 'canola-100x50.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertSame( 'canola-100x50-jpg.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Combo!
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new-jpg.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Test with a stream destination.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( 'file://testing/path/canola-100x50.jpg', $editor->generate_filename( null, 'file://testing/path' ) );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -362,4 +364,404 @@
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * Test creating  the original image mime type when the image is uploaded.
+        *
+        * @ticket 55443
+        *
+        * @dataProvider provider_image_with_default_behaviors_during_upload
+        */
+       public function it_should_create_the_original_image_mime_type_when_the_image_is_uploaded( $file_location, $expected_mime, $targeted_mime ) {
+               $attachment_id = $this->factory->attachment->create_upload_object( $file_location );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertIsArray( $metadata );
+               foreach ( $metadata['sizes'] as $size_name => $properties ) {
+                       $this->assertArrayHasKey( 'sources', $properties );
+                       $this->assertIsArray( $properties['sources'] );
+                       $this->assertArrayHasKey( $expected_mime, $properties['sources'] );
+                       $this->assertArrayHasKey( 'filesize', $properties['sources'][ $expected_mime ] );
+                       $this->assertArrayHasKey( 'file', $properties['sources'][ $expected_mime ] );
+                       $this->assertArrayHasKey( $targeted_mime, $properties['sources'] );
+                       $this->assertArrayHasKey( 'filesize', $properties['sources'][ $targeted_mime ] );
+                       $this->assertArrayHasKey( 'file', $properties['sources'][ $targeted_mime ] );
+               }
+       }
+
+       /**
+        * Data provider for it_should_create_the_original_image_mime_type_when_the_image_is_uploaded.
+        */
+       public function provider_image_with_default_behaviors_during_upload() {
+               yield 'JPEG image' => array(
+                       DIR_TESTDATA . '/images/test-image.jpg',
+                       'image/jpeg',
+                       'image/webp',
+               );
+
+               yield 'WebP image' => array(
+                       DIR_TESTDATA . '/images/webp-lossy.webp',
+                       'image/webp',
+                       'image/jpeg',
+               );
+       }
+
+       /**
+        * Test Do not create the sources property if no transform is provided.
+        *
+        * @ticket 55443
+        */
+       public function it_should_not_create_the_sources_property_if_no_transform_is_provided() {
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertIsArray( $metadata );
+               foreach ( $metadata['sizes'] as $size_name => $properties ) {
+                       $this->assertArrayNotHasKey( 'sources', $properties );
+               }
+       }
+
+       /**
+        * Test creating the sources property when no transform is available.
+        *
+        * @ticket 55443
+        */
+       public function it_should_create_the_sources_property_when_no_transform_is_available() {
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function () {
+                               return array( 'image/jpeg' => array() );
+                       }
+               );
+
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertIsArray( $metadata );
+               foreach ( $metadata['sizes'] as $size_name => $properties ) {
+                       $this->assertArrayHasKey( 'sources', $properties );
+                       $this->assertIsArray( $properties['sources'] );
+                       $this->assertArrayHasKey( 'image/jpeg', $properties['sources'] );
+                       $this->assertArrayHasKey( 'filesize', $properties['sources']['image/jpeg'] );
+                       $this->assertArrayHasKey( 'file', $properties['sources']['image/jpeg'] );
+                       $this->assertArrayNotHasKey( 'image/webp', $properties['sources'] );
+               }
+       }
+
+       /**
+        * Test not creating the sources property if the mime is not specified on the transforms images.
+        *
+        * @ticket 55443
+        */
+       public function it_should_not_create_the_sources_property_if_the_mime_is_not_specified_on_the_transforms_images() {
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function () {
+                               return array( 'image/jpeg' => array() );
+                       }
+               );
+
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/webp-lossy.webp'
+               );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertIsArray( $metadata );
+               foreach ( $metadata['sizes'] as $size_name => $properties ) {
+                       $this->assertArrayNotHasKey( 'sources', $properties );
+               }
+       }
+
+
+       /**
+        * Test creating a WebP version with all the required properties.
+        *
+        * @ticket 55443
+        */
+       public function it_should_create_a_webp_version_with_all_the_required_properties() {
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+               $this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] );
+               $this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] );
+               $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
+               $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
+               $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] );
+               $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
+               $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
+               $this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
+               $this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
+       }
+
+       /**
+        * Test removing `scaled` suffix from the generated filename.
+        *
+        * @ticket 55443
+        */
+       public function it_should_remove_scaled_suffix_from_the_generated_filename() {
+               // The leafs image is 1080 pixels wide with this filter we ensure a -scaled version is created.
+               add_filter(
+                       'big_image_size_threshold',
+                       function () {
+                               return 850;
+                       }
+               );
+
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+               $metadata      = wp_get_attachment_metadata( $attachment_id );
+               $this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) );
+               $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] );
+               $this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
+               $this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
+       }
+
+       /**
+        * Test removing the generated webp images when the attachment is deleted.
+        *
+        * @ticket 55443
+        */
+       public function it_should_remove_the_generated_webp_images_when_the_attachment_is_deleted() {
+               // Make sure no editor is available.
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $file    = get_attached_file( $attachment_id, true );
+               $dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+               $this->assertIsString( $file );
+               $this->assertFileExists( $file );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+               $sizes    = array( 'thumbnail', 'medium' );
+
+               foreach ( $sizes as $size_name ) {
+                       $this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] );
+                       $this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] );
+                       $this->assertFileExists(
+                               path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
+                       );
+               }
+
+               wp_delete_attachment( $attachment_id );
+
+               foreach ( $sizes as $size_name ) {
+                       $this->assertFileDoesNotExist(
+                               path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
+                       );
+               }
+       }
+
+       /**
+        * Test removing the attached WebP version if the attachment is force deleted but empty trash day is not defined.
+        *
+        * @ticket 55443
+        */
+       public function it_should_remove_the_attached_webp_version_if_the_attachment_is_force_deleted_but_empty_trash_day_is_not_defined() {
+               // Make sure no editor is available.
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $file    = get_attached_file( $attachment_id, true );
+               $dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+               $this->assertIsString( $file );
+               $this->assertFileExists( $file );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertFileExists(
+                       path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+               );
+
+               wp_delete_attachment( $attachment_id, true );
+
+               $this->assertFileDoesNotExist(
+                       path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+               );
+       }
+
+       /**
+        * Test removing the WebP version of the image if the image is force deleted and empty trash days is set to zero.
+        *
+        * @ticket 55443
+        */
+       public function it_should_remove_the_webp_version_of_the_image_if_the_image_is_force_deleted_and_empty_trash_days_is_set_to_zero() {
+               // Make sure no editor is available.
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $file    = get_attached_file( $attachment_id, true );
+               $dirname = pathinfo( $file, PATHINFO_DIRNAME );
+
+               $this->assertIsString( $file );
+               $this->assertFileExists( $file );
+
+               $metadata = wp_get_attachment_metadata( $attachment_id );
+
+               $this->assertFileExists(
+                       path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+               );
+
+               define( 'EMPTY_TRASH_DAYS', 0 );
+
+               wp_delete_attachment( $attachment_id, true );
+
+               $this->assertFileDoesNotExist(
+                       path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
+               );
+       }
+
+       /**
+        * Test avoiding the change of URLs of images that are not part of the media library.
+        *
+        * @ticket 55443
+        */
+       public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
+               $paragraph = '<p>Donec accumsan, sapien et <img src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';
+
+               $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
+       }
+
+       /**
+        * Test avoiding replacing not existing attachment IDs.
+        *
+        * @ticket 55443
+        */
+       public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
+               $paragraph = '<p>Donec accumsan, sapien et <img class="wp-image-0" src="https://ia600200.us.archive.org/16/items/SPD-SLRSY-1867/hubblesite_2001_06.jpg">, id commodo nisi sapien et est. Mauris nisl odio, iaculis vitae pellentesque nec.</p>';
+
+               $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
+       }
+
+       /**
+        * Test preventing replacing a WebP image.
+        *
+        * @ticket 55443
+        */
+       public function it_should_test_preventing_replacing_a_webp_image() {
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/webp-lossy.webp'
+               );
+
+               $tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+               $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+       }
+
+       /**
+        * Test preventing replacing a jpg image if the image does not have the target class name.
+        *
+        * @ticket 55443
+        */
+       public function it_should_test_preventing_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $tag = wp_get_attachment_image( $attachment_id, 'medium' );
+
+               $this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
+       }
+
+       /**
+        * Test replacing the references to a JPG image to a WebP version.
+        *
+        * @dataProvider provider_replace_images_with_different_extensions
+        *
+        * @ticket 55443
+        */
+       public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
+               $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
+
+               $tag          = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+               $expected_tag = $tag;
+               $metadata     = wp_get_attachment_metadata( $attachment_id );
+               foreach ( $metadata['sizes'] as $size => $properties ) {
+                       $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+               }
+
+               $this->assertNotEmpty( $expected_tag );
+               $this->assertNotSame( $tag, $expected_tag );
+               $this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+       }
+
+       public function provider_replace_images_with_different_extensions() {
+               yield 'An image with a .jpg extension' => array( DIR_TESTDATA . '/images/test-image.jpg' );
+               yield 'An image with a .jpeg extension' => array( DIR_TESTDATA . '/images/test-image.jpeg' );
+       }
+
+       /**
+        * Test the full image size from the original mime type.
+        *
+        * @ticket 55443
+        */
+       public function it_should_contain_the_full_image_size_from_the_original_mime() {
+               $attachment_id = $this->factory->attachment->create_upload_object(
+                       DIR_TESTDATA . '/images/test-image.jpg'
+               );
+
+               $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+               $expected = array(
+                       'ext'  => 'jpg',
+                       'type' => 'image/jpeg',
+               );
+               $this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
+               $this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+       }
+
+       /**
+        * Test preventing replacing an image with no available sources.
+        *
+        * @ticket 55443
+        */
+       public function it_should_prevent_replacing_an_image_with_no_available_sources() {
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+               $attachment_id = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.jpg' );
+
+               $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+               $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+       }
+
+       /**
+        * Test preventing update not supported images with no available sources.
+        *
+        * @dataProvider provider_it_should_prevent_update_not_supported_images_with_no_available_sources
+        *
+        * @ticket 55443
+        */
+       public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
+               $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
+
+               $this->assertIsNumeric( $attachment_id );
+               $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
+
+               $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
+       }
+
+       /**
+        * Data provider for it_should_prevent_update_not_supported_images_with_no_available_sources.
+        */
+       public function provider_it_should_prevent_update_not_supported_images_with_no_available_sources() {
+               yield 'PNG image' => array( DIR_TESTDATA . '/images/test-image.png' );
+               yield 'GIFT image' => array( DIR_TESTDATA . '/images/test-image.gif' );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsimagefunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/image/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/image/functions.php     2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/tests/phpunit/tests/image/functions.php       2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -639,6 +639,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Use legacy JPEG output.
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                copy( $orig_file, $test_file );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -677,6 +680,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 300,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'large'     => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -684,6 +693,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 1024,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'thumbnail' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -691,6 +706,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 150,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'filesize' => wp_filesize( $test_file ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -702,6 +723,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $metadata['sizes'] as $size ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        unlink( $temp_dir . $size['file'] );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -716,6 +738,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                update_option( 'medium_crop', 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Use legacy JPEG output.
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                copy( $orig_file, $test_file );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -754,6 +779,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 300,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'large'     => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -761,6 +792,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 1024,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
+                                               ),
+                                       ),
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                                'thumbnail' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -768,6 +806,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'height'    => 150,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'sources'   => array(
+                                               'image/jpeg' => array(
+                                                       'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
+                                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                 ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                        'filesize' => wp_filesize( $test_file ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -779,6 +823,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $metadata['sizes'] as $size ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        unlink( $temp_dir . $size['file'] );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -789,6 +835,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                // Use legacy JPEG output.
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
</span><span class="cx" style="display: block; padding: 0 10px">                copy( $orig_file, $test_file );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -821,6 +870,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'height'    => 100,
</span><span class="cx" style="display: block; padding: 0 10px">                        'mime-type' => 'image/jpeg',
</span><span class="cx" style="display: block; padding: 0 10px">                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'sources'   => array(
+                               'image/jpeg' => array(
+                                       'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
+                                       'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
+                               ),
+                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Different environments produce slightly different filesize results.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -836,6 +891,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $metadata['sizes'] as $size ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        unlink( $temp_dir . $size['file'] );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function filter_fallback_intermediate_image_sizes( $fallback_sizes, $metadata ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1026,4 +1082,431 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_upload_image_mime_transforms_generates_webp_and_jpeg_for_both_by_default() {
+               $result = wp_upload_image_mime_transforms( 42 );
+               $this->assertArrayHasKey( 'image/jpeg', $result );
+               $this->assertArrayHasKey( 'image/webp', $result );
+               $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/jpeg'] );
+               $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/webp'] );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_upload_image_mime_transforms_filter_always_use_webp_instead_of_jpeg() {
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function( $transforms ) {
+                               // Ensure JPG only results in WebP files.
+                               $transforms['image/jpeg'] = array( 'image/webp' );
+                               // Unset WebP since it does not need any transformation in that case.
+                               unset( $transforms['image/webp'] );
+                               return $transforms;
+                       }
+               );
+
+               $result = wp_upload_image_mime_transforms( 42 );
+               $this->assertArrayHasKey( 'image/jpeg', $result );
+               $this->assertArrayNotHasKey( 'image/webp', $result );
+               $this->assertSameSets( array( 'image/webp' ), $result['image/jpeg'] );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_upload_image_mime_transforms_filter_receives_parameters() {
+               $attachment_id = null;
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function( $transforms, $param1 ) use ( &$attachment_id ) {
+                               $attachment_id = $param1;
+                               return $transforms;
+                       },
+                       10,
+                       2
+               );
+
+               wp_upload_image_mime_transforms( 23 );
+               $this->assertSame( 23, $attachment_id );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_upload_image_mime_transforms_filter_with_empty_array() {
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+               $result = wp_upload_image_mime_transforms( 42 );
+               $this->assertSame( array(), $result );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_upload_image_mime_transforms_filter_with_invalid_usage() {
+               $default = wp_upload_image_mime_transforms( 42 );
+
+               add_filter( 'wp_upload_image_mime_transforms', '__return_false' );
+               $result = wp_upload_image_mime_transforms( 42 );
+               $this->assertSame( $default, $result );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_get_primary_and_additional_mime_types_default() {
+               $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+               $this->assertSame( 'image/jpeg', $primary_mime_type );
+
+               // WebP may not be supported by the server, in which case it will be stripped from the results.
+               if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->assertSame( array( 'image/webp' ), $additional_mime_types );
+               } else {
+                       $this->assertSame( array(), $additional_mime_types );
+               }
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_get_primary_and_additional_mime_types_prefer_original_mime() {
+               $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+               // Set 'image/jpeg' only as secondary output MIME type.
+               // Still, because it is the original, it should be chosen as primary over 'image/webp'.
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function( $transforms ) {
+                               $transforms['image/jpeg'] = array( 'image/webp', 'image/jpeg' );
+                               return $transforms;
+                       }
+               );
+
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+               $this->assertSame( 'image/jpeg', $primary_mime_type );
+
+               // WebP may not be supported by the server, in which case it will be stripped from the results.
+               if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->assertSame( array( 'image/webp' ), $additional_mime_types );
+               } else {
+                       $this->assertSame( array(), $additional_mime_types );
+               }
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_get_primary_and_additional_mime_types_use_original_mime_when_no_transformation_rules() {
+               $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+               // Strip all transformation rules.
+               add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
+
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+               $this->assertSame( 'image/jpeg', $primary_mime_type );
+               $this->assertSame( array(), $additional_mime_types );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_get_primary_and_additional_mime_types_different_output_mime() {
+               $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+               // Set 'image/webp' as the only output MIME type.
+               // In that case, JPEG is not generated at all, so WebP becomes the primary MIME type.
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function( $transforms ) {
+                               $transforms['image/jpeg'] = array( 'image/webp' );
+                               return $transforms;
+                       }
+               );
+
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+
+               // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
+               if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->assertSame( 'image/webp', $primary_mime_type );
+               } else {
+                       $this->assertSame( 'image/jpeg', $primary_mime_type );
+               }
+
+               $this->assertSame( array(), $additional_mime_types );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_get_primary_and_additional_mime_types_different_output_mimes() {
+               $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
+
+               // Set 'image/webp' and 'image/avif' as output MIME types.
+               // In that case, JPEG is not generated at all, with WebP being the primary MIME type and AVIF the secondary.
+               add_filter(
+                       'wp_upload_image_mime_transforms',
+                       function( $transforms ) {
+                               $transforms['image/jpeg'] = array( 'image/webp', 'image/avif' );
+                               return $transforms;
+                       }
+               );
+
+               list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
+
+               // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
+               if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->assertSame( 'image/webp', $primary_mime_type );
+               } else {
+                       $this->assertSame( 'image/jpeg', $primary_mime_type );
+               }
+
+               // AVIF may not be supported by the server, in which case it will be stripped from the results.
+               if ( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
+                       $this->assertSame( array( 'image/avif' ), $additional_mime_types );
+               } else {
+                       $this->assertSame( array(), $additional_mime_types );
+               }
+       }
+
+       /**
+        * @ticket 55443
+        * @dataProvider data__wp_filter_image_sizes_additional_mime_type_support
+        */
+       public function test__wp_filter_image_sizes_additional_mime_type_support( $input_size_data, $filter_callback, $expected_size_names ) {
+               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 );
+               }
+
+               $expected_size_data = array_intersect_key( $input_size_data, array_flip( $expected_size_names ) );
+
+               $output_size_data = _wp_filter_image_sizes_additional_mime_type_support( $input_size_data, 42 );
+               $this->assertEqualSetsWithIndex( $expected_size_data, $output_size_data );
+       }
+
+       public function data__wp_filter_image_sizes_additional_mime_type_support() {
+               $thumbnail_data    = array(
+                       'width'  => 150,
+                       'height' => 150,
+                       'crop'   => true,
+               );
+               $medium_data       = array(
+                       'width'  => 300,
+                       'height' => 300,
+                       'crop'   => false,
+               );
+               $medium_large_data = array(
+                       'width'  => 768,
+                       'height' => 0,
+                       'crop'   => false,
+               );
+               $large_data        = array(
+                       'width'  => 1024,
+                       'height' => 1024,
+                       'crop'   => false,
+               );
+               $custom_data       = array(
+                       'width'  => 512,
+                       'height' => 512,
+                       'crop'   => true,
+               );
+
+               return array(
+                       array(
+                               array(
+                                       'thumbnail'    => $thumbnail_data,
+                                       'medium'       => $medium_data,
+                                       'medium_large' => $medium_large_data,
+                                       'large'        => $large_data,
+                               ),
+                               null,
+                               array( 'thumbnail', 'medium', 'medium_large', 'large' ),
+                       ),
+                       array(
+                               array(
+                                       'thumbnail' => $thumbnail_data,
+                                       'medium'    => $medium_data,
+                                       'custom'    => $custom_data,
+                               ),
+                               null,
+                               array( 'thumbnail', 'medium' ),
+                       ),
+                       array(
+                               array(
+                                       'thumbnail'    => $thumbnail_data,
+                                       'medium'       => $medium_data,
+                                       'medium_large' => $medium_large_data,
+                                       'large'        => $large_data,
+                               ),
+                               function( $enabled_sizes ) {
+                                       unset( $enabled_sizes['medium_large'], $enabled_sizes['large'] );
+                                       return $enabled_sizes;
+                               },
+                               array( 'thumbnail', 'medium' ),
+                       ),
+                       array(
+                               array(
+                                       'thumbnail'    => $thumbnail_data,
+                                       'medium'       => $medium_data,
+                                       'medium_large' => $medium_large_data,
+                                       'large'        => $large_data,
+                               ),
+                               function( $enabled_sizes ) {
+                                       $enabled_sizes['medium_large'] = false;
+                                       $enabled_sizes['large']        = false;
+                                       return $enabled_sizes;
+                               },
+                               array( 'thumbnail', 'medium' ),
+                       ),
+                       array(
+                               array(
+                                       'thumbnail' => $thumbnail_data,
+                                       'medium'    => $medium_data,
+                                       'custom'    => $custom_data,
+                               ),
+                               function( $enabled_sizes ) {
+                                       unset( $enabled_sizes['medium'] );
+                                       $enabled_sizes['custom'] = true;
+                                       return $enabled_sizes;
+                               },
+                               array( 'thumbnail', 'custom' ),
+                       ),
+               );
+       }
+
+       /**
+        * Test the `_wp_maybe_scale_and_rotate_image()` function.
+        *
+        * @dataProvider data_test__wp_maybe_scale_and_rotate_image
+        *
+        * @ticket 55443
+        */
+       public function test__wp_maybe_scale_and_rotate_image( $file, $imagesize, $mime_type, $expected ) {
+               if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
+                       $this->markTestSkipped( sprintf( 'This test requires %s support.', $mime_type ) );
+               }
+
+               $attributes    = array( 'post_mime_type' => $mime_type );
+               $attachment_id = $this->factory->attachment->create_object( $file, 0, $attributes );
+               $exif_meta     = wp_read_image_metadata( $file );
+
+               list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
+
+               $this->assertSame( $expected['rotated'], $rotated );
+               $this->assertSame( $expected['resized'], $resized );
+               $this->assertSame( $expected['size'], $editor->get_size() );
+       }
+
+       /**
+        * Data provider for the `test__wp_maybe_scale_and_rotate_image()` test.
+        *
+        * @return array
+        */
+       public function data_test__wp_maybe_scale_and_rotate_image() {
+               return array(
+
+                       // Image that will be scaled.
+                       array(
+                               DIR_TESTDATA . '/images/test-image-large.jpg',
+                               array( 3000, 2250 ),
+                               'image/jpeg',
+                               array(
+                                       'rotated' => false,
+                                       'resized' => true,
+                                       'size'    => array(
+                                               'width'  => 2560,
+                                               'height' => 1920,
+                                       ),
+                               ),
+                       ),
+
+                       // Image that will not be scaled.
+                       array(
+                               DIR_TESTDATA . '/images/canola.jpg',
+                               array( 640, 480 ),
+                               'image/jpeg',
+                               array(
+                                       'rotated' => false,
+                                       'resized' => false,
+                                       'size'    => array(
+                                               'width'  => 640,
+                                               'height' => 480,
+                                       ),
+                               ),
+                       ),
+
+                       // Image that will be flipped.
+                       array(
+                               DIR_TESTDATA . '/images/test-image-upside-down.jpg',
+                               array( 600, 450 ),
+                               'image/jpeg',
+                               array(
+                                       'rotated' => true,
+                                       'resized' => false,
+                                       'size'    => array(
+                                               'width'  => 600,
+                                               'height' => 450,
+                                       ),
+                               ),
+                       ),
+
+                       // Image that will be rotated.
+                       array(
+                               DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg',
+                               array( 1200, 1800 ),
+                               'image/jpeg',
+                               array(
+                                       'rotated' => true,
+                                       'resized' => false,
+                                       'size'    => array(
+                                               'width'  => 1800,
+                                               'height' => 1200,
+                                       ),
+                               ),
+                       ),
+
+                       // Image that will not be rotated - WebP Exif is not supported in PHP.
+                       array(
+                               DIR_TESTDATA . '/images/test-image-rotated-90cw.webp',
+                               array( 1024, 768 ),
+                               'image/webp',
+                               array(
+                                       'rotated' => false,
+                                       'resized' => false,
+                                       'size'    => array(
+                                               'width'  => 1024,
+                                               'height' => 768,
+                                       ),
+                               ),
+                       ),
+
+               );
+       }
+
+       /**
+        * Test the `_wp_get_image_suffix()` function.
+        * @dataProvider data_test__wp_get_image_suffix
+        *
+        * @ticket 55443
+        */
+       public function test__wp_get_image_suffix( $resized, $rotated, $expected ) {
+               $this->assertSame( $expected, _wp_get_image_suffix( $resized, $rotated ) );
+       }
+
+       /**
+        * Data provider for the `test__wp_get_image_suffix()` test.
+        */
+       public function data_test__wp_get_image_suffix() {
+               return array(
+                       array( false, false, '' ),
+                       array( true, false, 'scaled' ),
+                       array( false, true, 'rotated' ),
+                       array( true, true, 'scaled' ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsmediaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/media.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/media.php       2022-07-21 16:07:15 UTC (rev 53750)
+++ trunk/tests/phpunit/tests/media.php 2022-07-21 18:01:01 UTC (rev 53751)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2251,11 +2251,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Do not add width, height, and loading.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2289,9 +2292,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $img = wp_img_tag_add_loading_attr( $img, 'test' );
</span><span class="cx" style="display: block; padding: 0 10px">                $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px">                $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // The content filter should return the image unchanged.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $img, wp_filter_content_tags( $img ) );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2361,6 +2367,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter(
</span><span class="cx" style="display: block; padding: 0 10px">                        'wp_content_img_tag',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2423,6 +2430,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @requires function imagejpeg
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_wp_filter_content_tags_schemes() {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">                 $image_meta = wp_get_attachment_metadata( self::$large_id );
</span><span class="cx" style="display: block; padding: 0 10px">                $size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2468,6 +2476,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $actual = wp_filter_content_tags( $unfiltered );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $expected, $actual );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2961,11 +2970,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Do not add loading, srcset, and sizes.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3041,11 +3052,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Do not add width, height, srcset, and sizes.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3074,9 +3087,13 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Enable globally for all tags.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_lazy_loading_enabled', '__return_true' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_lazy_loading_enabled', '__return_true' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3101,9 +3118,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Disable globally for all tags.
</span><span class="cx" style="display: block; padding: 0 10px">                add_filter( 'wp_lazy_loading_enabled', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $this->assertSame( $content, wp_filter_content_tags( $content ) );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_lazy_loading_enabled', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3529,6 +3549,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
</span><span class="cx" style="display: block; padding: 0 10px">                global $wp_query, $wp_the_query;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
</span><span class="cx" style="display: block; padding: 0 10px">                $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3564,6 +3585,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $content_filtered = wp_filter_content_tags( $content_unfiltered, 'the_content' );
</span><span class="cx" style="display: block; padding: 0 10px">                        remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // After filtering, the first image should not be lazy-loaded while the other ones should be.
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $content_expected, $content_filtered );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3613,6 +3635,169 @@
</span><span class="cx" style="display: block; padding: 0 10px">                // Clean up the above filter.
</span><span class="cx" style="display: block; padding: 0 10px">                remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_image_use_alternate_mime_types_replaces_jpg_with_webp_where_available() {
+               if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->markTestSkipped( 'This test requires WebP support.' );
+               }
+
+               // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+               $tag          = wp_get_attachment_image( self::$large_id, 'full' );
+               $expected_tag = $tag;
+
+               $metadata = wp_get_attachment_metadata( self::$large_id );
+               foreach ( $metadata['sizes'] as $size => $properties ) {
+                       // Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
+                       if ( ! isset( $properties['sources']['image/webp'] ) ) {
+                               continue;
+                       }
+                       $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+               }
+               // Same applies to the full size.
+               if ( isset( $metadata['sources']['image/webp'] ) ) {
+                       $expected_tag = str_replace( $metadata['sources']['image/jpeg']['file'], $metadata['sources']['image/webp']['file'], $expected_tag );
+               }
+
+               $this->assertNotSame( $tag, $expected_tag );
+               $this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_image_use_alternate_mime_types_does_not_replace_jpg_when_webp_is_not_available() {
+               if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->markTestSkipped( 'This test requires WebP support.' );
+               }
+
+               // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+               $tag = wp_get_attachment_image( self::$large_id, 'full' );
+
+               // Update attachment metadata as if the image had no WebP available for any sub-sizes and the full size.
+               $metadata = wp_get_attachment_metadata( self::$large_id );
+               foreach ( $metadata['sizes'] as $size => $properties ) {
+                       unset( $metadata['sizes'][ $size ]['sources']['image/webp'] );
+               }
+               unset( $metadata['sources']['image/webp'] );
+               wp_update_attachment_metadata( self::$large_id, $metadata );
+
+               $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_image_use_alternate_mime_types_still_replaces_jpg_subsizes_when_webp_is_not_available_for_full_size() {
+               if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->markTestSkipped( 'This test requires WebP support.' );
+               }
+
+               // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+               $tag          = wp_get_attachment_image( self::$large_id, 'full' );
+               $expected_tag = $tag;
+
+               // Update attachment metadata as if the image had no WebP available for the full size.
+               $metadata = wp_get_attachment_metadata( self::$large_id );
+               unset( $metadata['sources']['image/webp'] );
+               wp_update_attachment_metadata( self::$large_id, $metadata );
+
+               foreach ( $metadata['sizes'] as $size => $properties ) {
+                       // Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
+                       if ( ! isset( $properties['sources']['image/webp'] ) ) {
+                               continue;
+                       }
+                       $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
+               }
+
+               $this->assertNotSame( $tag, $expected_tag );
+               $this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test_wp_image_use_alternate_mime_types_respects_wp_content_image_mimes_filter() {
+               if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
+                       $this->markTestSkipped( 'This test requires WebP support.' );
+               }
+
+               // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
+               $tag = wp_get_attachment_image( self::$large_id, 'full' );
+
+               // Invalid filter value results in no changes to content.
+               add_filter( 'wp_content_image_mimes', '__return_false' );
+               $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+
+               // Empty array results in no changes to content.
+               add_filter( 'wp_content_image_mimes', '__return_empty_array' );
+               $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+
+               // Preferring JPEG over WebP results in no changes to content.
+               add_filter(
+                       'wp_content_image_mimes',
+                       function() {
+                               return array( 'image/jpeg', 'image/webp' );
+                       }
+               );
+               $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_in_front_end_context_without_wp_query() {
+               unset( $GLOBALS['wp_query'] );
+
+               $this->assertFalse( _wp_in_front_end_context() );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_in_front_end_context_with_feed() {
+               remove_all_actions( 'template_redirect' );
+               do_action( 'template_redirect' );
+               $GLOBALS['wp_query']->is_feed = true;
+
+               $this->assertFalse( _wp_in_front_end_context() );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_in_front_end_context_before_and_after_template_redirect() {
+               $result = _wp_in_front_end_context();
+
+               remove_all_actions( 'template_redirect' );
+               do_action( 'template_redirect' );
+
+               $this->assertFalse( $result );
+               $this->assertTrue( _wp_in_front_end_context() );
+       }
+
+       /**
+        * @ticket 55443
+        */
+       public function test__wp_in_front_end_context_within_wp_head() {
+               remove_all_actions( 'template_redirect' );
+               do_action( 'template_redirect' );
+
+               // Call function within a 'wp_head' callback.
+               remove_all_actions( 'wp_head' );
+               $result = null;
+               add_action(
+                       'wp_head',
+                       function() use ( &$result ) {
+                               $result = _wp_in_front_end_context();
+                       }
+               );
+               do_action( 'wp_head' );
+
+               $this->assertFalse( $result );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre>
</div>
</div>

</body>
</html>