Make WordPress Core

Ticket #55443: 55443.2.diff

File 55443.2.diff, 53.7 KB (added by adamsilverstein, 2 years ago)

latest from PR

  • src/js/_enqueues/vendor/plupload/handlers.js

    diff --git src/js/_enqueues/vendor/plupload/handlers.js src/js/_enqueues/vendor/plupload/handlers.js
    index fa602daf43..c7e3c253d3 100644
    jQuery( document ).ready( function( $ ) { 
    486486
    487487                times = tryAgainCount[ file.id ];
    488488
    489                 if ( times && times > 4 ) {
     489                if ( times && times > 8 ) {
    490490                        /*
    491491                         * The file may have been uploaded and attachment post created,
    492492                         * but post-processing and resizing failed...
  • src/js/_enqueues/vendor/plupload/wp-plupload.js

    diff --git src/js/_enqueues/vendor/plupload/wp-plupload.js src/js/_enqueues/vendor/plupload/wp-plupload.js
    index 0fdebf77d1..217b3c09e2 100644
    window.wp = window.wp || {}; 
    138138
    139139                        times = tryAgainCount[ file.id ];
    140140
    141                         if ( times && times > 4 ) {
     141                        if ( times && times > 8 ) {
    142142                                /*
    143143                                 * The file may have been uploaded and attachment post created,
    144144                                 * but post-processing and resizing failed...
  • src/wp-admin/includes/image.php

    diff --git src/wp-admin/includes/image.php src/wp-admin/includes/image.php
    index 2818b3dd5d..854ba08ce1 100644
    function _wp_image_meta_replace_original( $saved_data, $original_file, $image_me 
    217217}
    218218
    219219/**
    220  * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
     220 * Creates image mime variations and sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
    221221 *
    222222 * Intended for use after an image is uploaded. Saves/updates the image metadata after each
    223223 * sub-size is created. If there was an error, it is added to the returned image metadata array.
    224224 *
    225225 * @since 5.3.0
     226 * @since 6.0.0 Generates sub-sizes in alternate mime types based on the `wp_image_mime_transforms` filter.
    226227 *
    227228 * @param string $file          Full path to the image file.
    228229 * @param int    $attachment_id Attachment ID to process.
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    243244                'file'     => _wp_relative_upload_path( $file ),
    244245                'filesize' => wp_filesize( $file ),
    245246                'sizes'    => array(),
     247                'sources'  => array(),
    246248        );
    247249
    248250        // Fetch additional metadata from EXIF/IPTC.
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    252254                $image_meta['image_meta'] = $exif_meta;
    253255        }
    254256
    255         // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
    256         if ( 'image/png' !== $imagesize['mime'] ) {
    257 
    258                 /**
    259                  * Filters the "BIG image" threshold value.
    260                  *
    261                  * If the original image width or height is above the threshold, it will be scaled down. The threshold is
    262                  * used as max width and max height. The scaled down image will be used as the largest available size, including
    263                  * the `_wp_attached_file` post meta value.
    264                  *
    265                  * Returning `false` from the filter callback will disable the scaling.
    266                  *
    267                  * @since 5.3.0
    268                  *
    269                  * @param int    $threshold     The threshold value in pixels. Default 2560.
    270                  * @param array  $imagesize     {
    271                  *     Indexed array of the image width and height in pixels.
    272                  *
    273                  *     @type int $0 The image width.
    274                  *     @type int $1 The image height.
    275                  * }
    276                  * @param string $file          Full path to the uploaded image file.
    277                  * @param int    $attachment_id Attachment post ID.
    278                  */
    279                 $threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id );
    280 
    281                 // If the original image's dimensions are over the threshold,
    282                 // scale the image and use it as the "full" size.
    283                 if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) {
    284                         $editor = wp_get_image_editor( $file );
     257        // Calculate the primary (first) and additional mime types to generate.
     258        $mime_types_to_generate = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
     259        foreach ( $mime_types_to_generate as $output_index => $output_mime_type ) {
     260                // Populate the top level primary mime type data.
     261                if ( 0 === $output_index ) {
     262                        $image_meta['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $image_meta );
     263                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     264                }
    285265
    286                         if ( is_wp_error( $editor ) ) {
    287                                 // This image cannot be edited.
    288                                 return $image_meta;
    289                         }
     266                // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
     267                if ( 'image/png' !== $imagesize['mime'] ) {
     268                        /**
     269                         * Filters the "BIG image" threshold value.
     270                         *
     271                         * If the original image width or height is above the threshold, it will be scaled down. The threshold is
     272                         * used as max width and max height. The scaled down image will be used as the largest available size, including
     273                         * the `_wp_attached_file` post meta value.
     274                         *
     275                         * Returning `false` from the filter callback will disable the scaling.
     276                         *
     277                         * @since 5.3.0
     278                         *
     279                         * @param int    $threshold     The threshold value in pixels. Default 2560.
     280                         * @param array  $imagesize     {
     281                         *     Indexed array of the image width and height in pixels.
     282                         *
     283                         *     @type int $0 The image width.
     284                         *     @type int $1 The image height.
     285                         * }
     286                         * @param string $file          Full path to the uploaded image file.
     287                         * @param int    $attachment_id Attachment post ID.
     288                         */
     289                        $threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id );
    290290
    291                         // Resize the image.
    292                         $resized = $editor->resize( $threshold, $threshold );
    293                         $rotated = null;
     291                        // If the original image's dimensions are over the threshold,
     292                        // scale the image and use it as the "full" size.
     293                        if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) {
     294                                $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    294295
    295                         // If there is EXIF data, rotate according to EXIF Orientation.
    296                         if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
    297                                 $resized = $editor->maybe_exif_rotate();
    298                                 $rotated = $resized;
    299                         }
     296                                if ( is_wp_error( $editor ) ) {
     297                                        // This image cannot be edited.
     298                                        return $image_meta;
     299                                }
     300                                $editor->set_mime_type( $output_mime_type );
    300301
    301                         if ( ! is_wp_error( $resized ) ) {
    302                                 // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
    303                                 // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
    304                                 $saved = $editor->save( $editor->generate_filename( 'scaled' ) );
     302                                // Resize the image.
     303                                $resized = $editor->resize( $threshold, $threshold );
     304                                $rotated = null;
    305305
    306                                 if ( ! is_wp_error( $saved ) ) {
    307                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     306                                // If there is EXIF data, rotate according to EXIF Orientation.
     307                                if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
     308                                        $resized = $editor->maybe_exif_rotate();
     309                                        $rotated = $resized;
     310                                }
    308311
    309                                         // If the image was rotated update the stored EXIF data.
    310                                         if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
    311                                                 $image_meta['image_meta']['orientation'] = 1;
     312                                if ( ! is_wp_error( $resized ) ) {
     313                                        // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
     314                                        // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
     315                                        $saved = $editor->save( $editor->generate_filename( 'scaled' ), $output_mime_type );
     316
     317                                        if ( ! is_wp_error( $saved ) ) {
     318                                                if ( 0 === $output_index ) {
     319                                                        $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     320                                                }
     321                                                // If the image was rotated update the stored EXIF data.
     322                                                if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
     323                                                        $image_meta['image_meta']['orientation'] = 1;
     324                                                }
     325                                        } else {
     326                                                // TODO: Log errors.
    312327                                        }
    313328                                } else {
    314329                                        // TODO: Log errors.
    315330                                }
    316331                        } else {
    317                                 // TODO: Log errors.
    318                         }
    319                 } elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
    320                         // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
     332                                // Generate additional full sized images.
     333                                if ( $output_index > 0 ) {
     334                                        if ( empty( $image_meta['sources'][ $output_mime_type ] ) ) {
     335                                                $extension = wp_get_default_extension_for_mime_type( $output_mime_type );
     336                                                $saved     = $editor->save( $editor->generate_filename( '', null, $extension ) );
     337                                                if ( ! is_wp_error( $saved ) ) {
     338                                                        $image_meta['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $saved );
     339                                                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     340                                                }
     341                                        }
     342                                }
    321343
    322                         $editor = wp_get_image_editor( $file );
     344                                if ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
     345                                        // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
    323346
    324                         if ( is_wp_error( $editor ) ) {
    325                                 // This image cannot be edited.
    326                                 return $image_meta;
    327                         }
     347                                        $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    328348
    329                         // Rotate the image.
    330                         $rotated = $editor->maybe_exif_rotate();
     349                                        if ( is_wp_error( $editor ) ) {
     350                                                // This image cannot be edited.
     351                                                return $image_meta;
     352                                        }
     353
     354                                        $editor->set_mime_type( $output_mime_type );
     355
     356                                        // Rotate the image.
     357                                        $rotated = $editor->maybe_exif_rotate();
    331358
    332                         if ( true === $rotated ) {
    333                                 // Append `-rotated` to the image file name.
    334                                 $saved = $editor->save( $editor->generate_filename( 'rotated' ) );
     359                                        if ( true === $rotated ) {
     360                                                // Append `-rotated` to the image file name.
     361                                                $saved = $editor->save( $editor->generate_filename( 'rotated' ), $output_mime_type );
    335362
    336                                 if ( ! is_wp_error( $saved ) ) {
    337                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     363                                                if ( ! is_wp_error( $saved ) ) {
     364                                                        $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
    338365
    339                                         // Update the stored EXIF data.
    340                                         if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
    341                                                 $image_meta['image_meta']['orientation'] = 1;
     366                                                        // Update the stored EXIF data.
     367                                                        if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
     368                                                                $image_meta['image_meta']['orientation'] = 1;
     369                                                        }
     370                                                } else {
     371                                                        // TODO: Log errors.
     372                                                }
    342373                                        }
    343                                 } else {
    344                                         // TODO: Log errors.
    345374                                }
    346375                        }
    347376                }
    348377        }
    349378
    350379        /*
    351          * Initial save of the new metadata.
    352          * At this point the file was uploaded and moved to the uploads directory
    353          * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
    354          */
     380        * Initial save of the new metadata.
     381        * At this point the file was uploaded and moved to the uploads directory
     382        * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
     383        */
    355384        wp_update_attachment_metadata( $attachment_id, $image_meta );
    356385
    357386        $new_sizes = wp_get_registered_image_subsizes();
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    372401        return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
    373402}
    374403
     404/**
     405 * Gets a sources array element from a meta.
     406 *
     407 * @since 6.0.0
     408 * @access private
     409 *
     410 * @param array $meta The meta to get the source from.
     411 * @return array The source array element.
     412 */
     413function _wp_get_sources_from_meta( $meta ) {
     414        return array(
     415                'file'     => wp_basename( $meta['file'] ),
     416                'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
     417        );
     418}
     419
    375420/**
    376421 * Low-level function to create image sub-sizes.
    377422 *
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    379424 * Errors are stored in the returned image metadata array.
    380425 *
    381426 * @since 5.3.0
     427 * @since 6.0.0 Support for generating multiple mime type sub-sizes was added.
    382428 * @access private
    383429 *
    384430 * @param array  $new_sizes     Array defining what sizes to create.
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    392438                // Not an image attachment.
    393439                return array();
    394440        }
    395 
    396         // Check if any of the new sizes already exist.
    397         if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
    398                 foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
    399                         /*
    400                          * Only checks "size name" so we don't override existing images even if the dimensions
    401                          * don't match the currently defined size with the same name.
    402                          * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
    403                          */
    404                         if ( array_key_exists( $size_name, $new_sizes ) ) {
    405                                 unset( $new_sizes[ $size_name ] );
     441        // Calculate the primary (first) and additional mime types to generate.
     442        $mime_types_to_generate = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
     443        foreach ( $mime_types_to_generate as $output_index => $output_mime_type ) {
     444                // For the primary image, check if any of the new sizes already exist.
     445                if ( 0 === $output_index && isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
     446                        foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
     447                                /*
     448                                * Only checks "size name" so we don't override existing images even if the dimensions
     449                                * don't match the currently defined size with the same name.
     450                                * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
     451                                */
     452                                if ( array_key_exists( $size_name, $new_sizes ) ) {
     453                                        unset( $new_sizes[ $size_name ] );
     454                                }
    406455                        }
     456                } else {
     457                        $image_meta['sizes'] = array();
    407458                }
    408         } else {
    409                 $image_meta['sizes'] = array();
    410         }
    411459
    412         if ( empty( $new_sizes ) ) {
    413                 // Nothing to do...
    414                 return $image_meta;
    415         }
    416 
    417         /*
    418          * Sort the image sub-sizes in order of priority when creating them.
    419          * This ensures there is an appropriate sub-size the user can access immediately
    420          * even when there was an error and not all sub-sizes were created.
    421          */
    422         $priority = array(
    423                 'medium'       => null,
    424                 'large'        => null,
    425                 'thumbnail'    => null,
    426                 'medium_large' => null,
    427         );
    428 
    429         $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
     460                if ( empty( $new_sizes ) ) {
     461                        // Nothing to do...
     462                        return $image_meta;
     463                }
    430464
    431         $editor = wp_get_image_editor( $file );
     465                /*
     466                * Sort the image sub-sizes in order of priority when creating them.
     467                * This ensures there is an appropriate sub-size the user can access immediately
     468                * even when there was an error and not all sub-sizes were created.
     469                */
     470                $priority = array(
     471                        'medium'       => null,
     472                        'large'        => null,
     473                        'thumbnail'    => null,
     474                        'medium_large' => null,
     475                );
    432476
    433         if ( is_wp_error( $editor ) ) {
    434                 // The image cannot be edited.
    435                 return $image_meta;
    436         }
     477                $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
    437478
    438         // If stored EXIF data exists, rotate the source image before creating sub-sizes.
    439         if ( ! empty( $image_meta['image_meta'] ) ) {
    440                 $rotated = $editor->maybe_exif_rotate();
     479                $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    441480
    442                 if ( is_wp_error( $rotated ) ) {
    443                         // TODO: Log errors.
     481                if ( is_wp_error( $editor ) ) {
     482                        // The image cannot be edited.
     483                        return $image_meta;
    444484                }
    445         }
    446485
    447         if ( method_exists( $editor, 'make_subsize' ) ) {
    448                 foreach ( $new_sizes as $new_size_name => $new_size_data ) {
    449                         $new_size_meta = $editor->make_subsize( $new_size_data );
     486                $editor->set_mime_type( $output_mime_type );
    450487
    451                         if ( is_wp_error( $new_size_meta ) ) {
     488                // If stored EXIF data exists, rotate the source image before creating sub-sizes.
     489                if ( ! empty( $image_meta['image_meta'] ) ) {
     490                        $rotated = $editor->maybe_exif_rotate();
     491
     492                        if ( is_wp_error( $rotated ) ) {
    452493                                // TODO: Log errors.
    453                         } else {
    454                                 // Save the size meta value.
    455                                 $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
    456                                 wp_update_attachment_metadata( $attachment_id, $image_meta );
    457494                        }
    458495                }
    459         } else {
    460                 // Fall back to `$editor->multi_resize()`.
    461                 $created_sizes = $editor->multi_resize( $new_sizes );
    462496
    463                 if ( ! empty( $created_sizes ) ) {
    464                         $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
    465                         wp_update_attachment_metadata( $attachment_id, $image_meta );
     497                if ( method_exists( $editor, 'make_subsize' ) ) {
     498                        foreach ( $new_sizes as $new_size_name => $new_size_data ) {
     499                                $new_size_meta = $editor->make_subsize( $new_size_data );
     500
     501                                if ( is_wp_error( $new_size_meta ) ) {
     502                                        // TODO: Log errors.
     503                                } else {
     504                                        // Save the primary mime type sizes meta value.
     505                                        if ( 0 === $output_index ) {
     506                                                $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     507                                        }
     508                                        // Update the sources array with the new sub-size.
     509                                        $image_meta['sizes'][ $new_size_name ]['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     510                                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     511                                }
     512                        }
     513                } else {
     514                        // Fall back to `$editor->multi_resize()`.
     515                        $created_sizes = $editor->multi_resize( $new_sizes );
     516
     517                        if ( ! empty( $created_sizes ) ) {
     518                                // Save the primary mime type sizes meta value.
     519                                if ( 0 === $output_index ) {
     520                                        $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
     521                                }
     522                                // Update the sources array with the new sub-size.
     523                                $image_meta['sizes'][ $new_size_name ]['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     524                                wp_update_attachment_metadata( $attachment_id, $image_meta );
     525                        }
    466526                }
    467527        }
    468528
    function wp_generate_attachment_metadata( $attachment_id, $file ) { 
    651711         */
    652712        return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'create' );
    653713}
    654 
    655714/**
    656715 * Convert a fraction string to a decimal.
    657716 *
    function _copy_image_file( $attachment_id ) { 
    11431202
    11441203        return $dst_file;
    11451204}
     1205
     1206/**
     1207 * Returns an array with the list of valid mime types that a specific mime type should be converted into.
     1208 * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
     1209 * is considered the primary output type for this image.
     1210 *
     1211 * @since 6.0.0
     1212 *
     1213 * @param $attachment_id int The attachment ID.
     1214 * @return array<string, array<string>> An array of valid mime types, where the key is the source file mime type and the
     1215 *                                      value is one or more mime file types to generate.
     1216 */
     1217function wp_upload_image_mime_transforms( $attachment_id ) {
     1218        $image_mime_transforms = array(
     1219                'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
     1220                'image/webp' => array( 'image/webp', 'image/jpeg' ),
     1221        );
     1222
     1223        /**
     1224         * Filter to the output mime types for a given input mime type.
     1225         *
     1226         * @since 6.0.0
     1227         *
     1228         * @param array $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
     1229         *                                     and the value is one or more mime file types to generate.
     1230         * @param int   $attachment_id         The ID of the attachment where the hook was dispatched.
     1231         */
     1232        return (array) apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
     1233}
     1234
     1235/**
     1236 * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
     1237 *
     1238 * @since 6.0.0
     1239 * @access private
     1240 *
     1241 * @param string $file          Full path to the image file.
     1242 * @param int    $attachment_id Attachment ID to process.
     1243 * @return array<string, array<string>> An array with the primary mime type and the additional mime types.
     1244 */
     1245function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
     1246        $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
     1247        $original_mime_type    = wp_get_image_mime( $file );
     1248        $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
     1249
     1250        // Exclude any output mime types that the system doesn't support.
     1251        $output_mime_types = array_filter(
     1252                $output_mime_types,
     1253                function( $mime_type ) {
     1254                        return wp_image_editor_supports(
     1255                                array(
     1256                                        'mime_type' => $mime_type,
     1257                                )
     1258                        );
     1259                }
     1260        );
     1261
     1262        // Use original mime type as primary mime type, or alternatively the first one.
     1263        $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
     1264        if ( false === $primary_mime_type_key ) {
     1265                $primary_mime_type_key = 0;
     1266        }
     1267        // Split output mime types into primary mime type and additional mime types.
     1268        $additional_mime_types     = $output_mime_types;
     1269        list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
     1270
     1271        // Ensure $primary_mime_type is set.
     1272        if ( empty( $primary_mime_type ) ) {
     1273                $primary_mime_type = $original_mime_type;
     1274        }
     1275        return array_merge(
     1276                array( $primary_mime_type ),
     1277                $additional_mime_types
     1278        );
     1279}
  • src/wp-includes/class-wp-image-editor.php

    diff --git src/wp-includes/class-wp-image-editor.php src/wp-includes/class-wp-image-editor.php
    index 823501eb46..b85f558ec5 100644
    abstract class WP_Image_Editor { 
    1515        protected $file              = null;
    1616        protected $size              = null;
    1717        protected $mime_type         = null;
     18        protected $mime_type_set     = false;
    1819        protected $output_mime_type  = null;
    1920        protected $default_mime_type = 'image/jpeg';
    2021        protected $quality           = false;
    abstract class WP_Image_Editor { 
    336337                        // If no file specified, grab editor's current extension and mime-type.
    337338                        $file_ext  = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
    338339                        $file_mime = $this->mime_type;
     340                        // Favor the file mime extension.
     341                        $ext = $this->get_extension( $file_mime );
     342                        if ( $this->mime_type_set && $ext !== $file_ext ) {
     343                                $new_ext = $ext;
     344                                $mime_type = $file_mime;
     345                        }
    339346                }
    340347
    341348                // Check to see if specified mime-type is the same as type implied by
    342349                // file extension. If so, prefer extension from file.
    343                 if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
     350                if ( ! $this->mime_type_set && ( ! $mime_type || ( $file_mime == $mime_type ) ) ) {
    344351                        $mime_type = $file_mime;
    345352                        $new_ext   = $file_ext;
    346353                }
    abstract class WP_Image_Editor { 
    426433         */
    427434        public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
    428435                // $suffix will be appended to the destination filename, just before the extension.
    429                 if ( ! $suffix ) {
     436                if ( null === $suffix ) {
    430437                        $suffix = $this->get_suffix();
    431438                }
    432439
    abstract class WP_Image_Editor { 
    627634
    628635                return wp_get_default_extension_for_mime_type( $mime_type );
    629636        }
    630 }
    631637
     638        /**
     639         * Set the editor mime type, useful when outputting alternate mime types.
     640         *
     641         * Track that the mime type is set with the mime type set flag.
     642         *
     643         * @since 6.0.0
     644         *
     645         * @param string $mime_type The mime type to set.
     646         */
     647        public function set_mime_type( $mime_type ) {
     648                $this->mime_type     = $mime_type;
     649                $this->mime_type_set = true;
     650        }
     651
     652        /**
     653         * Reset the mime type to the original file mime type.
     654         *
     655         * Reset the mime type set flag.
     656         */
     657        public function reset_mime_type() {
     658                $this->mime_type     = wp_get_image_mime( $this->file );
     659                $this->mime_type_set = false;
     660        }
     661}
  • src/wp-includes/media.php

    diff --git src/wp-includes/media.php src/wp-includes/media.php
    index a0e7ef9c6c..ab72f8fc8c 100644
    function wp_filter_content_tags( $content, $context = null ) { 
    18431843                                $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context );
    18441844                        }
    18451845
     1846                        // Use alternate mime types when specified and available.
     1847                        if ( $attachment_id > 0 ) {
     1848                                $filtered_image = wp_image_use_alternate_mime_types( $filtered_image, $context, $attachment_id );
     1849                        }
     1850
    18461851                        if ( $filtered_image !== $match[0] ) {
    18471852                                $content = str_replace( $match[0], $filtered_image, $content );
    18481853                        }
    function wp_filter_content_tags( $content, $context = null ) { 
    18661871        return $content;
    18671872}
    18681873
     1874/**
     1875 * Use alternate mime type images in the content output when available.
     1876 *
     1877 * @since 6.0.0
     1878 *
     1879 * @param string $image         The HTML `img` tag where the attribute should be added.
     1880 * @param string $context       Additional context to pass to the filters.
     1881 * @param int    $attachment_id The attachment ID.
     1882 * @return string Converted `img` tag with `loading` attribute added.
     1883 */
     1884function wp_image_use_alternate_mime_types( $image, $context, $attachment_id ) {
     1885        $metadata = wp_get_attachment_metadata( $attachment_id );
     1886        if ( empty( $metadata['file'] ) ) {
     1887                return $image;
     1888        }
     1889
     1890        // Only alter images with a `sources` attribute
     1891        if ( empty( $metadata['sources'] ) ) {
     1892                return $image;
     1893        };
     1894
     1895        $target_mimes = array( 'image/webp', 'image/jpeg' );
     1896
     1897        /**
     1898         * Filter the content image mime type output selection and order.
     1899         *
     1900         * When outputting images in the content, the first mime type available will be used.
     1901         *
     1902         * @since 6.0.0
     1903         *
     1904         * @param array  $target_mimes  The image output mime type and order. Default is array( 'image/webp', 'image/jpeg' ).
     1905         * @param int    $attachment_id The attachment ID.
     1906         * @param string $context       Additional context to pass to the filters.
     1907         * @return array The filtered output mime type and order. Return an empty array to skip mime type substitution.
     1908         */
     1909        $target_mimes = apply_filters( 'wp_content_image_mimes', $target_mimes, $attachment_id, $context );
     1910
     1911        if ( false === $target_mimes ) {
     1912                return $image;
     1913        }
     1914
     1915        // Find the appropriate size for the provided URL in the first available mime type.
     1916        foreach ( $target_mimes as $target_mime ) {
     1917                if ( ! isset( $metadata['sources'][ $target_mime ] ) || empty( $metadata['sources'][ $target_mime ]['file'] ) ) {
     1918                        continue;
     1919                }
     1920
     1921                // Go through each image and replace with the first available mime type version.
     1922                foreach ( $metadata['sizes'] as $name => $size_data ) {
     1923                        // Check if size has a file.
     1924                        if ( empty( $size_data['file'] ) ) {
     1925                                continue;
     1926                        }
     1927
     1928                        // Check if size has a source in the desired mime type.
     1929                        if ( empty( $size_data['sources'][ $target_mime ]['file'] ) ) {
     1930                                continue;
     1931                        }
     1932                        $target_file = $size_data['sources'][ $target_mime ]['file'];
     1933
     1934                        // Replace the existing output image for this size.
     1935                        $src_filename = wp_basename( $size_data['file'] );
     1936
     1937                        // This is the same as the file we want to replace nothing to do here.
     1938                        if ( $target_file === $src_filename ) {
     1939                                continue;
     1940                        }
     1941
     1942                        // Found a match, replace with the new filename and stop searching.
     1943                        $image = str_replace( $src_filename, $size_data['sources'][ $target_mime ]['file'], $image );
     1944                        continue;
     1945                }
     1946
     1947                // Handle full size image replacement.
     1948                $src_filename = wp_basename( $metadata['file'] );
     1949
     1950                // This is the same as the file we want to replace nothing else to do here.
     1951                if ( $metadata['sources'][ $target_mime ]['file'] === $src_filename ) {
     1952                        return $image;
     1953                }
     1954
     1955                $image = str_replace( $src_filename, $metadata['sources'][ $target_mime ]['file'], $image );
     1956        }
     1957        return $image;
     1958}
     1959
    18691960/**
    18701961 * Adds `loading` attribute to an `img` HTML tag.
    18711962 *
  • src/wp-includes/post.php

    diff --git src/wp-includes/post.php src/wp-includes/post.php
    index 3906e21f7c..4c1aa0327b 100644
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    64806480                                        $deleted = false;
    64816481                                }
    64826482                        }
     6483                        // Check for alternate size mime types in the sizeinfo['sources'] array to delete.
     6484                        if ( isset( $sizeinfo['sources'] ) && is_array( $sizeinfo['sources'] ) && count( $sizeinfo['sources'] ) > 1 ) {
     6485                                $sources = $sizeinfo['sources'];
     6486                                array_shift( $sources );
     6487                                foreach ( $sources as $mime => $properties ) {
     6488                                        if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
     6489                                                continue;
     6490                                        }
     6491
     6492                                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6493                                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6494                                                $deleted = false;
     6495                                        }
     6496                                }
     6497                        }
    64836498                }
    64846499        }
    64856500
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    64996514                }
    65006515        }
    65016516
     6517        // Check for alternate full size mime types in the root sources array to delete.
     6518        if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
     6519                $sources = $meta['sources'];
     6520                array_shift( $sources );
     6521                foreach ( $sources as $mime => $properties ) {
     6522                        if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
     6523                                continue;
     6524                        }
     6525
     6526                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6527                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6528                                $deleted = false;
     6529                        }
     6530                }
     6531        }
     6532
    65026533        if ( is_array( $backup_sizes ) ) {
    65036534                $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
    65046535
  • tests/phpunit/tests/image/editor.php

    diff --git tests/phpunit/data/images/test-image.jpeg tests/phpunit/data/images/test-image.jpeg
    new file mode 100644
    index 0000000000..534aac1d6b
    Binary files /dev/null and tests/phpunit/data/images/test-image.jpeg differ
    diff --git tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editor.php
    index 487dad0664..06d31e83f1 100644
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    361361                        ),
    362362                );
    363363        }
     364        /**
     365         * Create the original image mime type when the image is uploaded
     366         *
     367         * @dataProvider provider_image_with_default_behaviors_during_upload
     368         *
     369         * @since 6.0.0
     370         */
     371        public function it_should_create_the_original_image_mime_type_when_the_image_is_uploaded( $file_location, $expected_mime, $targeted_mime ) {
     372                $attachment_id = $this->factory->attachment->create_upload_object( $file_location );
     373
     374                $metadata = wp_get_attachment_metadata( $attachment_id );
     375
     376                $this->assertIsArray( $metadata );
     377                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     378                        $this->assertArrayHasKey( 'sources', $properties );
     379                        $this->assertIsArray( $properties['sources'] );
     380                        $this->assertArrayHasKey( $expected_mime, $properties['sources'] );
     381                        $this->assertArrayHasKey( 'filesize', $properties['sources'][ $expected_mime ] );
     382                        $this->assertArrayHasKey( 'file', $properties['sources'][ $expected_mime ] );
     383                        $this->assertArrayHasKey( $targeted_mime, $properties['sources'] );
     384                        $this->assertArrayHasKey( 'filesize', $properties['sources'][ $targeted_mime ] );
     385                        $this->assertArrayHasKey( 'file', $properties['sources'][ $targeted_mime ] );
     386                }
     387        }
     388
     389        public function provider_image_with_default_behaviors_during_upload() {
     390                yield 'JPEG image' => array(
     391                        DIR_TESTDATA . '/images/test-image.jpg',
     392                        'image/jpeg',
     393                        'image/webp',
     394                );
     395
     396                yield 'WebP image' => array(
     397                        DIR_TESTDATA . '/images/webp-lossy.webp',
     398                        'image/webp',
     399                        'image/jpeg',
     400                );
     401        }
     402
     403        /**
     404         * Not create the sources property if no transform is provided
     405         *
     406         * @since 6.0.0
     407         */
     408        public function it_should_not_create_the_sources_property_if_no_transform_is_provided() {
     409                add_filter( 'webp_uploads_supported_image_mime_transforms', '__return_empty_array' );
     410
     411                $attachment_id = $this->factory->attachment->create_upload_object(
     412                        DIR_TESTDATA . '/images/test-image.jpg'
     413                );
     414
     415                $metadata = wp_get_attachment_metadata( $attachment_id );
     416
     417                $this->assertIsArray( $metadata );
     418                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     419                        $this->assertArrayNotHasKey( 'sources', $properties );
     420                }
     421        }
     422
     423        /**
     424         * Create the sources property when no transform is available
     425         *
     426         * @since 6.0.0
     427         */
     428        public function it_should_create_the_sources_property_when_no_transform_is_available() {
     429                add_filter(
     430                        'webp_uploads_supported_image_mime_transforms',
     431                        function () {
     432                                return array( 'image/jpeg' => array() );
     433                        }
     434                );
     435
     436                $attachment_id = $this->factory->attachment->create_upload_object(
     437                        DIR_TESTDATA . '/images/test-image.jpg'
     438                );
     439
     440                $metadata = wp_get_attachment_metadata( $attachment_id );
     441
     442                $this->assertIsArray( $metadata );
     443                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     444                        $this->assertArrayHasKey( 'sources', $properties );
     445                        $this->assertIsArray( $properties['sources'] );
     446                        $this->assertArrayHasKey( 'image/jpeg', $properties['sources'] );
     447                        $this->assertArrayHasKey( 'filesize', $properties['sources']['image/jpeg'] );
     448                        $this->assertArrayHasKey( 'file', $properties['sources']['image/jpeg'] );
     449                        $this->assertArrayNotHasKey( 'image/webp', $properties['sources'] );
     450                }
     451        }
     452
     453        /**
     454         * Not create the sources property if the mime is not specified on the transforms images
     455         *
     456         * @since 6.0.0
     457         */
     458        public function it_should_not_create_the_sources_property_if_the_mime_is_not_specified_on_the_transforms_images() {
     459                add_filter(
     460                        'webp_uploads_supported_image_mime_transforms',
     461                        function () {
     462                                return array( 'image/jpeg' => array() );
     463                        }
     464                );
     465
     466                $attachment_id = $this->factory->attachment->create_upload_object(
     467                        DIR_TESTDATA . '/images/webp-lossy.webp'
     468                );
     469
     470                $metadata = wp_get_attachment_metadata( $attachment_id );
     471
     472                $this->assertIsArray( $metadata );
     473                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     474                        $this->assertArrayNotHasKey( 'sources', $properties );
     475                }
     476        }
     477
     478        /**
     479         * Create a WebP version with all the required properties
     480         *
     481         * @since 6.0.0
     482         */
     483        public function it_should_create_a_webp_version_with_all_the_required_properties() {
     484                $attachment_id = $this->factory->attachment->create_upload_object(
     485                        DIR_TESTDATA . '/images/test-image.jpg'
     486                );
     487
     488                $metadata = wp_get_attachment_metadata( $attachment_id );
     489                $this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] );
     490                $this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] );
     491                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     492                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     493                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] );
     494                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     495                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     496                $this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     497                $this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     498        }
     499
     500        /**
     501         * Remove `scaled` suffix from the generated filename
     502         *
     503         * @since 6.0.0
     504         */
     505        public function it_should_remove_scaled_suffix_from_the_generated_filename() {
     506                // The leafs image is 1080 pixels wide with this filter we ensure a -scaled version is created.
     507                add_filter(
     508                        'big_image_size_threshold',
     509                        function () {
     510                                return 850;
     511                        }
     512                );
     513
     514                $attachment_id = $this->factory->attachment->create_upload_object(
     515                        DIR_TESTDATA . '/images/test-image.jpg'
     516                );
     517                $metadata      = wp_get_attachment_metadata( $attachment_id );
     518                $this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) );
     519                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] );
     520                $this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     521                $this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     522        }
     523
     524        /**
     525         * Remove the generated webp images when the attachment is deleted
     526         *
     527         * @since 6.0.0
     528         */
     529        public function it_should_remove_the_generated_webp_images_when_the_attachment_is_deleted() {
     530                // Make sure no editor is available.
     531                $attachment_id = $this->factory->attachment->create_upload_object(
     532                        DIR_TESTDATA . '/images/test-image.jpg'
     533                );
     534
     535                $file    = get_attached_file( $attachment_id, true );
     536                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     537
     538                $this->assertIsString( $file );
     539                $this->assertFileExists( $file );
     540
     541                $metadata = wp_get_attachment_metadata( $attachment_id );
     542                $sizes    = array( 'thumbnail', 'medium' );
     543
     544                foreach ( $sizes as $size_name ) {
     545                        $this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] );
     546                        $this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] );
     547                        $this->assertFileExists(
     548                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     549                        );
     550                }
     551
     552                wp_delete_attachment( $attachment_id );
     553
     554                foreach ( $sizes as $size_name ) {
     555                        $this->assertFileDoesNotExist(
     556                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     557                        );
     558                }
     559        }
     560
     561        /**
     562         * Remove the attached WebP version if the attachment is force deleted but empty trash day is not defined
     563         *
     564         * @since 6.0.0
     565         */
     566        public function it_should_remove_the_attached_webp_version_if_the_attachment_is_force_deleted_but_empty_trash_day_is_not_defined() {
     567                // Make sure no editor is available.
     568                $attachment_id = $this->factory->attachment->create_upload_object(
     569                        DIR_TESTDATA . '/images/test-image.jpg'
     570                );
     571
     572                $file    = get_attached_file( $attachment_id, true );
     573                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     574
     575                $this->assertIsString( $file );
     576                $this->assertFileExists( $file );
     577
     578                $metadata = wp_get_attachment_metadata( $attachment_id );
     579
     580                $this->assertFileExists(
     581                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     582                );
     583
     584                wp_delete_attachment( $attachment_id, true );
     585
     586                $this->assertFileDoesNotExist(
     587                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     588                );
     589        }
     590
     591        /**
     592         * Remove the WebP version of the image if the image is force deleted and empty trash days is set to zero
     593         *
     594         * @since 6.0.0
     595         */
     596        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() {
     597                // Make sure no editor is available.
     598                $attachment_id = $this->factory->attachment->create_upload_object(
     599                        DIR_TESTDATA . '/images/test-image.jpg'
     600                );
     601
     602                $file    = get_attached_file( $attachment_id, true );
     603                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     604
     605                $this->assertIsString( $file );
     606                $this->assertFileExists( $file );
     607
     608                $metadata = wp_get_attachment_metadata( $attachment_id );
     609
     610                $this->assertFileExists(
     611                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     612                );
     613
     614                define( 'EMPTY_TRASH_DAYS', 0 );
     615
     616                wp_delete_attachment( $attachment_id, true );
     617
     618                $this->assertFileDoesNotExist(
     619                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     620                );
     621        }
     622
     623        /**
     624         * Avoid the change of URLs of images that are not part of the media library
     625         *
     626         * @group webp_uploads_update_image_references
     627         *
     628         * @since 6.0.0
     629         */
     630        public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
     631                $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>';
     632
     633                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     634        }
     635
     636        /**
     637         * Avoid replacing not existing attachment IDs
     638         *
     639         * @group webp_uploads_update_image_references
     640         *
     641         * @since 6.0.0
     642         */
     643        public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
     644                $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>';
     645
     646                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     647        }
     648
     649        /**
     650         * Prevent replacing a WebP image
     651         *
     652         * @group webp_uploads_update_image_references
     653         *
     654         * @since 6.0.0
     655         */
     656        public function it_should_prevent_replacing_a_webp_image() {
     657                $attachment_id = $this->factory->attachment->create_upload_object(
     658                        DIR_TESTDATA . '/images/webp-lossy.webp'
     659                );
     660
     661                $tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     662
     663                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     664        }
     665
     666        /**
     667         * Prevent replacing a jpg image if the image does not have the target class name
     668         *
     669         * @since 6.0.0
     670         */
     671        public function it_should_prevent_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
     672                $attachment_id = $this->factory->attachment->create_upload_object(
     673                        DIR_TESTDATA . '/images/test-image.jpg'
     674                );
     675
     676                $tag = wp_get_attachment_image( $attachment_id, 'medium' );
     677
     678                $this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
     679        }
     680
     681        /**
     682         * Replace the references to a JPG image to a WebP version
     683         *
     684         * @dataProvider provider_replace_images_with_different_extensions
     685         * @group webp_uploads_update_image_references
     686         *
     687         * @since 6.0.0
     688         */
     689        public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
     690                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     691
     692                $tag          = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     693                $expected_tag = $tag;
     694                $metadata     = wp_get_attachment_metadata( $attachment_id );
     695                foreach ( $metadata['sizes'] as $size => $properties ) {
     696                        $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
     697                }
     698
     699                $this->assertNotEmpty( $expected_tag );
     700                $this->assertNotSame( $tag, $expected_tag );
     701                $this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     702        }
     703
     704        public function provider_replace_images_with_different_extensions() {
     705                yield 'An image with a .jpg extension' => array( DIR_TESTDATA . '/images/test-image.jpg' );
     706                yield 'An image with a .jpeg extension' => array( DIR_TESTDATA . '/images/test-image.jpeg' );
     707        }
     708
     709        /**
     710         * Contain the full image size from the original mime
     711         *
     712         * @group webp_uploads_update_image_references
     713         *
     714         * @since 6.0.0
     715         */
     716        public function it_should_contain_the_full_image_size_from_the_original_mime() {
     717                $attachment_id = $this->factory->attachment->create_upload_object(
     718                        DIR_TESTDATA . '/images/test-image.jpg'
     719                );
     720
     721                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     722
     723                $expected = array(
     724                        'ext'  => 'jpg',
     725                        'type' => 'image/jpeg',
     726                );
     727                $this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
     728                $this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     729        }
     730
     731        /**
     732         * Prevent replacing an image with no available sources
     733         *
     734         * @group webp_uploads_update_image_references
     735         *
     736         * @since 6.0.0
     737         */
     738        public function it_should_prevent_replacing_an_image_with_no_available_sources() {
     739                add_filter( 'webp_uploads_supported_image_mime_transforms', '__return_empty_array' );
     740
     741                $attachment_id = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.jpg' );
     742
     743                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     744                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     745        }
     746
     747        /**
     748         * Prevent update not supported images with no available sources
     749         *
     750         * @dataProvider data_provider_not_supported_webp_images
     751         * @group webp_uploads_update_image_references
     752         *
     753         * @since 6.0.0
     754         */
     755        public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
     756                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     757
     758                $this->assertIsNumeric( $attachment_id );
     759                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     760
     761                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     762        }
     763
     764        public function data_provider_not_supported_webp_images() {
     765                yield 'PNG image' => array( DIR_TESTDATA . '/images/test-image.png' );
     766                yield 'GIFT image' => array( DIR_TESTDATA . '/images/test-image.gif' );
     767        }
     768
     769
    364770
    365771}
  • tests/phpunit/tests/image/functions.php

    diff --git tests/phpunit/tests/image/functions.php tests/phpunit/tests/image/functions.php
    index c02862b3e7..39bf09cf0c 100644
    class Tests_Image_Functions extends WP_UnitTestCase { 
    480480                $this->assertNotEmpty( $attachment_id );
    481481
    482482                $temp_dir = get_temp_dir();
    483 
    484483                $metadata = wp_generate_attachment_metadata( $attachment_id, $test_file );
    485484
    486485                $expected = array(
    class Tests_Image_Functions extends WP_UnitTestCase { 
    498497                                        'height'    => 300,
    499498                                        'mime-type' => 'image/jpeg',
    500499                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     500                                        'sources'   => array(
     501                                                'image/jpeg' => array(
     502                                                        'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
     503                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     504
     505                                                ),
     506                                        ),
    501507                                ),
    502508                                'large'     => array(
    503509                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    505511                                        'height'    => 1024,
    506512                                        'mime-type' => 'image/jpeg',
    507513                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     514                                        'sources'   => array(
     515                                                'image/jpeg' => array(
     516                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     517                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     518                                                ),
     519                                        ),
    508520                                ),
    509521                                'thumbnail' => array(
    510522                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    512524                                        'height'    => 150,
    513525                                        'mime-type' => 'image/jpeg',
    514526                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     527                                        'sources'   => array(
     528                                                'image/jpeg' => array(
     529                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     530                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     531                                                ),
     532                                        ),
    515533                                ),
    516534                        ),
    517535                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    557575                $this->assertNotEmpty( $attachment_id );
    558576
    559577                $temp_dir = get_temp_dir();
    560 
    561578                $metadata = wp_generate_attachment_metadata( $attachment_id, $test_file );
    562579
    563580                $expected = array(
    class Tests_Image_Functions extends WP_UnitTestCase { 
    575592                                        'height'    => 300,
    576593                                        'mime-type' => 'image/jpeg',
    577594                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     595                                        'sources'   => array(
     596                                                'image/jpeg' => array(
     597                                                        'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
     598                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     599                                                ),
     600                                        ),
    578601                                ),
    579602                                'large'     => array(
    580603                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    582605                                        'height'    => 1024,
    583606                                        'mime-type' => 'image/jpeg',
    584607                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     608                                        'sources'   => array(
     609                                                'image/jpeg' => array(
     610                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     611                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     612                                                ),
     613                                        ),
     614
    585615                                ),
    586616                                'thumbnail' => array(
    587617                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    589619                                        'height'    => 150,
    590620                                        'mime-type' => 'image/jpeg',
    591621                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     622                                        'sources'   => array(
     623                                                'image/jpeg' => array(
     624                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     625                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     626                                                ),
     627                                        ),
    592628                                ),
    593629                        ),
    594630                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    642678                        'height'    => 100,
    643679                        'mime-type' => 'image/jpeg',
    644680                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     681                        'sources'   => array(
     682                                'image/jpeg' => array(
     683                                        'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
     684                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     685                                ),
     686                        ),
    645687                );
    646688
    647689                // Different environments produce slightly different filesize results.
  • tests/phpunit/tests/media.php

    diff --git tests/phpunit/tests/media.php tests/phpunit/tests/media.php
    index 5d92d9718b..2e9e2173bc 100644
    EOF; 
    22522252                // Do not add width, height, and loading.
    22532253                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    22542254                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
     2255                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22552256
    22562257                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    22572258
    22582259                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    22592260                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
     2261                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2262
    22602263        }
    22612264
    22622265        /**
    EOF; 
    22882291                $img = get_image_tag( self::$large_id, '', '', '', 'medium' );
    22892292                $img = wp_img_tag_add_loading_attr( $img, 'test' );
    22902293                $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
     2294                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22912295
    22922296                // The content filter should return the image unchanged.
    22932297                $this->assertSame( $img, wp_filter_content_tags( $img ) );
     2298
     2299                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22942300        }
    22952301
    22962302        /**
    EOF; 
    23432349         * @requires function imagejpeg
    23442350         */
    23452351        public function test_wp_filter_content_tags_schemes() {
     2352                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    23462353                $image_meta = wp_get_attachment_metadata( self::$large_id );
    23472354                $size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
    23482355
    EOF; 
    23872394                $actual = wp_filter_content_tags( $unfiltered );
    23882395
    23892396                $this->assertSame( $expected, $actual );
     2397                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    23902398        }
    23912399
    23922400        /**
    EOF; 
    28042812         * @requires function imagejpeg
    28052813         */
    28062814        public function test_wp_filter_content_tags_width_height() {
     2815                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2816
    28072817                $image_meta = wp_get_attachment_metadata( self::$large_id );
    28082818                $size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
    28092819
    EOF; 
    28372847                // Do not add loading, srcset, and sizes.
    28382848                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
    28392849                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2850                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    28402851
    28412852                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    28422853
    28432854                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
    28442855                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2856                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    28452857        }
    28462858
    28472859        /**
    EOF; 
    28952907                // Do not add width, height, srcset, and sizes.
    28962908                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    28972909                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2910                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    28982911
    28992912                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    29002913
    29012914                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    29022915                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2916                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    29032917        }
    29042918
    29052919        /**
    EOF; 
    29272941                // Enable globally for all tags.
    29282942                add_filter( 'wp_lazy_loading_enabled', '__return_true' );
    29292943
     2944                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2945
    29302946                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    29312947                remove_filter( 'wp_lazy_loading_enabled', '__return_true' );
    29322948                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2949                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2950
    29332951        }
    29342952
    29352953        /**
    EOF; 
    29532971                // Disable globally for all tags.
    29542972                add_filter( 'wp_lazy_loading_enabled', '__return_false' );
    29552973
     2974                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2975
    29562976                $this->assertSame( $content, wp_filter_content_tags( $content ) );
    29572977                remove_filter( 'wp_lazy_loading_enabled', '__return_false' );
    29582978                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2979                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    29592980        }
    29602981
    29612982        /**
    EOF; 
    33813402         */
    33823403        function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
    33833404                global $wp_query, $wp_the_query;
     3405                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    33843406
    33853407                $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
    33863408                $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
    EOF; 
    34153437                        $content_filtered = wp_filter_content_tags( $content_unfiltered, 'the_content' );
    34163438                        remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
    34173439                }
     3440                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    34183441
    34193442                // After filtering, the first image should not be lazy-loaded while the other ones should be.
    34203443                $this->assertSame( $content_expected, $content_filtered );