Make WordPress Core

Ticket #55443: 55443.5.diff

File 55443.5.diff, 89.7 KB (added by adamsilverstein, 20 months ago)

latest from git

  • 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 1a9c9e2e9c..49563fa26a 100644
    function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $s 
    7777 * Registered sub-sizes that are larger than the image are skipped.
    7878 *
    7979 * @since 5.3.0
     80 * @since 6.1.0 The $mime_type parameter was added.
    8081 *
    81  * @param int $attachment_id The image attachment post ID.
     82 * @param int    $attachment_id The image attachment post ID.
     83 * @param string $mime_type     Optional. The mime type to check for missing sizes. Default is the primary image mime.
    8284 * @return array[] Associative array of arrays of image sub-size information for
    8385 *                 missing image sizes, keyed by image size name.
    8486 */
    85 function wp_get_missing_image_subsizes( $attachment_id ) {
     87function wp_get_missing_image_subsizes( $attachment_id, $mime_type = '' ) {
    8688        if ( ! wp_attachment_is_image( $attachment_id ) ) {
    8789                return array();
    8890        }
    8991
     92        $primary_mime_type = get_post_mime_type( get_post( $attachment_id ) );
     93        if ( ! $mime_type ) {
     94                $mime_type = $primary_mime_type;
     95        }
     96
    9097        $registered_sizes = wp_get_registered_image_subsizes();
    9198        $image_meta       = wp_get_attachment_metadata( $attachment_id );
    9299
    function wp_get_missing_image_subsizes( $attachment_id ) { 
    129136         * However we keep the old sub-sizes with the previous dimensions
    130137         * as the image may have been used in an older post.
    131138         */
    132         $missing_sizes = array_diff_key( $possible_sizes, $image_meta['sizes'] );
     139        $missing_sizes = array();
     140        foreach ( $possible_sizes as $size_name => $size_data ) {
     141                if ( ! isset( $image_meta['sizes'][ $size_name ] ) ) {
     142                        $missing_sizes[ $size_name ] = $size_data;
     143                        continue;
     144                }
     145
     146                if ( ( isset( $size_data['mime-type'] ) && $size_data['mime-type'] === $mime_type ) || isset( $size_data['sources'][ $mime_type ] ) ) {
     147                        continue;
     148                }
     149
     150                $missing_sizes[ $size_name ] = $size_data;
     151        }
     152
     153        // Filter secondary mime types to those sizes that are enabled.
     154        if ( $primary_mime_type !== $mime_type ) {
     155                $missing_sizes = _wp_filter_image_sizes_additional_mime_type_support( $missing_sizes, $attachment_id );
     156        }
    133157
    134158        /**
    135159         * Filters the array of missing image sub-sizes for an uploaded image.
    136160         *
    137161         * @since 5.3.0
     162         * @since 6.1.0 The $mime_type filter parameter was added.
    138163         *
    139164         * @param array[] $missing_sizes Associative array of arrays of image sub-size information for
    140165         *                               missing image sizes, keyed by image size name.
    141166         * @param array   $image_meta    The image meta data.
    142167         * @param int     $attachment_id The image attachment post ID.
     168         * @param string  $mime_type     The image mime type to get missing sizes for.
    143169         */
    144         return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id );
     170        return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id, $mime_type );
    145171}
    146172
    147173/**
    function wp_get_missing_image_subsizes( $attachment_id ) { 
    149175 * create them and update the image meta data.
    150176 *
    151177 * @since 5.3.0
     178 * @since 6.1.0 Now supports additional mime types, creating the additional sub-sizes and 'full' sized images.
    152179 *
    153180 * @param int $attachment_id The image attachment post ID.
    154181 * @return array|WP_Error The updated image meta data array or WP_Error object
    function wp_update_image_subsizes( $attachment_id ) { 
    167194                        return new WP_Error( 'invalid_attachment', __( 'The attached file cannot be found.' ) );
    168195                }
    169196        } else {
    170                 $missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
     197                // Get the primary and additional mime types to generate.
     198                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $image_file, $attachment_id );
    171199
    172                 if ( empty( $missing_sizes ) ) {
    173                         return $image_meta;
     200                // Generate missing 'full' image files for additional mime types.
     201                if ( ! empty( $additional_mime_types ) ) {
     202                        if ( isset( $image_meta['sources'] ) ) {
     203                                $missing_mime_types = array_diff( $additional_mime_types, array_keys( $image_meta['sources'] ) );
     204                        } else {
     205                                $missing_mime_types = $additional_mime_types;
     206                        }
     207                        if ( ! empty( $missing_mime_types ) ) {
     208                                $image_meta = _wp_make_additional_mime_types( $missing_mime_types, $image_file, $image_meta, $attachment_id );
     209                        }
    174210                }
    175211
    176                 // This also updates the image meta.
    177                 $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
     212                // Generate missing image sub-sizes for each mime type.
     213                $all_mime_types = array_merge( array( $primary_mime_type ), $additional_mime_types );
     214                foreach ( $all_mime_types as $mime_type ) {
     215                        $missing_sizes = wp_get_missing_image_subsizes( $attachment_id, $mime_type );
     216
     217                        if ( empty( $missing_sizes ) ) {
     218                                continue;
     219                        }
     220
     221                        // This also updates the image meta.
     222                        $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id, $mime_type );
     223                }
    178224        }
    179225
    180226        /** This filter is documented in wp-admin/includes/image.php */
    function _wp_image_meta_replace_original( $saved_data, $original_file, $image_me 
    222268}
    223269
    224270/**
    225  * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
     271 * Creates image mime variations and sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
    226272 *
    227273 * Intended for use after an image is uploaded. Saves/updates the image metadata after each
    228274 * sub-size is created. If there was an error, it is added to the returned image metadata array.
    229275 *
    230276 * @since 5.3.0
     277 * @since 6.1.0 Generates sub-sizes in alternate mime types based on the `wp_image_mime_transforms` filter.
    231278 *
    232279 * @param string $file          Full path to the image file.
    233280 * @param int    $attachment_id Attachment ID to process.
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    248295                'file'     => _wp_relative_upload_path( $file ),
    249296                'filesize' => wp_filesize( $file ),
    250297                'sizes'    => array(),
     298                'sources'  => array(),
    251299        );
    252300
    253301        // Fetch additional metadata from EXIF/IPTC.
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    257305                $image_meta['image_meta'] = $exif_meta;
    258306        }
    259307
    260         // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
    261         if ( 'image/png' !== $imagesize['mime'] ) {
     308        // Get the primary and additional mime types to generate.
     309        list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $file, $attachment_id );
     310
     311        list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $primary_mime_type );
     312        if ( is_wp_error( $editor ) ) {
     313                return $image_meta;
     314        }
     315        $suffix = _wp_get_image_suffix( $resized, $rotated );
     316
     317        // Save image only if either it was modified or if the primary mime type is different from the original.
     318        if ( ! empty( $suffix ) || $primary_mime_type !== $imagesize['mime'] ) {
     319                $saved = $editor->save( $editor->generate_filename( $suffix ) );
     320
     321                if ( ! is_wp_error( $saved ) ) {
     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.
     330                }
     331        }
     332
     333        // Set 'sources' for the primary mime type.
     334        $image_meta['sources'][ $primary_mime_type ] = _wp_get_sources_from_meta( $image_meta );
    262335
     336        /*
     337         * Initial save of the new metadata.
     338         * At this point the file was uploaded and moved to the uploads directory
     339         * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
     340         */
     341        wp_update_attachment_metadata( $attachment_id, $image_meta );
     342
     343        if ( ! empty( $additional_mime_types ) ) {
     344                // Use the original file's exif_meta orientation information for secondary mime generation.
     345                $saved_orientation                       = $image_meta['image_meta']['orientation'];
     346                $image_meta['image_meta']['orientation'] = $exif_meta['orientation'];
     347                $image_meta                              = _wp_make_additional_mime_types( $additional_mime_types, $file, $image_meta, $attachment_id );
     348                $image_meta['image_meta']['orientation'] = $saved_orientation;
     349
     350        }
     351
     352        $new_sizes = wp_get_registered_image_subsizes();
     353
     354        /**
     355         * Filters the image sizes automatically generated when uploading an image.
     356         *
     357         * @since 2.9.0
     358         * @since 4.4.0 Added the `$image_meta` argument.
     359         * @since 5.3.0 Added the `$attachment_id` argument.
     360         *
     361         * @param array $new_sizes     Associative array of image sizes to be created.
     362         * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
     363         * @param int   $attachment_id The attachment post ID for the image.
     364         */
     365        $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
     366
     367        $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $primary_mime_type );
     368
     369        // Filter secondary mime types to those sizes that are enabled.
     370        $new_sizes = _wp_filter_image_sizes_additional_mime_type_support( $new_sizes, $attachment_id );
     371
     372        foreach ( $additional_mime_types as $additional_mime_type ) {
     373                $image_meta = _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $additional_mime_type );
     374        }
     375
     376        return $image_meta;
     377}
     378
     379/**
     380 * Returns a WP_Image_Editor instance where the image file has been scaled and rotated as necessary.
     381 *
     382 * @since 6.1.0
     383 * @access private
     384 *
     385 * @param string     $file          Full path to the image file.
     386 * @param int        $attachment_id Attachment ID.
     387 * @param array      $imagesize     {
     388 *     Indexed array of the image width and height in pixels.
     389 *
     390 *     @type int $0 The image width.
     391 *     @type int $1 The image height.
     392 * }
     393 * @param array|null $exif_meta EXIF metadata if extracted from the image file.
     394 * @param string     $mime_type Output mime type.
     395 * @return array Array with three entries: The WP_Image_Editor instance, whether the image was resized, and whether the
     396 *               image was rotated (booleans). Each entry can alternatively be a WP_Error in case something went wrong.
     397 */
     398function _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type ) {
     399        $resized = false;
     400        $rotated = false;
     401
     402        $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
     403        if ( is_wp_error( $editor ) ) {
     404                // This image cannot be edited.
     405                return array( $editor, $resized, $rotated );
     406        }
     407
     408        if ( ! empty( $mime_type ) ) {
     409                $editor->set_mime_type( $mime_type );
     410        }
     411
     412        // Do not scale (large) PNG images. May result in sub-sizes that have greater file size than the original. See #48736.
     413        if ( 'image/png' !== $mime_type ) {
    263414                /**
    264415                 * Filters the "BIG image" threshold value.
    265416                 *
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    285436
    286437                // If the original image's dimensions are over the threshold,
    287438                // scale the image and use it as the "full" size.
    288                 if ( $threshold && ( $image_meta['width'] > $threshold || $image_meta['height'] > $threshold ) ) {
    289                         $editor = wp_get_image_editor( $file );
    290 
    291                         if ( is_wp_error( $editor ) ) {
    292                                 // This image cannot be edited.
    293                                 return $image_meta;
    294                         }
    295 
     439                if ( $threshold && ( $imagesize[0] > $threshold || $imagesize[1] > $threshold ) ) {
    296440                        // Resize the image.
    297441                        $resized = $editor->resize( $threshold, $threshold );
    298                         $rotated = null;
    299442
    300443                        // If there is EXIF data, rotate according to EXIF Orientation.
    301444                        if ( ! is_wp_error( $resized ) && is_array( $exif_meta ) ) {
    302                                 $resized = $editor->maybe_exif_rotate();
    303                                 $rotated = $resized;
    304                         }
    305 
    306                         if ( ! is_wp_error( $resized ) ) {
    307                                 // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
    308                                 // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
    309                                 $saved = $editor->save( $editor->generate_filename( 'scaled' ) );
    310 
    311                                 if ( ! is_wp_error( $saved ) ) {
    312                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
    313 
    314                                         // If the image was rotated update the stored EXIF data.
    315                                         if ( true === $rotated && ! empty( $image_meta['image_meta']['orientation'] ) ) {
    316                                                 $image_meta['image_meta']['orientation'] = 1;
    317                                         }
    318                                 } else {
    319                                         // TODO: Log errors.
    320                                 }
    321                         } else {
    322                                 // TODO: Log errors.
     445                                $rotated = $editor->maybe_exif_rotate();
    323446                        }
    324447                } elseif ( ! empty( $exif_meta['orientation'] ) && 1 !== (int) $exif_meta['orientation'] ) {
    325448                        // Rotate the whole original image if there is EXIF data and "orientation" is not 1.
    326 
    327                         $editor = wp_get_image_editor( $file );
    328 
    329                         if ( is_wp_error( $editor ) ) {
    330                                 // This image cannot be edited.
    331                                 return $image_meta;
    332                         }
    333 
    334                         // Rotate the image.
    335449                        $rotated = $editor->maybe_exif_rotate();
    336 
    337                         if ( true === $rotated ) {
    338                                 // Append `-rotated` to the image file name.
    339                                 $saved = $editor->save( $editor->generate_filename( 'rotated' ) );
    340 
    341                                 if ( ! is_wp_error( $saved ) ) {
    342                                         $image_meta = _wp_image_meta_replace_original( $saved, $file, $image_meta, $attachment_id );
    343 
    344                                         // Update the stored EXIF data.
    345                                         if ( ! empty( $image_meta['image_meta']['orientation'] ) ) {
    346                                                 $image_meta['image_meta']['orientation'] = 1;
    347                                         }
    348                                 } else {
    349                                         // TODO: Log errors.
    350                                 }
    351                         }
    352450                }
    353451        }
    354452
    355         /*
    356          * Initial save of the new metadata.
    357          * At this point the file was uploaded and moved to the uploads directory
    358          * but the image sub-sizes haven't been created yet and the `sizes` array is empty.
    359          */
    360         wp_update_attachment_metadata( $attachment_id, $image_meta );
     453        return array( $editor, $resized, $rotated );
     454}
    361455
    362         $new_sizes = wp_get_registered_image_subsizes();
     456/**
     457 * Gets the suffix to use for image files based on resizing and rotating.
     458 *
     459 * @since 6.1.0
     460 * @access private
     461 *
     462 * @param bool|WP_Error Whether the image was resized, or an error if resizing failed.
     463 * @param bool|WP_Error Whether the image was rotated, or an error if rotating failed.
     464 * @return string The suffix to use for the file name, or empty string if none.
     465 */
     466function _wp_get_image_suffix( $resized, $rotated ) {
     467        if ( $resized && ! is_wp_error( $resized ) ) {
     468                // Append "-scaled" to the image file name. It will look like "my_image-scaled.jpg".
     469                // This doesn't affect the sub-sizes names as they are generated from the original image (for best quality).
     470                return 'scaled';
     471        }
    363472
    364         /**
    365          * Filters the image sizes automatically generated when uploading an image.
    366          *
    367          * @since 2.9.0
    368          * @since 4.4.0 Added the `$image_meta` argument.
    369          * @since 5.3.0 Added the `$attachment_id` argument.
    370          *
    371          * @param array $new_sizes     Associative array of image sizes to be created.
    372          * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
    373          * @param int   $attachment_id The attachment post ID for the image.
    374          */
    375         $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
     473        if ( true === $rotated ) {
     474                // Append `-rotated` to the image file name.
     475                return 'rotated';
     476        }
     477
     478        if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
     479                // TODO: Log errors.
     480        }
     481        return '';
     482}
    376483
    377         return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
     484/**
     485 * Gets a sources array element from a meta.
     486 *
     487 * @since 6.1.0
     488 * @access private
     489 *
     490 * @param array $meta The meta to get the source from.
     491 * @return array The source array element.
     492 */
     493function _wp_get_sources_from_meta( $meta ) {
     494        return array(
     495                'file'     => isset( $meta['file'] ) ? wp_basename( $meta['file'] ) : '',
     496                'filesize' => isset( $meta['filesize'] ) ? $meta['filesize'] : wp_filesize( $meta['path'] ),
     497        );
    378498}
    379499
    380500/**
    function wp_create_image_subsizes( $file, $attachment_id ) { 
    384504 * Errors are stored in the returned image metadata array.
    385505 *
    386506 * @since 5.3.0
     507 * @since 6.1.0 The $mime_type parameter was added.
    387508 * @access private
    388509 *
    389  * @param array  $new_sizes     Array defining what sizes to create.
    390  * @param string $file          Full path to the image file.
    391  * @param array  $image_meta    The attachment meta data array.
    392  * @param int    $attachment_id Attachment ID to process.
     510 * @param array  $new_sizes       Array defining what sizes to create.
     511 * @param string $file            Full path to the image file.
     512 * @param array  $image_meta      The attachment meta data array.
     513 * @param int    $attachment_id   Attachment ID to process.
     514 * @param string $mime_type       Optional. The mime type to check for missing sizes. Default is the image mime of $file.
    393515 * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
    394516 */
    395 function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
     517function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id, $mime_type = '' ) {
    396518        if ( empty( $image_meta ) || ! is_array( $image_meta ) ) {
    397519                // Not an image attachment.
    398520                return array();
    399521        }
    400522
     523        $sizes = array();
     524
     525        $original_mime_type = wp_get_image_mime( $file );
     526        if ( ! $mime_type ) {
     527                $mime_type = $original_mime_type;
     528        }
     529
    401530        // Check if any of the new sizes already exist.
    402         if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
    403                 foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
     531        if ( isset( $sizes ) && is_array( $sizes ) ) {
     532                foreach ( $sizes as $size_name => $size_meta ) {
    404533                        /*
    405534                         * Only checks "size name" so we don't override existing images even if the dimensions
    406535                         * don't match the currently defined size with the same name.
    407536                         * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
    408537                         */
    409538                        if ( array_key_exists( $size_name, $new_sizes ) ) {
    410                                 unset( $new_sizes[ $size_name ] );
     539                                // Unset the size if it is either the required mime type already exists either as main mime type or
     540                                // within sources.
     541                                if ( $size_meta['mime-type'] === $mime_type || isset( $size_meta['sources'][ $mime_type ] ) ) {
     542                                        unset( $new_sizes[ $size_name ] );
     543                                }
    411544                        }
    412545                }
    413546        } else {
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    433566
    434567        $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
    435568
    436         $editor = wp_get_image_editor( $file );
     569        $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
    437570
    438571        if ( is_wp_error( $editor ) ) {
    439572                // The image cannot be edited.
    440573                return $image_meta;
    441574        }
    442575
     576        $editor->set_mime_type( $mime_type );
     577
    443578        // If stored EXIF data exists, rotate the source image before creating sub-sizes.
    444579        if ( ! empty( $image_meta['image_meta'] ) ) {
    445580                $rotated = $editor->maybe_exif_rotate();
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    457592                                // TODO: Log errors.
    458593                        } else {
    459594                                // Save the size meta value.
    460                                 $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     595                                if ( ! isset( $image_meta['sizes'][ $new_size_name ] ) ) {
     596                                        $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     597                                } else {
     598                                        // Remove any newly generated images that are larger than the primary mime type.
     599                                        $new_size     = isset( $new_size_meta['filesize'] ) ? $new_size_meta['filesize'] : 0;
     600                                        $primary_size = isset( $image_meta['sizes'][ $new_size_name ]['filesize'] ) ? $image_meta['sizes'][ $new_size_name ]['filesize'] : 0;
     601
     602                                        if ( $new_size && $primary_size && $new_size >= $primary_size ) {
     603                                                wp_delete_file( dirname( $file ) . '/' . $new_size_meta['file'] );
     604                                                continue;
     605                                        }
     606                                }
     607                                if ( ! isset( $image_meta['sizes'][ $new_size_name ]['sources'] ) ) {
     608                                        $image_meta['sizes'][ $new_size_name ]['sources'] = array();
     609                                }
     610                                $image_meta['sizes'][ $new_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
    461611                                wp_update_attachment_metadata( $attachment_id, $image_meta );
    462612                        }
    463613                }
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    466616                $created_sizes = $editor->multi_resize( $new_sizes );
    467617
    468618                if ( ! empty( $created_sizes ) ) {
    469                         $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
     619                        foreach ( $created_sizes as $created_size_name => $created_size_meta ) {
     620
     621                                // Primary mime type is set in 'sizes' array.
     622                                if ( ! isset( $image_meta['sizes'][ $created_size_name ] ) ) {
     623                                        $image_meta['sizes'][ $created_size_name ] = $created_size_meta;
     624                                } else {
     625                                        // Remove any newly generated images that are larger than the primary mime type.
     626                                        $new_size     = isset( $created_size_meta['filesize'] ) ? $created_size_meta['filesize'] : 0;
     627                                        $primary_size = isset( $image_meta['sizes'][ $created_size_name ]['filesize'] ) ? $image_meta['sizes'][ $created_size_name ]['filesize'] : 0;
     628
     629                                        if ( $new_size && $primary_size && $new_size >= $primary_size ) {
     630                                                wp_delete_file( dirname( $file ) . '/' . $created_size_meta['file'] );
     631                                                continue;
     632                                        }
     633                                }
     634                                if ( ! isset( $image_meta['sizes'][ $created_size_name ]['sources'] ) ) {
     635                                        $image_meta['sizes'][ $created_size_name ]['sources'] = array();
     636                                }
     637                                $image_meta['sizes'][ $created_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     638                        }
    470639                        wp_update_attachment_metadata( $attachment_id, $image_meta );
    471640                }
    472641        }
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    474643        return $image_meta;
    475644}
    476645
     646/**
     647 * Filters the list of image size objects that support secondary mime type output.
     648 *
     649 * @since 6.1.0
     650 *
     651 * @param array $sizes         Array of image sizes to filter
     652 * @param int   $attachment_id Attachment ID.
     653 * @return array $sizes Array of size objects that support secondary mime type output.
     654 */
     655function _wp_filter_image_sizes_additional_mime_type_support( $sizes, $attachment_id ) {
     656
     657        // Include only the core sizes that do not rely on add_image_size(). Additional image sizes are opt-in.
     658        $enabled_sizes = array(
     659                'thumbnail'      => true,
     660                'medium'         => true,
     661                'medium_large'   => true,
     662                'large'          => true,
     663                'post-thumbnail' => true,
     664        );
     665
     666        /**
     667         * Filter the sizes that support secondary mime type output. Developers can use this
     668         * to control the output of additional mime type sub-sized images.
     669         *
     670         * @since 6.1.0
     671         *
     672         * @param array $enabled_sizes Map of size names and whether they support secondary mime type output.
     673         * @param int   $attachment_id Attachment ID.
     674         */
     675        $enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes, $attachment_id );
     676
     677        // Filter supported sizes to only include enabled sizes.
     678        return array_intersect_key( $sizes, array_filter( $enabled_sizes ) );
     679}
     680
     681/**
     682 * Low-level function to create full-size images in additional mime types.
     683 *
     684 * Updates the image meta after each mime type image is created.
     685 *
     686 * @since 6.1.0
     687 * @access private
     688 *
     689 * @param array  $new_mime_types Array defining what mime types to create.
     690 * @param string $file           Full path to the image file.
     691 * @param array  $image_meta     The attachment meta data array.
     692 * @param int    $attachment_id  Attachment ID to process.
     693 * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
     694 */
     695function _wp_make_additional_mime_types( $new_mime_types, $file, $image_meta, $attachment_id ) {
     696        $imagesize          = array(
     697                $image_meta['width'],
     698                $image_meta['height'],
     699        );
     700        $exif_meta          = isset( $image_meta['image_meta'] ) ? $image_meta['image_meta'] : null;
     701        $original_file_size = isset( $image_meta['filesize'] ) ? $image_meta['filesize'] : wp_filesize( $file );
     702
     703        foreach ( $new_mime_types as $mime_type ) {
     704                list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
     705                if ( is_wp_error( $editor ) ) {
     706                        // The image cannot be edited.
     707                        continue;
     708                }
     709
     710                $suffix    = _wp_get_image_suffix( $resized, $rotated );
     711                $extension = wp_get_default_extension_for_mime_type( $mime_type );
     712
     713                $saved = $editor->save( $editor->generate_filename( $suffix, null, $extension ) );
     714
     715                if ( is_wp_error( $saved ) ) {
     716                        // TODO: Log errors.
     717                } else {
     718                        // If the saved image is larger than the original, discard it.
     719                        $filesize = isset( $saved['filesize'] ) ? $saved['filesize'] : wp_filesize( $saved['path'] );
     720                        if ( $filesize && $original_file_size && $filesize > $original_file_size ) {
     721                                wp_delete_file( $saved['path'] );
     722                                continue;
     723                        }
     724                        $image_meta['sources'][ $mime_type ] = _wp_get_sources_from_meta( $saved );
     725                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     726                }
     727        }
     728
     729        return $image_meta;
     730}
     731
     732
     733/**
     734 * Check if an image belongs to an attachment.
     735 *
     736 * @since 6.1.0
     737 * @access private
     738 *
     739 * @param string $filename     Full path to the image file.
     740 * @param int   $attachment_id Attachment ID to check.
     741 * @return bool True if the image belongs to the attachment, false otherwise.
     742 */
     743function _wp_image_belongs_to_attachment( $filename, $attachment_id ) {
     744        $meta_data = wp_get_attachment_metadata( $attachment_id );
     745
     746        if ( ! isset( $image_meta['sizes'] ) ) {
     747                return false;
     748        }
     749        $sizes = $image_meta['sizes'];
     750        foreach ( $sizes as $size ) {
     751                if ( $size['file'] === $filename ) {
     752                        return true;
     753                }
     754                if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
     755                        foreach ( $size['sources'] as $source ) {
     756                                if ( $source['file'] === $filename ) {
     757                                        return true;
     758                                }
     759                        }
     760                }
     761        }
     762        return false;
     763}
     764
    477765/**
    478766 * Generate attachment meta data and create image sub-sizes for images.
    479767 *
    function wp_generate_attachment_metadata( $attachment_id, $file ) { 
    630918                                        wp_update_attachment_metadata( $attachment_id, $metadata );
    631919
    632920                                        // Create sub-sizes saving the image meta after each.
    633                                         $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id );
     921                                        $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id, '' );
    634922                                }
    635923                        }
    636924                }
    function _copy_image_file( $attachment_id ) { 
    11571445
    11581446        return $dst_file;
    11591447}
     1448
     1449/**
     1450 * Returns an array with the list of valid mime types that a specific mime type should be converted into.
     1451 * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
     1452 * is considered the primary output type for this image.
     1453 *
     1454 * Called for each uploaded image to determine the list of mime types that should be converted into. Then,
     1455 * called again for each image size as they are generated to check if the image should be converted into the mime type
     1456 * for that size.
     1457 *
     1458 * @since 6.1.0
     1459 *
     1460 * @param int    $attachment_id  The attachment ID.
     1461 * @param string $image_size     The image size name. False when called on the image and target size is unavailable.
     1462 * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
     1463 *               generate.
     1464 */
     1465function wp_upload_image_mime_transforms( $attachment_id, $image_size ) {
     1466        $default_image_mime_transforms = array(
     1467                'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
     1468                'image/webp' => array( 'image/webp', 'image/jpeg' ),
     1469        );
     1470        $image_mime_transforms         = $default_image_mime_transforms;
     1471
     1472        /**
     1473         * Filter the output mime types for a given input mime type and image size.
     1474         *
     1475         * @since 6.1.0
     1476         *
     1477         * @param array  $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
     1478         *                                      and the value is one or more mime file types to generate.
     1479         * @param int    $attachment_id         The ID of the attachment where the hook was dispatched.
     1480         * @param string $image_size            The image size name. Optional.
     1481         */
     1482        $image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id, $image_size );
     1483
     1484        if ( ! is_array( $image_mime_transforms ) ) {
     1485                return $default_image_mime_transforms;
     1486        }
     1487
     1488        return array_map(
     1489                function( $transforms_list ) {
     1490                        return (array) $transforms_list;
     1491                },
     1492                $image_mime_transforms
     1493        );
     1494}
     1495
     1496/**
     1497 * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
     1498 *
     1499 * @since 6.1.0
     1500 * @access private
     1501 *
     1502 * @param string $file          Full path to the image file.
     1503 * @param int    $attachment_id Attachment ID to process.
     1504 * @return array An array with two entries, the primary mime type and the list of additional mime types.
     1505 */
     1506function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
     1507        $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id, false );
     1508        $original_mime_type    = wp_get_image_mime( $file );
     1509        $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
     1510
     1511        // Exclude any output mime types that the system doesn't support.
     1512        $output_mime_types = array_values(
     1513                array_filter(
     1514                        $output_mime_types,
     1515                        function( $mime_type ) {
     1516                                return wp_image_editor_supports(
     1517                                        array(
     1518                                                'mime_type' => $mime_type,
     1519                                        )
     1520                                );
     1521                        }
     1522                )
     1523        );
     1524
     1525        // Handle an empty value for $output_mime_types: only output the original type.
     1526        if ( empty( $output_mime_types ) ) {
     1527                return array( $original_mime_type, array() );
     1528        }
     1529
     1530        // Use original mime type as primary mime type, or alternatively the first one.
     1531        $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
     1532        if ( false === $primary_mime_type_key ) {
     1533                $primary_mime_type_key = 0;
     1534        }
     1535        // Split output mime types into primary mime type and additional mime types.
     1536        $additional_mime_types     = $output_mime_types;
     1537        list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
     1538
     1539        return array(
     1540                $primary_mime_type,
     1541                $additional_mime_types,
     1542        );
     1543}
  • 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 caa3092d36..a2aff4d415 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 { 
    334335        protected function get_output_format( $filename = null, $mime_type = null ) {
    335336                $new_ext = null;
    336337
     338                if ( ! $mime_type && $this->mime_type_set ) {
     339                        $mime_type = $this->mime_type;
     340                }
     341
    337342                // By default, assume specified type takes priority.
    338343                if ( $mime_type ) {
    339344                        $new_ext = $this->get_extension( $mime_type );
    abstract class WP_Image_Editor { 
    345350                } else {
    346351                        // If no file specified, grab editor's current extension and mime-type.
    347352                        $file_ext  = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
    348                         $file_mime = $this->mime_type;
     353                        $file_mime = ! $this->mime_type_set ? $this->mime_type : $this->get_mime_type( $file_ext );
    349354                }
    350355
    351356                // Check to see if specified mime-type is the same as type implied by
    abstract class WP_Image_Editor { 
    428433         * Builds an output filename based on current file, and adding proper suffix
    429434         *
    430435         * @since 3.5.0
     436         * @since 6.1.0 Skips adding a suffix when set to an empty string.
    431437         *
    432          * @param string $suffix
     438         * @param string $suffix Optional. Suffix to add to the filename. Passing null
     439         *                       will result in a 'widthxheight' suffix. Passing
     440         *                       an empty string will result in no suffix.
    433441         * @param string $dest_path
    434442         * @param string $extension
    435443         * @return string filename
    436444         */
    437445        public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
    438446                // $suffix will be appended to the destination filename, just before the extension.
    439                 if ( ! $suffix ) {
     447                if ( null === $suffix ) {
    440448                        $suffix = $this->get_suffix();
    441449                }
    442450
    abstract class WP_Image_Editor { 
    457465                        }
    458466                }
    459467
    460                 return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
     468                if ( empty( $suffix ) ) {
     469                        $suffix = '';
     470                } else {
     471                        $suffix = "-{$suffix}";
     472                }
     473
     474                // When the mime type being generated is different from the source, add the extension to the suffix to ensure uniqueness.
     475                if ( ! empty( $extension ) && $extension !== $ext ) {
     476                        $suffix .= "-{$ext}";
     477                }
     478
     479                return trailingslashit( $dir ) . "{$name}{$suffix}.{$new_ext}";
    461480        }
    462481
    463482        /**
    abstract class WP_Image_Editor { 
    476495        }
    477496
    478497        /**
    479          * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
     498         * Check if an image has EXIF Orientation tag and rotate it if needed.
    480499         *
    481500         * @since 5.3.0
    482501         *
    abstract class WP_Image_Editor { 
    486505        public function maybe_exif_rotate() {
    487506                $orientation = null;
    488507
    489                 if ( is_callable( 'exif_read_data' ) && 'image/jpeg' === $this->mime_type ) {
     508                if ( is_callable( 'exif_read_data' ) ) {
    490509                        $exif_data = @exif_read_data( $this->file );
    491510
    492511                        if ( ! empty( $exif_data['Orientation'] ) ) {
    abstract class WP_Image_Editor { 
    637656
    638657                return wp_get_default_extension_for_mime_type( $mime_type );
    639658        }
    640 }
    641659
     660        /**
     661         * Set the editor mime type, useful when outputting alternate mime types.
     662         *
     663         * Track that the mime type is set with the mime type set flag.
     664         *
     665         * @since 6.1.0
     666         *
     667         * @param string $mime_type The mime type to set.
     668         */
     669        public function set_mime_type( $mime_type ) {
     670                $this->mime_type     = $mime_type;
     671                $this->mime_type_set = true;
     672        }
     673
     674        /**
     675         * Reset the mime type to the original file mime type.
     676         *
     677         * Reset the mime type set flag.
     678         *
     679         * @since 6.1.0
     680         */
     681        public function reset_mime_type() {
     682                $this->mime_type     = wp_get_image_mime( $this->file );
     683                $this->mime_type_set = false;
     684        }
     685}
  • src/wp-includes/media.php

    diff --git src/wp-includes/media.php src/wp-includes/media.php
    index aaf811d2d0..51c49f83d2 100644
    function wp_filter_content_tags( $content, $context = null ) { 
    18521852                                $filtered_image = wp_img_tag_add_decoding_attr( $filtered_image, $context );
    18531853                        }
    18541854
     1855                        // Use alternate mime types when specified and available.
     1856                        if ( $attachment_id > 0 && _wp_in_front_end_context() ) {
     1857                                $filtered_image = wp_image_use_alternate_mime_types( $filtered_image, $context, $attachment_id );
     1858                        }
     1859
    18551860                        /**
    18561861                         * Filters an img tag within the content for a given context.
    18571862                         *
    function wp_filter_content_tags( $content, $context = null ) { 
    18981903        return $content;
    18991904}
    19001905
     1906/**
     1907 * Use alternate mime type images in the front end content output when available.
     1908 *
     1909 * @since 6.1.0
     1910 *
     1911 * @param string $image         The HTML `img` tag where the attribute should be added.
     1912 * @param string $context       Additional context to pass to the filters.
     1913 * @param int    $attachment_id The attachment ID.
     1914 * @return string Converted `img` tag with `loading` attribute added.
     1915 */
     1916function wp_image_use_alternate_mime_types( $image, $context, $attachment_id ) {
     1917        $metadata = wp_get_attachment_metadata( $attachment_id );
     1918        if ( empty( $metadata['file'] ) ) {
     1919                return $image;
     1920        }
     1921
     1922        // Only alter images with a `sources` attribute
     1923        if ( empty( $metadata['sources'] ) ) {
     1924                return $image;
     1925        };
     1926
     1927        $target_mimes = array( 'image/webp', 'image/jpeg' );
     1928
     1929        /**
     1930         * Filter the content image mime type output selection and order.
     1931         *
     1932         * When outputting images in the content, the first mime type available will be used.
     1933         *
     1934         * @since 6.1.0
     1935         *
     1936         * @param array  $target_mimes  The image output mime type and order. Default is array( 'image/webp', 'image/jpeg' ).
     1937         * @param int    $attachment_id The attachment ID.
     1938         * @param string $context       Additional context to pass to the filters.
     1939         * @return array The filtered output mime type and order. Return an empty array to skip mime type substitution.
     1940         */
     1941        $target_mimes = apply_filters( 'wp_content_image_mimes', $target_mimes, $attachment_id, $context );
     1942
     1943        if ( false === $target_mimes ) {
     1944                return $image;
     1945        }
     1946
     1947        // Find the appropriate size for the provided URL in the first available mime type.
     1948        foreach ( $target_mimes as $target_mime ) {
     1949                // Handle full size image replacement.
     1950                if ( ! empty( $metadata['sources'][ $target_mime ]['file'] ) ) {
     1951                        $src_filename = wp_basename( $metadata['file'] );
     1952
     1953                        // This is the same MIME type as the original, so the entire $target_mime can be skipped.
     1954                        // Since it is already the preferred MIME type, the entire loop can be cancelled.
     1955                        if ( $metadata['sources'][ $target_mime ]['file'] === $src_filename ) {
     1956                                break;
     1957                        }
     1958
     1959                        $image = str_replace( $src_filename, $metadata['sources'][ $target_mime ]['file'], $image );
     1960
     1961                        // The full size was replaced, so unset this entirely here so that in the next iteration it is no longer
     1962                        // considered, simply for a small performance optimization.
     1963                        unset( $metadata['sources'] );
     1964                }
     1965
     1966                // Go through each image size and replace with the first available mime type version.
     1967                foreach ( $metadata['sizes'] as $name => $size_data ) {
     1968                        // Check if size has an original file.
     1969                        if ( empty( $size_data['file'] ) ) {
     1970                                continue;
     1971                        }
     1972
     1973                        // Check if size has a source in the desired mime type.
     1974                        if ( empty( $size_data['sources'][ $target_mime ]['file'] ) ) {
     1975                                continue;
     1976                        }
     1977
     1978                        $src_filename = wp_basename( $size_data['file'] );
     1979
     1980                        // This is the same MIME type as the original, so the entire $target_mime can be skipped.
     1981                        // Since it is already the preferred MIME type, the entire loop can be cancelled.
     1982                        if ( $size_data['sources'][ $target_mime ]['file'] === $src_filename ) {
     1983                                break 2;
     1984                        }
     1985
     1986                        // Found a match, replace with the new filename.
     1987                        $image = str_replace( $src_filename, $size_data['sources'][ $target_mime ]['file'], $image );
     1988
     1989                        // This size was replaced, so unset this entirely here so that in the next iteration it is no longer
     1990                        // considered, simply for a small performance optimization.
     1991                        unset( $metadata['sizes'][ $name ] );
     1992                }
     1993        }
     1994        return $image;
     1995}
     1996
     1997/**
     1998 * Check if execution is currently in the front end content context, outside of <head>.
     1999 *
     2000 * @since 6.1.0
     2001 * @access private
     2002 *
     2003 * @return bool True if in the front end content context, false otherwise.
     2004 */
     2005function _wp_in_front_end_context() {
     2006        global $wp_query;
     2007
     2008        // Check if this request is generally outside (or before) any frontend context.
     2009        if ( ! isset( $wp_query ) || defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || is_feed() ) {
     2010                return false;
     2011        }
     2012
     2013        // Check if we're anywhere before the 'wp_head' action has completed.
     2014        return did_action( 'template_redirect' ) && ! doing_action( 'wp_head' );
     2015}
     2016
    19012017/**
    19022018 * Adds `loading` attribute to an `img` HTML tag.
    19032019 *
  • src/wp-includes/post.php

    diff --git src/wp-includes/post.php src/wp-includes/post.php
    index b20dfc603f..4e221e726f 100644
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    64596459                $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
    64606460
    64616461                foreach ( $meta['sizes'] as $size => $sizeinfo ) {
    6462                         $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
    64636462
    6464                         if ( ! empty( $intermediate_file ) ) {
    6465                                 $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6463                        // Check for alternate size mime types in the sizeinfo['sources'] array to delete.
     6464                        if ( isset( $sizeinfo['sources'] ) && is_array( $sizeinfo['sources'] ) ) {
     6465                                foreach ( $sizeinfo['sources'] as $mime => $properties ) {
     6466                                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6467                                        if ( ! empty( $intermediate_file ) ) {
     6468                                                $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6469                                                if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6470                                                        $deleted = false;
     6471                                                }
     6472                                        }
     6473                                }
     6474                        } else {
     6475                                // Otherwise, delete files from the sizeinfo data.
     6476                                $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
    64666477
    6467                                 if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
    6468                                         $deleted = false;
     6478                                if ( ! empty( $intermediate_file ) ) {
     6479                                        $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6480
     6481                                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6482                                                $deleted = false;
     6483                                        }
    64696484                                }
    64706485                        }
    64716486                }
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    64876502                }
    64886503        }
    64896504
     6505        // Delete the full size images from 'sources' if available, or the root file.
     6506        if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
     6507                $sources          = $meta['sources'];
     6508                $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
     6509                foreach ( $sources as $mime => $properties ) {
     6510                        if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
     6511                                continue;
     6512                        }
     6513                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6514                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6515                                $deleted = false;
     6516                        }
     6517                }
     6518        } else {
     6519                if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
     6520                        $deleted = false;
     6521                }
     6522        }
     6523
    64906524        if ( is_array( $backup_sizes ) ) {
    6491                 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
    64926525
     6526                $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
     6527                // Delete the root (edited) file which was not deleted above.
     6528                if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
     6529                        $deleted = false;
     6530                }
    64936531                foreach ( $backup_sizes as $size ) {
    6494                         $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
    6495 
    6496                         if ( ! empty( $del_file ) ) {
    6497                                 $del_file = path_join( $uploadpath['basedir'], $del_file );
     6532                        // Delete files from 'sources' data if available, otherwise from 'sizes' data.
     6533                        if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
     6534                                // Delete any backup images stored in the 'sources' array.
     6535                                if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
     6536                                        foreach ( $size['sources'] as $mime => $properties ) {
     6537                                                $del_file = path_join( dirname( $meta['file'] ), $properties['file'] );
     6538                                                if ( ! empty( $del_file ) ) {
     6539                                                        $del_file = path_join( $uploadpath['basedir'], $del_file );
     6540                                                        if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
     6541                                                                $deleted = false;
     6542                                                        }
     6543                                                }
     6544                                        }
     6545                                }
     6546                        } else {
     6547                                $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
    64986548
    6499                                 if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
    6500                                         $deleted = false;
     6549                                if ( ! empty( $del_file ) ) {
     6550                                        $del_file = path_join( $uploadpath['basedir'], $del_file );
     6551                                        if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
     6552                                                $deleted = false;
     6553                                        }
    65016554                                }
    65026555                        }
    65036556                }
    65046557        }
    65056558
    6506         if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
    6507                 $deleted = false;
    6508         }
    6509 
    65106559        return $deleted;
    65116560}
    65126561
  • tests/phpunit/tests/image/editor.php

    diff --git tests/phpunit/data/images/test-image-rotated-90ccw.jpg tests/phpunit/data/images/test-image-rotated-90ccw.jpg
    new file mode 100644
    index 0000000000..b579b7f9ab
    Binary files /dev/null and tests/phpunit/data/images/test-image-rotated-90ccw.jpg differ
    diff --git tests/phpunit/data/images/test-image-rotated-90cw.webp tests/phpunit/data/images/test-image-rotated-90cw.webp
    new file mode 100644
    index 0000000000..82e77bed08
    Binary files /dev/null and tests/phpunit/data/images/test-image-rotated-90cw.webp differ
    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..742a78adfb 100644
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    226226                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) );
    227227
    228228                // Test with a suffix only.
    229                 $this->assertSame( 'canola-100x50.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
     229                $this->assertSame( 'canola-100x50-jpg.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
    230230
    231231                // Combo!
    232                 $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
     232                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new-jpg.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
    233233
    234234                // Test with a stream destination.
    235235                $this->assertSame( 'file://testing/path/canola-100x50.jpg', $editor->generate_filename( null, 'file://testing/path' ) );
    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.1.0
     407         */
     408        public function it_should_not_create_the_sources_property_if_no_transform_is_provided() {
     409                add_filter( 'wp_upload_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.1.0
     427         */
     428        public function it_should_create_the_sources_property_when_no_transform_is_available() {
     429                add_filter(
     430                        'wp_upload_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.1.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                        'wp_upload_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         * Do not create the sources property when opting out by size.
     480         * @since 6.1.0
     481         */
     482        public function it_should_not_create_the_sources_property_when_opting_out_by_size() {
     483                // Exclude the large image from mime transforms.
     484                add_filter(
     485                        'wp_upload_image_mime_transforms',
     486                        function ( $transforms, $attachment_id, $size_name ) {
     487                                if ( 'large' === $size_name ) {
     488                                        return array();
     489                                }
     490                                return $transforms;
     491                        },
     492                        10,
     493                        3
     494                );
     495
     496                $attachment_id = $this->factory->attachment->create_upload_object(
     497                        DIR_TESTDATA . '/images/test-image.jpg'
     498                );
     499
     500                $metadata = wp_get_attachment_metadata( $attachment_id );
     501
     502                $this->assertIsArray( $metadata );
     503                // Expect sources data for every size except the large size.
     504                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     505                        if ( 'large' === $size_name ) {
     506                                $this->assertArrayNotHasKey( 'sources', $properties );
     507                        } else {
     508                                $this->assertArrayHasKey( 'sources', $properties );
     509
     510                        }
     511                }
     512        }
     513
     514
     515
     516        /**
     517         * Create a WebP version with all the required properties
     518         *
     519         * @since 6.0.0
     520         */
     521        public function it_should_create_a_webp_version_with_all_the_required_properties() {
     522                $attachment_id = $this->factory->attachment->create_upload_object(
     523                        DIR_TESTDATA . '/images/test-image.jpg'
     524                );
     525
     526                $metadata = wp_get_attachment_metadata( $attachment_id );
     527                $this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] );
     528                $this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] );
     529                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     530                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     531                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] );
     532                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     533                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     534                $this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     535                $this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     536        }
     537
     538        /**
     539         * Remove `scaled` suffix from the generated filename
     540         *
     541         * @since 6.0.0
     542         */
     543        public function it_should_remove_scaled_suffix_from_the_generated_filename() {
     544                // The leafs image is 1080 pixels wide with this filter we ensure a -scaled version is created.
     545                add_filter(
     546                        'big_image_size_threshold',
     547                        function () {
     548                                return 850;
     549                        }
     550                );
     551
     552                $attachment_id = $this->factory->attachment->create_upload_object(
     553                        DIR_TESTDATA . '/images/test-image.jpg'
     554                );
     555                $metadata      = wp_get_attachment_metadata( $attachment_id );
     556                $this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) );
     557                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] );
     558                $this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     559                $this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     560        }
     561
     562        /**
     563         * Remove the generated webp images when the attachment is deleted
     564         *
     565         * @since 6.0.0
     566         */
     567        public function it_should_remove_the_generated_webp_images_when_the_attachment_is_deleted() {
     568                // Make sure no editor is available.
     569                $attachment_id = $this->factory->attachment->create_upload_object(
     570                        DIR_TESTDATA . '/images/test-image.jpg'
     571                );
     572
     573                $file    = get_attached_file( $attachment_id, true );
     574                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     575
     576                $this->assertIsString( $file );
     577                $this->assertFileExists( $file );
     578
     579                $metadata = wp_get_attachment_metadata( $attachment_id );
     580                $sizes    = array( 'thumbnail', 'medium' );
     581
     582                foreach ( $sizes as $size_name ) {
     583                        $this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] );
     584                        $this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] );
     585                        $this->assertFileExists(
     586                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     587                        );
     588                }
     589
     590                wp_delete_attachment( $attachment_id );
     591
     592                foreach ( $sizes as $size_name ) {
     593                        $this->assertFileDoesNotExist(
     594                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     595                        );
     596                }
     597        }
     598
     599        /**
     600         * Remove the attached WebP version if the attachment is force deleted but empty trash day is not defined
     601         *
     602         * @since 6.0.0
     603         */
     604        public function it_should_remove_the_attached_webp_version_if_the_attachment_is_force_deleted_but_empty_trash_day_is_not_defined() {
     605                // Make sure no editor is available.
     606                $attachment_id = $this->factory->attachment->create_upload_object(
     607                        DIR_TESTDATA . '/images/test-image.jpg'
     608                );
     609
     610                $file    = get_attached_file( $attachment_id, true );
     611                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     612
     613                $this->assertIsString( $file );
     614                $this->assertFileExists( $file );
     615
     616                $metadata = wp_get_attachment_metadata( $attachment_id );
     617
     618                $this->assertFileExists(
     619                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     620                );
     621
     622                wp_delete_attachment( $attachment_id, true );
     623
     624                $this->assertFileDoesNotExist(
     625                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     626                );
     627        }
     628
     629        /**
     630         * Remove the WebP version of the image if the image is force deleted and empty trash days is set to zero
     631         *
     632         * @since 6.0.0
     633         */
     634        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() {
     635                // Make sure no editor is available.
     636                $attachment_id = $this->factory->attachment->create_upload_object(
     637                        DIR_TESTDATA . '/images/test-image.jpg'
     638                );
     639
     640                $file    = get_attached_file( $attachment_id, true );
     641                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     642
     643                $this->assertIsString( $file );
     644                $this->assertFileExists( $file );
     645
     646                $metadata = wp_get_attachment_metadata( $attachment_id );
     647
     648                $this->assertFileExists(
     649                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     650                );
     651
     652                define( 'EMPTY_TRASH_DAYS', 0 );
     653
     654                wp_delete_attachment( $attachment_id, true );
     655
     656                $this->assertFileDoesNotExist(
     657                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     658                );
     659        }
     660
     661        /**
     662         * Avoid the change of URLs of images that are not part of the media library
     663         *
     664         * @group webp_uploads_update_image_references
     665         *
     666         * @since 6.0.0
     667         */
     668        public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
     669                $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>';
     670
     671                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     672        }
     673
     674        /**
     675         * Avoid replacing not existing attachment IDs
     676         *
     677         * @group webp_uploads_update_image_references
     678         *
     679         * @since 6.0.0
     680         */
     681        public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
     682                $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>';
     683
     684                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     685        }
     686
     687        /**
     688         * Prevent replacing a WebP image
     689         *
     690         * @group webp_uploads_update_image_references
     691         *
     692         * @since 6.0.0
     693         */
     694        public function it_should_prevent_replacing_a_webp_image() {
     695                $attachment_id = $this->factory->attachment->create_upload_object(
     696                        DIR_TESTDATA . '/images/webp-lossy.webp'
     697                );
     698
     699                $tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     700
     701                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     702        }
     703
     704        /**
     705         * Prevent replacing a jpg image if the image does not have the target class name
     706         *
     707         * @since 6.0.0
     708         */
     709        public function it_should_prevent_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
     710                $attachment_id = $this->factory->attachment->create_upload_object(
     711                        DIR_TESTDATA . '/images/test-image.jpg'
     712                );
     713
     714                $tag = wp_get_attachment_image( $attachment_id, 'medium' );
     715
     716                $this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
     717        }
     718
     719        /**
     720         * Replace the references to a JPG image to a WebP version
     721         *
     722         * @dataProvider provider_replace_images_with_different_extensions
     723         * @group webp_uploads_update_image_references
     724         *
     725         * @since 6.0.0
     726         */
     727        public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
     728                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     729
     730                $tag          = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     731                $expected_tag = $tag;
     732                $metadata     = wp_get_attachment_metadata( $attachment_id );
     733                foreach ( $metadata['sizes'] as $size => $properties ) {
     734                        $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
     735                }
     736
     737                $this->assertNotEmpty( $expected_tag );
     738                $this->assertNotSame( $tag, $expected_tag );
     739                $this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     740        }
     741
     742        public function provider_replace_images_with_different_extensions() {
     743                yield 'An image with a .jpg extension' => array( DIR_TESTDATA . '/images/test-image.jpg' );
     744                yield 'An image with a .jpeg extension' => array( DIR_TESTDATA . '/images/test-image.jpeg' );
     745        }
     746
     747        /**
     748         * Contain the full image size from the original mime
     749         *
     750         * @group webp_uploads_update_image_references
     751         *
     752         * @since 6.0.0
     753         */
     754        public function it_should_contain_the_full_image_size_from_the_original_mime() {
     755                $attachment_id = $this->factory->attachment->create_upload_object(
     756                        DIR_TESTDATA . '/images/test-image.jpg'
     757                );
     758
     759                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     760
     761                $expected = array(
     762                        'ext'  => 'jpg',
     763                        'type' => 'image/jpeg',
     764                );
     765                $this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
     766                $this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     767        }
     768
     769        /**
     770         * Prevent replacing an image with no available sources
     771         *
     772         * @group webp_uploads_update_image_references
     773         *
     774         * @since 6.0.0
     775         */
     776        public function it_should_prevent_replacing_an_image_with_no_available_sources() {
     777                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     778
     779                $attachment_id = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.jpg' );
     780
     781                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     782                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     783        }
     784
     785        /**
     786         * Prevent update not supported images with no available sources
     787         *
     788         * @dataProvider data_provider_not_supported_webp_images
     789         * @group webp_uploads_update_image_references
     790         *
     791         * @since 6.0.0
     792         */
     793        public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
     794                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     795
     796                $this->assertIsNumeric( $attachment_id );
     797                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     798
     799                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     800        }
     801
     802        public function data_provider_not_supported_webp_images() {
     803                yield 'PNG image' => array( DIR_TESTDATA . '/images/test-image.png' );
     804                yield 'GIFT image' => array( DIR_TESTDATA . '/images/test-image.gif' );
     805        }
     806
     807
    364808
    365809}
  • tests/phpunit/tests/image/functions.php

    diff --git tests/phpunit/tests/image/functions.php tests/phpunit/tests/image/functions.php
    index 86d559145e..57650e7a0b 100644
    class Tests_Image_Functions extends WP_UnitTestCase { 
    639639                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
    640640                }
    641641
     642                // Use legacy JPEG output.
     643                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     644
    642645                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    643646                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    644647                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    659662                $this->assertNotEmpty( $attachment_id );
    660663
    661664                $temp_dir = get_temp_dir();
    662 
    663665                $metadata = wp_generate_attachment_metadata( $attachment_id, $test_file );
    664666
    665667                $expected = array(
    class Tests_Image_Functions extends WP_UnitTestCase { 
    677679                                        'height'    => 300,
    678680                                        'mime-type' => 'image/jpeg',
    679681                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     682                                        'sources'   => array(
     683                                                'image/jpeg' => array(
     684                                                        'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
     685                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     686                                                ),
     687                                        ),
    680688                                ),
    681689                                'large'     => array(
    682690                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    684692                                        'height'    => 1024,
    685693                                        'mime-type' => 'image/jpeg',
    686694                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     695                                        'sources'   => array(
     696                                                'image/jpeg' => array(
     697                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     698                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     699                                                ),
     700                                        ),
    687701                                ),
    688702                                'thumbnail' => array(
    689703                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    691705                                        'height'    => 150,
    692706                                        'mime-type' => 'image/jpeg',
    693707                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     708                                        'sources'   => array(
     709                                                'image/jpeg' => array(
     710                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     711                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     712                                                ),
     713                                        ),
    694714                                ),
    695715                        ),
    696716                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    702722                foreach ( $metadata['sizes'] as $size ) {
    703723                        unlink( $temp_dir . $size['file'] );
    704724                }
     725                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    705726        }
    706727
    707728        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    716737
    717738                update_option( 'medium_crop', 1 );
    718739
     740                // Use legacy JPEG output.
     741                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     742
    719743                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    720744                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    721745                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    754778                                        'height'    => 300,
    755779                                        'mime-type' => 'image/jpeg',
    756780                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     781                                        'sources'   => array(
     782                                                'image/jpeg' => array(
     783                                                        'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
     784                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     785                                                ),
     786                                        ),
    757787                                ),
    758788                                'large'     => array(
    759789                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    761791                                        'height'    => 1024,
    762792                                        'mime-type' => 'image/jpeg',
    763793                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     794                                        'sources'   => array(
     795                                                'image/jpeg' => array(
     796                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     797                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     798                                                ),
     799                                        ),
     800
    764801                                ),
    765802                                'thumbnail' => array(
    766803                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    768805                                        'height'    => 150,
    769806                                        'mime-type' => 'image/jpeg',
    770807                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     808                                        'sources'   => array(
     809                                                'image/jpeg' => array(
     810                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     811                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     812                                                ),
     813                                        ),
    771814                                ),
    772815                        ),
    773816                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    779822                foreach ( $metadata['sizes'] as $size ) {
    780823                        unlink( $temp_dir . $size['file'] );
    781824                }
     825                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     826
    782827        }
    783828
    784829        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    789834                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
    790835                }
    791836
     837                // Use legacy JPEG output.
     838                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     839
    792840                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    793841                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    794842                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    821869                        'height'    => 100,
    822870                        'mime-type' => 'image/jpeg',
    823871                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     872                        'sources'   => array(
     873                                'image/jpeg' => array(
     874                                        'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
     875                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     876                                ),
     877                        ),
    824878                );
    825879
    826880                // Different environments produce slightly different filesize results.
    class Tests_Image_Functions extends WP_UnitTestCase { 
    836890                foreach ( $metadata['sizes'] as $size ) {
    837891                        unlink( $temp_dir . $size['file'] );
    838892                }
     893                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    839894        }
    840895
    841896        public function filter_fallback_intermediate_image_sizes( $fallback_sizes, $metadata ) {
    class Tests_Image_Functions extends WP_UnitTestCase { 
    10261081                        ),
    10271082                );
    10281083        }
     1084
     1085        /**
     1086         * @ticket 55443
     1087         */
     1088        public function test_wp_upload_image_mime_transforms_generates_webp_and_jpeg_for_both_by_default() {
     1089                $result = wp_upload_image_mime_transforms( 42, 'large' );
     1090                $this->assertArrayHasKey( 'image/jpeg', $result );
     1091                $this->assertArrayHasKey( 'image/webp', $result );
     1092                $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/jpeg'] );
     1093                $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/webp'] );
     1094        }
     1095
     1096        /**
     1097         * @ticket 55443
     1098         */
     1099        public function test_wp_upload_image_mime_transforms_filter_always_use_webp_instead_of_jpeg() {
     1100                add_filter(
     1101                        'wp_upload_image_mime_transforms',
     1102                        function( $transforms ) {
     1103                                // Ensure JPG only results in WebP files.
     1104                                $transforms['image/jpeg'] = array( 'image/webp' );
     1105                                // Unset WebP since it does not need any transformation in that case.
     1106                                unset( $transforms['image/webp'] );
     1107                                return $transforms;
     1108                        }
     1109                );
     1110
     1111                $result = wp_upload_image_mime_transforms( 42, 'large' );
     1112                $this->assertArrayHasKey( 'image/jpeg', $result );
     1113                $this->assertArrayNotHasKey( 'image/webp', $result );
     1114                $this->assertSameSets( array( 'image/webp' ), $result['image/jpeg'] );
     1115        }
     1116
     1117        /**
     1118         * @ticket 55443
     1119         */
     1120        public function test_wp_upload_image_mime_transforms_filter_receives_parameters() {
     1121                $attachment_id = null;
     1122                $image_size    = null;
     1123                add_filter(
     1124                        'wp_upload_image_mime_transforms',
     1125                        function( $transforms, $param1, $param2 ) use ( &$attachment_id, &$image_size ) {
     1126                                $attachment_id = $param1;
     1127                                $image_size    = $param2;
     1128                                return $transforms;
     1129                        },
     1130                        10,
     1131                        3
     1132                );
     1133
     1134                wp_upload_image_mime_transforms( 23, 'medium' );
     1135                $this->assertSame( 23, $attachment_id );
     1136                $this->assertSame( 'medium', $image_size );
     1137        }
     1138
     1139        /**
     1140         * @ticket 55443
     1141         */
     1142        public function test_wp_upload_image_mime_transforms_filter_with_empty_array() {
     1143                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1144                $result = wp_upload_image_mime_transforms( 42, 'large' );
     1145                $this->assertSame( array(), $result );
     1146        }
     1147
     1148        /**
     1149         * @ticket 55443
     1150         */
     1151        public function test_wp_upload_image_mime_transforms_filter_with_invalid_usage() {
     1152                $default = wp_upload_image_mime_transforms( 42, 'large' );
     1153
     1154                add_filter( 'wp_upload_image_mime_transforms', '__return_false' );
     1155                $result = wp_upload_image_mime_transforms( 42, 'large' );
     1156                $this->assertSame( $default, $result );
     1157        }
     1158
     1159        /**
     1160         * @ticket 55443
     1161         */
     1162        public function test__wp_get_primary_and_additional_mime_types_default() {
     1163                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1164
     1165                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1166                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1167
     1168                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1169                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1170                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1171                } else {
     1172                        $this->assertSame( array(), $additional_mime_types );
     1173                }
     1174        }
     1175
     1176        /**
     1177         * @ticket 55443
     1178         */
     1179        public function test__wp_get_primary_and_additional_mime_types_prefer_original_mime() {
     1180                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1181
     1182                // Set 'image/jpeg' only as secondary output MIME type.
     1183                // Still, because it is the original, it should be chosen as primary over 'image/webp'.
     1184                add_filter(
     1185                        'wp_upload_image_mime_transforms',
     1186                        function( $transforms ) {
     1187                                $transforms['image/jpeg'] = array( 'image/webp', 'image/jpeg' );
     1188                                return $transforms;
     1189                        }
     1190                );
     1191
     1192                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1193                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1194
     1195                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1196                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1197                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1198                } else {
     1199                        $this->assertSame( array(), $additional_mime_types );
     1200                }
     1201        }
     1202
     1203        /**
     1204         * @ticket 55443
     1205         */
     1206        public function test__wp_get_primary_and_additional_mime_types_use_original_mime_when_no_transformation_rules() {
     1207                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1208
     1209                // Strip all transformation rules.
     1210                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1211
     1212                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1213                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1214                $this->assertSame( array(), $additional_mime_types );
     1215        }
     1216
     1217        /**
     1218         * @ticket 55443
     1219         */
     1220        public function test__wp_get_primary_and_additional_mime_types_different_output_mime() {
     1221                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1222
     1223                // Set 'image/webp' as the only output MIME type.
     1224                // In that case, JPEG is not generated at all, so WebP becomes the primary MIME type.
     1225                add_filter(
     1226                        'wp_upload_image_mime_transforms',
     1227                        function( $transforms ) {
     1228                                $transforms['image/jpeg'] = array( 'image/webp' );
     1229                                return $transforms;
     1230                        }
     1231                );
     1232
     1233                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1234
     1235                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1236                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1237                        $this->assertSame( 'image/webp', $primary_mime_type );
     1238                } else {
     1239                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1240                }
     1241
     1242                $this->assertSame( array(), $additional_mime_types );
     1243        }
     1244
     1245        /**
     1246         * @ticket 55443
     1247         */
     1248        public function test__wp_get_primary_and_additional_mime_types_different_output_mimes() {
     1249                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1250
     1251                // Set 'image/webp' and 'image/avif' as output MIME types.
     1252                // In that case, JPEG is not generated at all, with WebP being the primary MIME type and AVIF the secondary.
     1253                add_filter(
     1254                        'wp_upload_image_mime_transforms',
     1255                        function( $transforms ) {
     1256                                $transforms['image/jpeg'] = array( 'image/webp', 'image/avif' );
     1257                                return $transforms;
     1258                        }
     1259                );
     1260
     1261                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1262
     1263                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1264                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1265                        $this->assertSame( 'image/webp', $primary_mime_type );
     1266                } else {
     1267                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1268                }
     1269
     1270                // AVIF may not be supported by the server, in which case it will be stripped from the results.
     1271                if ( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
     1272                        $this->assertSame( array( 'image/avif' ), $additional_mime_types );
     1273                } else {
     1274                        $this->assertSame( array(), $additional_mime_types );
     1275                }
     1276        }
     1277
     1278        /**
     1279         * Test the `_wp_maybe_scale_and_rotate_image()` function.
     1280         *
     1281         * @dataProvider data_test__wp_maybe_scale_and_rotate_image
     1282         *
     1283         * @ticket 55443
     1284         */
     1285        public function test__wp_maybe_scale_and_rotate_image( $file, $imagesize, $mime_type, $expected ) {
     1286                if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
     1287                        $this->markTestSkipped( sprintf( 'This test requires %s support.', $mime_type ) );
     1288                }
     1289
     1290                $attributes    = array( 'post_mime_type' => $mime_type );
     1291                $attachment_id = $this->factory->attachment->create_object( $file, 0, $attributes );
     1292                $exif_meta     = wp_read_image_metadata( $file );
     1293
     1294                list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
     1295                if ( is_wp_error( $editor ) ) {
     1296                        $this->markTestSkipped( sprintf( 'Editor not supported for: %s.', $editor->get_error_data() ) );
     1297                }
     1298                $this->assertSame( $expected['rotated'], $rotated );
     1299                $this->assertSame( $expected['resized'], $resized );
     1300                $this->assertSame( $expected['size'], $editor->get_size() );
     1301        }
     1302
     1303        /**
     1304         * Data provider for the `test__wp_maybe_scale_and_rotate_image()` test.
     1305         *
     1306         * @return array
     1307         */
     1308        public function data_test__wp_maybe_scale_and_rotate_image() {
     1309                return array(
     1310
     1311                        // Image that will be scaled.
     1312                        array(
     1313                                DIR_TESTDATA . '/images/test-image-large.jpg',
     1314                                array( 3000, 2250 ),
     1315                                'image/jpeg',
     1316                                array(
     1317                                        'rotated' => false,
     1318                                        'resized' => true,
     1319                                        'size'    => array(
     1320                                                'width'  => 2560,
     1321                                                'height' => 1920,
     1322                                        ),
     1323                                ),
     1324                        ),
     1325
     1326                        // Image that will not be scaled.
     1327                        array(
     1328                                DIR_TESTDATA . '/images/canola.jpg',
     1329                                array( 640, 480 ),
     1330                                'image/jpeg',
     1331                                array(
     1332                                        'rotated' => false,
     1333                                        'resized' => false,
     1334                                        'size'    => array(
     1335                                                'width'  => 640,
     1336                                                'height' => 480,
     1337                                        ),
     1338                                ),
     1339                        ),
     1340
     1341                        // Image that will be flipped.
     1342                        array(
     1343                                DIR_TESTDATA . '/images/test-image-upside-down.jpg',
     1344                                array( 600, 450 ),
     1345                                'image/jpeg',
     1346                                array(
     1347                                        'rotated' => true,
     1348                                        'resized' => false,
     1349                                        'size'    => array(
     1350                                                'width'  => 600,
     1351                                                'height' => 450,
     1352                                        ),
     1353                                ),
     1354                        ),
     1355
     1356                        // Image that will be rotated.
     1357                        array(
     1358                                DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg',
     1359                                array( 1200, 1800 ),
     1360                                'image/jpeg',
     1361                                array(
     1362                                        'rotated' => true,
     1363                                        'resized' => false,
     1364                                        'size'    => array(
     1365                                                'width'  => 1800,
     1366                                                'height' => 1200,
     1367                                        ),
     1368                                ),
     1369                        ),
     1370
     1371                        // Image that will not be rotated - WebP Exif is not supported in PHP.
     1372                        array(
     1373                                DIR_TESTDATA . '/images/test-image-rotated-90cw.webp',
     1374                                array( 1024, 768 ),
     1375                                'image/jpeg',
     1376                                array(
     1377                                        'rotated' => false,
     1378                                        'resized' => false,
     1379                                        'size'    => array(
     1380                                                'width'  => 1024,
     1381                                                'height' => 768,
     1382                                        ),
     1383                                ),
     1384                        ),
     1385
     1386                );
     1387        }
     1388
     1389        /**
     1390         * Test the `_wp_get_image_suffix()` function.
     1391         * @dataProvider data_test__wp_get_image_suffix
     1392         *
     1393         * @ticket 55443
     1394         */
     1395        public function test__wp_get_image_suffix( $resized, $rotated, $expected ) {
     1396                $this->assertSame( $expected, _wp_get_image_suffix( $resized, $rotated ) );
     1397        }
     1398
     1399        /**
     1400         * Data provider for the `test__wp_get_image_suffix()` test.
     1401         */
     1402        public function data_test__wp_get_image_suffix() {
     1403                return array(
     1404                        array( false, false, '' ),
     1405                        array( true, false, 'scaled' ),
     1406                        array( false, true, 'rotated' ),
     1407                        array( true, true, 'scaled' ),
     1408                );
     1409        }
    10291410}
  • tests/phpunit/tests/media.php

    diff --git tests/phpunit/tests/media.php tests/phpunit/tests/media.php
    index 0341170721..592b19e328 100644
    EOF; 
    22512251                // Do not add width, height, and loading.
    22522252                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    22532253                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
     2254                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22542255
    22552256                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    22562257
    22572258                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    22582259                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
     2260                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
     2261
    22592262        }
    22602263
    22612264        /**
    EOF; 
    22892292                $img = wp_img_tag_add_loading_attr( $img, 'test' );
    22902293                $img = wp_img_tag_add_decoding_attr( $img, 'the_content' );
    22912294                $img = preg_replace( '|<img ([^>]+) />|', '<img $1 ' . 'srcset="image2x.jpg 2x" />', $img );
     2295                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22922296
    22932297                // The content filter should return the image unchanged.
    22942298                $this->assertSame( $img, wp_filter_content_tags( $img ) );
     2299
     2300                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    22952301        }
    22962302
    22972303        /**
    EOF; 
    23612367                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    23622368                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
    23632369                add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' );
     2370                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    23642371
    23652372                add_filter(
    23662373                        'wp_content_img_tag',
    EOF; 
    24232430         * @requires function imagejpeg
    24242431         */
    24252432        public function test_wp_filter_content_tags_schemes() {
     2433                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    24262434                $image_meta = wp_get_attachment_metadata( self::$large_id );
    24272435                $size_array = $this->get_image_size_array_from_meta( $image_meta, 'medium' );
    24282436
    EOF; 
    24682476                $actual = wp_filter_content_tags( $unfiltered );
    24692477
    24702478                $this->assertSame( $expected, $actual );
     2479                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    24712480        }
    24722481
    24732482        /**
    EOF; 
    29612970                // Do not add loading, srcset, and sizes.
    29622971                add_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
    29632972                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2973                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    29642974
    29652975                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    29662976
    29672977                remove_filter( 'wp_img_tag_add_loading_attr', '__return_false' );
    29682978                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     2979                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    29692980        }
    29702981
    29712982        /**
    EOF; 
    30413052                // Do not add width, height, srcset, and sizes.
    30423053                add_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    30433054                add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3055                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    30443056
    30453057                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    30463058
    30473059                remove_filter( 'wp_img_tag_add_width_and_height_attr', '__return_false' );
    30483060                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3061                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    30493062        }
    30503063
    30513064        /**
    EOF; 
    30743087                // Enable globally for all tags.
    30753088                add_filter( 'wp_lazy_loading_enabled', '__return_true' );
    30763089
     3090                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     3091
    30773092                $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
    30783093                remove_filter( 'wp_lazy_loading_enabled', '__return_true' );
    30793094                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3095                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
     3096
    30803097        }
    30813098
    30823099        /**
    EOF; 
    31013118                // Disable globally for all tags.
    31023119                add_filter( 'wp_lazy_loading_enabled', '__return_false' );
    31033120
     3121                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     3122
    31043123                $this->assertSame( $content, wp_filter_content_tags( $content ) );
    31053124                remove_filter( 'wp_lazy_loading_enabled', '__return_false' );
    31063125                remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3126                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    31073127        }
    31083128
    31093129        /**
    EOF; 
    35293549         */
    35303550        function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
    35313551                global $wp_query, $wp_the_query;
     3552                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
    35323553
    35333554                $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
    35343555                $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
    EOF; 
    35643585                        $content_filtered = wp_filter_content_tags( $content_unfiltered, 'the_content' );
    35653586                        remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
    35663587                }
     3588                remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    35673589
    35683590                // After filtering, the first image should not be lazy-loaded while the other ones should be.
    35693591                $this->assertSame( $content_expected, $content_filtered );
    EOF; 
    36133635                // Clean up the above filter.
    36143636                remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
    36153637        }
     3638
     3639        /**
     3640         * @ticket 55443
     3641         */
     3642        public function test_wp_image_use_alternate_mime_types_replaces_jpg_with_webp_where_available() {
     3643                if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     3644                        $this->markTestSkipped( 'This test requires WebP support.' );
     3645                }
     3646
     3647                // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
     3648                $tag          = wp_get_attachment_image( self::$large_id, 'full' );
     3649                $expected_tag = $tag;
     3650
     3651                $metadata = wp_get_attachment_metadata( self::$large_id );
     3652                foreach ( $metadata['sizes'] as $size => $properties ) {
     3653                        // Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
     3654                        if ( ! isset( $properties['sources']['image/webp'] ) ) {
     3655                                continue;
     3656                        }
     3657                        $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
     3658                }
     3659                // Same applies to the full size.
     3660                if ( isset( $metadata['sources']['image/webp'] ) ) {
     3661                        $expected_tag = str_replace( $metadata['sources']['image/jpeg']['file'], $metadata['sources']['image/webp']['file'], $expected_tag );
     3662                }
     3663
     3664                $this->assertNotSame( $tag, $expected_tag );
     3665                $this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3666        }
     3667
     3668        /**
     3669         * @ticket 55443
     3670         */
     3671        public function test_wp_image_use_alternate_mime_types_does_not_replace_jpg_when_webp_is_not_available() {
     3672                if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     3673                        $this->markTestSkipped( 'This test requires WebP support.' );
     3674                }
     3675
     3676                // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
     3677                $tag = wp_get_attachment_image( self::$large_id, 'full' );
     3678
     3679                // Update attachment metadata as if the image had no WebP available for any sub-sizes and the full size.
     3680                $metadata = wp_get_attachment_metadata( self::$large_id );
     3681                foreach ( $metadata['sizes'] as $size => $properties ) {
     3682                        unset( $metadata['sizes'][ $size ]['sources']['image/webp'] );
     3683                }
     3684                unset( $metadata['sources']['image/webp'] );
     3685                wp_update_attachment_metadata( self::$large_id, $metadata );
     3686
     3687                $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3688        }
     3689
     3690        /**
     3691         * @ticket 55443
     3692         */
     3693        public function test_wp_image_use_alternate_mime_types_still_replaces_jpg_subsizes_when_webp_is_not_available_for_full_size() {
     3694                if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     3695                        $this->markTestSkipped( 'This test requires WebP support.' );
     3696                }
     3697
     3698                // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
     3699                $tag          = wp_get_attachment_image( self::$large_id, 'full' );
     3700                $expected_tag = $tag;
     3701
     3702                // Update attachment metadata as if the image had no WebP available for the full size.
     3703                $metadata = wp_get_attachment_metadata( self::$large_id );
     3704                unset( $metadata['sources']['image/webp'] );
     3705                wp_update_attachment_metadata( self::$large_id, $metadata );
     3706
     3707                foreach ( $metadata['sizes'] as $size => $properties ) {
     3708                        // Some sizes may not have WebP if the WebP file is larger than the JPEG for the size.
     3709                        if ( ! isset( $properties['sources']['image/webp'] ) ) {
     3710                                continue;
     3711                        }
     3712                        $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
     3713                }
     3714
     3715                $this->assertNotSame( $tag, $expected_tag );
     3716                $this->assertSame( $expected_tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3717        }
     3718
     3719        /**
     3720         * @ticket 55443
     3721         */
     3722        public function test_wp_image_use_alternate_mime_types_respects_wp_content_image_mimes_filter() {
     3723                if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     3724                        $this->markTestSkipped( 'This test requires WebP support.' );
     3725                }
     3726
     3727                // The attachment $large_id is a JPEG image, so it gets WebP files generated by default.
     3728                $tag = wp_get_attachment_image( self::$large_id, 'full' );
     3729
     3730                // Invalid filter value results in no changes to content.
     3731                add_filter( 'wp_content_image_mimes', '__return_false' );
     3732                $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3733
     3734                // Empty array results in no changes to content.
     3735                add_filter( 'wp_content_image_mimes', '__return_empty_array' );
     3736                $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3737
     3738                // Preferring JPEG over WebP results in no changes to content.
     3739                add_filter(
     3740                        'wp_content_image_mimes',
     3741                        function() {
     3742                                return array( 'image/jpeg', 'image/webp' );
     3743                        }
     3744                );
     3745                $this->assertSame( $tag, wp_image_use_alternate_mime_types( $tag, 'the_content', self::$large_id ) );
     3746        }
     3747
     3748        /**
     3749         * @ticket 55443
     3750         */
     3751        public function test__wp_in_front_end_context_without_wp_query() {
     3752                unset( $GLOBALS['wp_query'] );
     3753
     3754                $this->assertFalse( _wp_in_front_end_context() );
     3755        }
     3756
     3757        /**
     3758         * @ticket 55443
     3759         */
     3760        public function test__wp_in_front_end_context_with_feed() {
     3761                remove_all_actions( 'template_redirect' );
     3762                do_action( 'template_redirect' );
     3763                $GLOBALS['wp_query']->is_feed = true;
     3764
     3765                $this->assertFalse( _wp_in_front_end_context() );
     3766        }
     3767
     3768        /**
     3769         * @ticket 55443
     3770         */
     3771        public function test__wp_in_front_end_context_before_and_after_template_redirect() {
     3772                $result = _wp_in_front_end_context();
     3773
     3774                remove_all_actions( 'template_redirect' );
     3775                do_action( 'template_redirect' );
     3776
     3777                $this->assertFalse( $result );
     3778                $this->assertTrue( _wp_in_front_end_context() );
     3779        }
     3780
     3781        /**
     3782         * @ticket 55443
     3783         */
     3784        public function test__wp_in_front_end_context_within_wp_head() {
     3785                remove_all_actions( 'template_redirect' );
     3786                do_action( 'template_redirect' );
     3787
     3788                // Call function within a 'wp_head' callback.
     3789                remove_all_actions( 'wp_head' );
     3790                $result = null;
     3791                add_action(
     3792                        'wp_head',
     3793                        function() use ( &$result ) {
     3794                                $result = _wp_in_front_end_context();
     3795                        }
     3796                );
     3797                do_action( 'wp_head' );
     3798
     3799                $this->assertFalse( $result );
     3800        }
    36163801}
    36173802
    36183803/**