Make WordPress Core

Ticket #55443: 55443.3.diff

File 55443.3.diff, 56.4 KB (added by adamsilverstein, 3 years ago)
  • 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..b20a509ccc 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         * Filters the "BIG image" threshold value.
     259         *
     260         * If the original image width or height is above the threshold, it will be scaled down. The threshold is
     261         * used as max width and max height. The scaled down image will be used as the largest available size, including
     262         * the `_wp_attached_file` post meta value.
     263         *
     264         * Returning `false` from the filter callback will disable the scaling.
     265         *
     266         * @since 5.3.0
     267         *
     268         * @param int    $threshold     The threshold value in pixels. Default 2560.
     269         * @param array  $imagesize     {
     270         *     Indexed array of the image width and height in pixels.
     271         *
     272         *     @type int $0 The image width.
     273         *     @type int $1 The image height.
     274         * }
     275         * @param string $file          Full path to the uploaded image file.
     276         * @param int    $attachment_id Attachment post ID.
     277         */
     278        $threshold = (int) apply_filters( 'big_image_size_threshold', 2560, $imagesize, $file, $attachment_id );
    257279
    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        $over_threshold = $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold );
    280281
    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 );
     282        // Calculate the primary (first) and additional mime types to generate.
     283        $mime_types_to_generate = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
     284        foreach ( $mime_types_to_generate as $output_index => $output_mime_type ) {
     285                // Populate the top level primary mime type data.
     286                if ( 0 === $output_index ) {
     287                        $image_meta['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $image_meta );
     288                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     289                }
     290
     291                // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
     292                if ( 'image/png' !== $imagesize['mime'] ) {
     293                        $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    285294
    286295                        if ( is_wp_error( $editor ) ) {
    287296                                // This image cannot be edited.
    288                                 return $image_meta;
     297                                continue;
    289298                        }
     299                        $editor->set_mime_type( $output_mime_type );
    290300
    291                         // Resize the image.
    292                         $resized = $editor->resize( $threshold, $threshold );
    293                         $rotated = null;
    294 
    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                         }
     301                        // If the original image's dimensions are over the threshold,
     302                        // scale the image and use it as the "full" size.
     303                        if ( $over_threshold ) {
    300304
    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' ) );
     305                                // Resize the image.
     306                                $resized = $editor->resize( $threshold, $threshold );
     307                                $rotated = null;
    305308
    306                                 if ( ! is_wp_error( $saved ) ) {
    307                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     309                                // If there is EXIF data, rotate according to EXIF Orientation.
     310                                if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
     311                                        $resized = $editor->maybe_exif_rotate();
     312                                        $rotated = $resized;
     313                                }
    308314
    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;
     315                                if ( ! is_wp_error( $resized ) ) {
     316                                        // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
     317                                        // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
     318                                        $saved = $editor->save( $editor->generate_filename( 'scaled' ), $output_mime_type );
     319
     320                                        if ( ! is_wp_error( $saved ) ) {
     321                                                if ( 0 === $output_index ) {
     322                                                        $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     323                                                }
     324                                                // If the image was rotated update the stored EXIF data.
     325                                                if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
     326                                                        $image_meta['image_meta']['orientation'] = 1;
     327                                                }
     328                                        } else {
     329                                                // TODO: Log errors.
    312330                                        }
    313331                                } else {
    314332                                        // TODO: Log errors.
    315333                                }
    316334                        } 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.
     335                                // Generate additional full sized images.
     336                                if ( $output_index > 0 ) {
     337                                        if ( empty( $image_meta['sources'][ $output_mime_type ] ) ) {
     338                                                $extension = wp_get_default_extension_for_mime_type( $output_mime_type );
     339                                                $saved     = $editor->save( $editor->generate_filename( '', null, $extension ), $output_mime_type );
     340                                                if ( ! is_wp_error( $saved ) ) {
     341                                                        $image_meta['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $saved );
     342                                                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     343                                                }
     344                                        }
     345                                }
    321346
    322                         $editor = wp_get_image_editor( $file );
     347                                if ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
     348                                        // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
    323349
    324                         if ( is_wp_error( $editor ) ) {
    325                                 // This image cannot be edited.
    326                                 return $image_meta;
    327                         }
     350                                        $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    328351
    329                         // Rotate the image.
    330                         $rotated = $editor->maybe_exif_rotate();
     352                                        if ( is_wp_error( $editor ) ) {
     353                                                // This image cannot be edited.
     354                                                return $image_meta;
     355                                        }
    331356
    332                         if ( true === $rotated ) {
    333                                 // Append `-rotated` to the image file name.
    334                                 $saved = $editor->save( $editor->generate_filename( 'rotated' ) );
     357                                        $editor->set_mime_type( $output_mime_type );
    335358
    336                                 if ( ! is_wp_error( $saved ) ) {
    337                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     359                                        // Rotate the image.
     360                                        $rotated = $editor->maybe_exif_rotate();
    338361
    339                                         // Update the stored EXIF data.
    340                                         if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
    341                                                 $image_meta['image_meta']['orientation'] = 1;
     362                                        if ( true === $rotated ) {
     363                                                // Append `-rotated` to the image file name.
     364                                                $saved = $editor->save( $editor->generate_filename( 'rotated' ), $output_mime_type );
     365
     366                                                if ( ! is_wp_error( $saved ) ) {
     367                                                        $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
     368
     369                                                        // Update the stored EXIF data.
     370                                                        if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
     371                                                                $image_meta['image_meta']['orientation'] = 1;
     372                                                        }
     373                                                } else {
     374                                                        // TODO: Log errors.
     375                                                }
    342376                                        }
    343                                 } else {
    344                                         // TODO: Log errors.
    345377                                }
    346378                        }
    347379                }
    348380        }
    349381
    350382        /*
    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          */
     383        * Initial save of the new metadata.
     384        * At this point the file was uploaded and moved to the uploads directory
     385        * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
     386        */
    355387        wp_update_attachment_metadata( $attachment_id, $image_meta );
    356388
    357389        $new_sizes = wp_get_registered_image_subsizes();
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    372404        return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
    373405}
    374406
     407/**
     408 * Gets a sources array element from a meta.
     409 *
     410 * @since 6.0.0
     411 * @access private
     412 *
     413 * @param array $meta The meta to get the source from.
     414 * @return array The source array element.
     415 */
     416function _wp_get_sources_from_meta( $meta ) {
     417        return array(
     418                'file'     => wp_basename( $meta['file'] ),
     419                'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
     420        );
     421}
     422
    375423/**
    376424 * Low-level function to create image sub-sizes.
    377425 *
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    379427 * Errors are stored in the returned image metadata array.
    380428 *
    381429 * @since 5.3.0
     430 * @since 6.0.0 Support for generating multiple mime type sub-sizes was added.
    382431 * @access private
    383432 *
    384433 * @param array  $new_sizes     Array defining what sizes to create.
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    392441                // Not an image attachment.
    393442                return array();
    394443        }
    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 ] );
     444        // Calculate the primary (first) and additional mime types to generate.
     445        $mime_types_to_generate = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
     446        foreach ( $mime_types_to_generate as $output_index => $output_mime_type ) {
     447                // For the primary image, check if any of the new sizes already exist.
     448                if ( 0 === $output_index ) {
     449                        if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
     450                                foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
     451                                        /*
     452                                        * Only checks "size name" so we don't override existing images even if the dimensions
     453                                        * don't match the currently defined size with the same name.
     454                                        * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
     455                                        */
     456                                        if ( array_key_exists( $size_name, $new_sizes ) ) {
     457                                                unset( $new_sizes[ $size_name ] );
     458                                        }
     459                                }
     460                        } else {
     461                                $image_meta['sizes'] = array();
    406462                        }
    407463                }
    408         } else {
    409                 $image_meta['sizes'] = array();
    410         }
    411 
    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         );
    428464
    429         $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
     465                if ( empty( $new_sizes ) ) {
     466                        // Nothing to do...
     467                        return $image_meta;
     468                }
    430469
    431         $editor = wp_get_image_editor( $file );
     470                /*
     471                * Sort the image sub-sizes in order of priority when creating them.
     472                * This ensures there is an appropriate sub-size the user can access immediately
     473                * even when there was an error and not all sub-sizes were created.
     474                */
     475                $priority = array(
     476                        'medium'       => null,
     477                        'large'        => null,
     478                        'thumbnail'    => null,
     479                        'medium_large' => null,
     480                );
    432481
    433         if ( is_wp_error( $editor ) ) {
    434                 // The image cannot be edited.
    435                 return $image_meta;
    436         }
     482                $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
    437483
    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();
     484                $editor = wp_get_image_editor( $file, array( 'mime_type' => $output_mime_type ) );
    441485
    442                 if ( is_wp_error( $rotated ) ) {
    443                         // TODO: Log errors.
     486                if ( is_wp_error( $editor ) ) {
     487                        // The image cannot be edited.
     488                        return $image_meta;
    444489                }
    445         }
    446490
    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 );
     491                $editor->set_mime_type( $output_mime_type );
    450492
    451                         if ( is_wp_error( $new_size_meta ) ) {
     493                // If stored EXIF data exists, rotate the source image before creating sub-sizes.
     494                if ( ! empty( $image_meta['image_meta'] ) ) {
     495                        $rotated = $editor->maybe_exif_rotate();
     496
     497                        if ( is_wp_error( $rotated ) ) {
    452498                                // 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 );
    457499                        }
    458500                }
    459         } else {
    460                 // Fall back to `$editor->multi_resize()`.
    461                 $created_sizes = $editor->multi_resize( $new_sizes );
    462501
    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 );
     502                if ( method_exists( $editor, 'make_subsize' ) ) {
     503                        foreach ( $new_sizes as $new_size_name => $new_size_data ) {
     504                                $new_size_meta = $editor->make_subsize( $new_size_data );
     505
     506                                if ( is_wp_error( $new_size_meta ) ) {
     507                                        // TODO: Log errors.
     508                                } else {
     509                                        // Save the primary mime type sizes meta value.
     510                                        if ( 0 === $output_index ) {
     511                                                $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     512                                        }
     513                                        // Update the sources array with the new sub-size.
     514                                        $image_meta['sizes'][ $new_size_name ]['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     515                                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     516                                }
     517                        }
     518                } else {
     519                        // Fall back to `$editor->multi_resize()`.
     520                        $created_sizes = $editor->multi_resize( $new_sizes );
     521
     522                        if ( ! empty( $created_sizes ) ) {
     523                                // Save the primary mime type sizes meta value.
     524                                if ( 0 === $output_index ) {
     525                                        $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
     526                                }
     527                                // Update the sources array with the new sub-size.
     528                                $image_meta['sizes'][ $new_size_name ]['sources'][ $output_mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     529                                wp_update_attachment_metadata( $attachment_id, $image_meta );
     530                        }
    466531                }
    467532        }
    468533
    function wp_generate_attachment_metadata( $attachment_id, $file ) { 
    651716         */
    652717        return apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'create' );
    653718}
    654 
    655719/**
    656720 * Convert a fraction string to a decimal.
    657721 *
    function _copy_image_file( $attachment_id ) { 
    11431207
    11441208        return $dst_file;
    11451209}
     1210
     1211/**
     1212 * Returns an array with the list of valid mime types that a specific mime type should be converted into.
     1213 * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
     1214 * is considered the primary output type for this image.
     1215 *
     1216 * @since 6.0.0
     1217 *
     1218 * @param $attachment_id int The attachment ID.
     1219 * @return array<string, array<string>> An array of valid mime types, where the key is the source file mime type and the
     1220 *                                      value is one or more mime file types to generate.
     1221 */
     1222function wp_upload_image_mime_transforms( $attachment_id ) {
     1223        $image_mime_transforms = array(
     1224                'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
     1225                'image/webp' => array( 'image/webp', 'image/jpeg' ),
     1226        );
     1227
     1228        /**
     1229         * Filter to the output mime types for a given input mime type.
     1230         *
     1231         * @since 6.0.0
     1232         *
     1233         * @param array $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
     1234         *                                     and the value is one or more mime file types to generate.
     1235         * @param int   $attachment_id         The ID of the attachment where the hook was dispatched.
     1236         */
     1237        return (array) apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
     1238}
     1239
     1240/**
     1241 * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
     1242 *
     1243 * @since 6.0.0
     1244 * @access private
     1245 *
     1246 * @param string $file          Full path to the image file.
     1247 * @param int    $attachment_id Attachment ID to process.
     1248 * @return array<string, array<string>> An array with the primary mime type and the additional mime types.
     1249 */
     1250function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
     1251        $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
     1252        $original_mime_type    = wp_get_image_mime( $file );
     1253        $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
     1254
     1255        // Exclude any output mime types that the system doesn't support.
     1256        $output_mime_types = array_filter(
     1257                $output_mime_types,
     1258                function( $mime_type ) {
     1259                        return wp_image_editor_supports(
     1260                                array(
     1261                                        'mime_type' => $mime_type,
     1262                                )
     1263                        );
     1264                }
     1265        );
     1266
     1267        // Use original mime type as primary mime type, or alternatively the first one.
     1268        $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
     1269        if ( false === $primary_mime_type_key ) {
     1270                $primary_mime_type_key = 0;
     1271        }
     1272        // Split output mime types into primary mime type and additional mime types.
     1273        $additional_mime_types     = $output_mime_types;
     1274        list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
     1275
     1276        // Ensure $primary_mime_type is set.
     1277        if ( empty( $primary_mime_type ) ) {
     1278                $primary_mime_type = $original_mime_type;
     1279        }
     1280        return array_merge(
     1281                array( $primary_mime_type ),
     1282                $additional_mime_types
     1283        );
     1284}
  • 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..b0a14a4800 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 { 
    418425         * Builds an output filename based on current file, and adding proper suffix
    419426         *
    420427         * @since 3.5.0
     428         * @since 6.0.0 Skips adding a suffix when set to an empty string.
    421429         *
    422          * @param string $suffix
     430         * @param string $suffix Optional. Suffix to add to the filename. Passing null
     431         *                       will result in a 'widthxheight' suffix. Passing
     432         *                       an empty string will result in no suffix.
    423433         * @param string $dest_path
    424434         * @param string $extension
    425435         * @return string filename
    426436         */
    427437        public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
    428438                // $suffix will be appended to the destination filename, just before the extension.
    429                 if ( ! $suffix ) {
     439                if ( null === $suffix ) {
    430440                        $suffix = $this->get_suffix();
    431441                }
    432442
    abstract class WP_Image_Editor { 
    447457                        }
    448458                }
    449459
     460                if ( '' === $suffix ) {
     461                        return trailingslashit( $dir ) . "{$name}.{$new_ext}";
     462                }
     463
    450464                return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
    451465        }
    452466
    abstract class WP_Image_Editor { 
    627641
    628642                return wp_get_default_extension_for_mime_type( $mime_type );
    629643        }
    630 }
    631644
     645        /**
     646         * Set the editor mime type, useful when outputting alternate mime types.
     647         *
     648         * Track that the mime type is set with the mime type set flag.
     649         *
     650         * @since 6.0.0
     651         *
     652         * @param string $mime_type The mime type to set.
     653         */
     654        public function set_mime_type( $mime_type ) {
     655                $this->mime_type     = $mime_type;
     656                $this->mime_type_set = true;
     657        }
     658
     659        /**
     660         * Reset the mime type to the original file mime type.
     661         *
     662         * Reset the mime type set flag.
     663         */
     664        public function reset_mime_type() {
     665                $this->mime_type     = wp_get_image_mime( $this->file );
     666                $this->mime_type_set = false;
     667        }
     668}
  • 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..b9c96c8721 100644
    class Tests_Image_Functions extends WP_UnitTestCase { 
    460460                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
    461461                }
    462462
     463                // Use legacy JPEG output.
     464                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     465
    463466                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    464467                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    465468                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    480483                $this->assertNotEmpty( $attachment_id );
    481484
    482485                $temp_dir = get_temp_dir();
    483 
    484486                $metadata = wp_generate_attachment_metadata( $attachment_id, $test_file );
    485487
    486488                $expected = array(
    class Tests_Image_Functions extends WP_UnitTestCase { 
    498500                                        'height'    => 300,
    499501                                        'mime-type' => 'image/jpeg',
    500502                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     503                                        'sources'   => array(
     504                                                'image/jpeg' => array(
     505                                                        'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
     506                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     507
     508                                                ),
     509                                        ),
    501510                                ),
    502511                                'large'     => array(
    503512                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    505514                                        'height'    => 1024,
    506515                                        'mime-type' => 'image/jpeg',
    507516                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     517                                        'sources'   => array(
     518                                                'image/jpeg' => array(
     519                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     520                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     521                                                ),
     522                                        ),
    508523                                ),
    509524                                'thumbnail' => array(
    510525                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    512527                                        'height'    => 150,
    513528                                        'mime-type' => 'image/jpeg',
    514529                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     530                                        'sources'   => array(
     531                                                'image/jpeg' => array(
     532                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     533                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     534                                                ),
     535                                        ),
    515536                                ),
    516537                        ),
    517538                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    523544                foreach ( $metadata['sizes'] as $size ) {
    524545                        unlink( $temp_dir . $size['file'] );
    525546                }
     547                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    526548        }
    527549
    528550        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    537559
    538560                update_option( 'medium_crop', 1 );
    539561
     562                // Use legacy JPEG output.
     563                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     564
    540565                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    541566                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    542567                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    557582                $this->assertNotEmpty( $attachment_id );
    558583
    559584                $temp_dir = get_temp_dir();
    560 
    561585                $metadata = wp_generate_attachment_metadata( $attachment_id, $test_file );
    562586
    563587                $expected = array(
    class Tests_Image_Functions extends WP_UnitTestCase { 
    575599                                        'height'    => 300,
    576600                                        'mime-type' => 'image/jpeg',
    577601                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     602                                        'sources'   => array(
     603                                                'image/jpeg' => array(
     604                                                        'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
     605                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     606                                                ),
     607                                        ),
    578608                                ),
    579609                                'large'     => array(
    580610                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    582612                                        'height'    => 1024,
    583613                                        'mime-type' => 'image/jpeg',
    584614                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     615                                        'sources'   => array(
     616                                                'image/jpeg' => array(
     617                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     618                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     619                                                ),
     620                                        ),
     621
    585622                                ),
    586623                                'thumbnail' => array(
    587624                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    589626                                        'height'    => 150,
    590627                                        'mime-type' => 'image/jpeg',
    591628                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     629                                        'sources'   => array(
     630                                                'image/jpeg' => array(
     631                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     632                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     633                                                ),
     634                                        ),
    592635                                ),
    593636                        ),
    594637                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    600643                foreach ( $metadata['sizes'] as $size ) {
    601644                        unlink( $temp_dir . $size['file'] );
    602645                }
     646                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     647
    603648        }
    604649
    605650        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    610655                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
    611656                }
    612657
     658                // Use legacy JPEG output.
     659                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     660
    613661                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    614662                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    615663                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    642690                        'height'    => 100,
    643691                        'mime-type' => 'image/jpeg',
    644692                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     693                        'sources'   => array(
     694                                'image/jpeg' => array(
     695                                        'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
     696                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     697                                ),
     698                        ),
    645699                );
    646700
    647701                // Different environments produce slightly different filesize results.
    class Tests_Image_Functions extends WP_UnitTestCase { 
    657711                foreach ( $metadata['sizes'] as $size ) {
    658712                        unlink( $temp_dir . $size['file'] );
    659713                }
     714                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    660715        }
    661716
    662717        public function filter_fallback_intermediate_image_sizes( $fallback_sizes, $metadata ) {
  • 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 );