Make WordPress Core

Ticket #55443: 55443.6.diff

File 55443.6.diff, 91.8 KB (added by adamsilverstein, 2 years ago)
  • src/js/_enqueues/vendor/plupload/handlers.js

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

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

    diff --git src/wp-admin/includes/image.php src/wp-admin/includes/image.php
    index 1a9c9e2e9c..75b4e48562 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_output_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_output_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         Associative array of image sizes.
     652 * @param int   $attachment_id Attachment ID.
     653 * @return array $sizes Filtered $sizes with only those 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 * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
     1462 *               generate.
     1463 */
     1464function wp_upload_image_mime_transforms( $attachment_id ) {
     1465        $default_image_mime_transforms = array(
     1466                'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
     1467                'image/webp' => array( 'image/webp', 'image/jpeg' ),
     1468        );
     1469        $image_mime_transforms         = $default_image_mime_transforms;
     1470
     1471        /**
     1472         * Filter the output mime types for a given input mime type and image size.
     1473         *
     1474         * @since 6.1.0
     1475         *
     1476         * @param array  $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
     1477         *                                      and the value is one or more mime file types to generate.
     1478         * @param int    $attachment_id         The ID of the attachment where the hook was dispatched.
     1479         */
     1480        $image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
     1481
     1482        if ( ! is_array( $image_mime_transforms ) ) {
     1483                return $default_image_mime_transforms;
     1484        }
     1485
     1486        return array_map(
     1487                function( $transforms_list ) {
     1488                        return (array) $transforms_list;
     1489                },
     1490                $image_mime_transforms
     1491        );
     1492}
     1493
     1494/**
     1495 * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
     1496 *
     1497 * @since 6.1.0
     1498 * @access private
     1499 *
     1500 * @param string $file          Full path to the image file.
     1501 * @param int    $attachment_id Attachment ID to process.
     1502 * @return array An array with two entries, the primary mime type and the list of additional mime types.
     1503 */
     1504function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
     1505        $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
     1506        $original_mime_type    = wp_get_image_mime( $file );
     1507        $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
     1508
     1509        // Exclude any output mime types that the system doesn't support.
     1510        $output_mime_types = array_values(
     1511                array_filter(
     1512                        $output_mime_types,
     1513                        function( $mime_type ) {
     1514                                return wp_image_editor_supports(
     1515                                        array(
     1516                                                'mime_type' => $mime_type,
     1517                                        )
     1518                                );
     1519                        }
     1520                )
     1521        );
     1522
     1523        // Handle an empty value for $output_mime_types: only output the original type.
     1524        if ( empty( $output_mime_types ) ) {
     1525                return array( $original_mime_type, array() );
     1526        }
     1527
     1528        // Use original mime type as primary mime type, or alternatively the first one.
     1529        $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
     1530        if ( false === $primary_mime_type_key ) {
     1531                $primary_mime_type_key = 0;
     1532        }
     1533        // Split output mime types into primary mime type and additional mime types.
     1534        $additional_mime_types     = $output_mime_types;
     1535        list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
     1536
     1537        return array(
     1538                $primary_mime_type,
     1539                $additional_mime_types,
     1540        );
     1541}
  • 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..5353ec1bba 100644
    abstract class WP_Image_Editor { 
    334334        protected function get_output_format( $filename = null, $mime_type = null ) {
    335335                $new_ext = null;
    336336
     337                // If no mime type is passed but output mime type is set, use that.
     338                if ( ! $mime_type && ! empty( $this->output_mime_type ) ) {
     339                        $mime_type = $this->output_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 { 
    425430        }
    426431
    427432        /**
    428          * Builds an output filename based on current file, and adding proper suffix
     433         * Builds an output filename based on current file, and adding proper suffix.
    429434         *
    430435         * @since 3.5.0
    431          *
    432          * @param string $suffix
    433          * @param string $dest_path
    434          * @param string $extension
    435          * @return string filename
     436         * @since 6.1.0 Skips adding a suffix when set to an empty string. When the
     437         *              file extension being generated doesn't match the image file extension,
     438         *              add the extension to the suffix
     439         *
     440         * @param string $suffix    Optional. Suffix to add to the filename. The default null
     441         *                          will result in a 'widthxheight' suffix. Passing
     442         *                          an empty string will result in no suffix.
     443         * @param string $dest_path Optional. The path to save the file to. The default null
     444         *                          will use the image file path.
     445         * @param string $extension Optional. The file extension to use. The default null
     446         *                          will use the image file extension.
     447         * @return string filename The generated file name.
    436448         */
    437449        public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
    438450                // $suffix will be appended to the destination filename, just before the extension.
    439                 if ( ! $suffix ) {
     451                if ( null === $suffix ) {
    440452                        $suffix = $this->get_suffix();
    441453                }
    442454
    abstract class WP_Image_Editor { 
    457469                        }
    458470                }
    459471
    460                 return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
     472                if ( empty( $suffix ) ) {
     473                        $suffix = '';
     474                } else {
     475                        $suffix = "-{$suffix}";
     476                }
     477
     478                // When the file extension being generated doesn't match the image file extension,
     479                // add the extension to the suffix to ensure a unique file name. Prevents
     480                // name conflicts when a single image type can have multiple extensions,
     481                // eg. .jpg, .jpg and .jpeg are all valid JPEG extensions.
     482                if ( ! empty( $extension ) && $extension !== $ext ) {
     483                        $suffix .= "-{$ext}";
     484                }
     485
     486                return trailingslashit( $dir ) . "{$name}{$suffix}.{$new_ext}";
    461487        }
    462488
    463489        /**
    abstract class WP_Image_Editor { 
    637663
    638664                return wp_get_default_extension_for_mime_type( $mime_type );
    639665        }
    640 }
    641666
     667        /**
     668         * Set the editor output mime type, useful when outputting alternate mime types.
     669         *
     670         * Track that the mime type is set with the mime type set flag.
     671         *
     672         * @since 6.1.0
     673         *
     674         * @param string $output_mime_type The mime type to set.
     675         */
     676        public function set_output_mime_type( $output_mime_type ) {
     677                $this->output_mime_type = $output_mime_type;
     678        }
     679
     680        /**
     681         * Reset the mime type to the original file mime type.
     682         *
     683         * Reset the mime type set flag.
     684         *
     685         * @since 6.1.0
     686         */
     687        public function reset_output_mime_type() {
     688                $this->output_mime_type = $this->mime_type;
     689        }
     690}
  • 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 9689e008ea..c7dcc13b8d 100644
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    64816481                $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
    64826482
    64836483                foreach ( $meta['sizes'] as $size => $sizeinfo ) {
    6484                         $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
    64856484
    6486                         if ( ! empty( $intermediate_file ) ) {
    6487                                 $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6485                        // Check for alternate size mime types in the sizeinfo['sources'] array to delete.
     6486                        if ( isset( $sizeinfo['sources'] ) && is_array( $sizeinfo['sources'] ) ) {
     6487                                foreach ( $sizeinfo['sources'] as $mime => $properties ) {
     6488                                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6489                                        if ( ! empty( $intermediate_file ) ) {
     6490                                                $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6491                                                if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6492                                                        $deleted = false;
     6493                                                }
     6494                                        }
     6495                                }
     6496                        } else {
     6497                                // Otherwise, delete files from the sizeinfo data.
     6498                                $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
    64886499
    6489                                 if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
    6490                                         $deleted = false;
     6500                                if ( ! empty( $intermediate_file ) ) {
     6501                                        $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
     6502
     6503                                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6504                                                $deleted = false;
     6505                                        }
    64916506                                }
    64926507                        }
    64936508                }
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    65096524                }
    65106525        }
    65116526
     6527        // Delete the full size images from 'sources' if available, or the root file.
     6528        if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
     6529                $sources          = $meta['sources'];
     6530                $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
     6531                foreach ( $sources as $mime => $properties ) {
     6532                        if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
     6533                                continue;
     6534                        }
     6535                        $intermediate_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6536                        if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
     6537                                $deleted = false;
     6538                        }
     6539                }
     6540        } else {
     6541                if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
     6542                        $deleted = false;
     6543                }
     6544        }
     6545
    65126546        if ( is_array( $backup_sizes ) ) {
    6513                 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
    65146547
     6548                $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
     6549                // Delete the root (edited) file which was not deleted above.
     6550                if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
     6551                        $deleted = false;
     6552                }
    65156553                foreach ( $backup_sizes as $size ) {
    6516                         $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
    6517 
    6518                         if ( ! empty( $del_file ) ) {
    6519                                 $del_file = path_join( $uploadpath['basedir'], $del_file );
     6554                        // Delete files from 'sources' data if available, otherwise from 'sizes' data.
     6555                        if ( isset( $meta['sources'] ) && is_array( $meta['sources'] ) ) {
     6556                                // Delete any backup images stored in the 'sources' array.
     6557                                if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
     6558                                        foreach ( $size['sources'] as $mime => $properties ) {
     6559                                                $del_file = path_join( dirname( $meta['file'] ), $properties['file'] );
     6560                                                if ( ! empty( $del_file ) ) {
     6561                                                        $del_file = path_join( $uploadpath['basedir'], $del_file );
     6562                                                        if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
     6563                                                                $deleted = false;
     6564                                                        }
     6565                                                }
     6566                                        }
     6567                                }
     6568                        } else {
     6569                                $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
    65206570
    6521                                 if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
    6522                                         $deleted = false;
     6571                                if ( ! empty( $del_file ) ) {
     6572                                        $del_file = path_join( $uploadpath['basedir'], $del_file );
     6573                                        if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
     6574                                                $deleted = false;
     6575                                        }
    65236576                                }
    65246577                        }
    65256578                }
    65266579        }
    65276580
    6528         if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
    6529                 $deleted = false;
    6530         }
    6531 
    65326581        return $deleted;
    65336582}
    65346583
  • 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..2115fe2594 100644
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    131131                $this->assertSame( 86, $editor->get_quality(), 'Output image format is WEBP. Quality setting for it should be 86.' );
    132132
    133133                // Removing PNG to WEBP conversion on save. Quality setting should reset to the default.
     134                $editor->reset_output_mime_type();
    134135                remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
    135136                $editor->save();
    136137                $this->assertSame( 82, $editor->get_quality(), 'After removing image conversion quality setting should reset to the default of 82.' );
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    154155                $this->assertSame( 42, $editor->get_quality(), 'Image conversion from JPEG to WEBP. Filtered WEBP quality shoild be 42.' );
    155156
    156157                // After removing the conversion the quality setting should reset to the filtered value for the original image type, JPEG.
     158                $editor->reset_output_mime_type();
    157159                remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_formats' ) );
    158160                $editor->save();
    159161                $this->assertSame(
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    226228                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ), trailingslashit( realpath( dirname( $editor->generate_filename( null, get_temp_dir() ) ) ) ) );
    227229
    228230                // Test with a suffix only.
    229                 $this->assertSame( 'canola-100x50.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
     231                $this->assertSame( 'canola-100x50-jpg.png', wp_basename( $editor->generate_filename( null, null, 'png' ) ) );
    230232
    231233                // Combo!
    232                 $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
     234                $this->assertSame( trailingslashit( realpath( get_temp_dir() ) ) . 'canola-new-jpg.png', $editor->generate_filename( 'new', realpath( get_temp_dir() ), 'png' ) );
    233235
    234236                // Test with a stream destination.
    235237                $this->assertSame( 'file://testing/path/canola-100x50.jpg', $editor->generate_filename( null, 'file://testing/path' ) );
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    362364                );
    363365        }
    364366
     367        /**
     368         * Test creating  the original image mime type when the image is uploaded.
     369         *
     370         * @ticket 55443
     371         *
     372         * @dataProvider provider_image_with_default_behaviors_during_upload
     373         */
     374        public function it_should_create_the_original_image_mime_type_when_the_image_is_uploaded( $file_location, $expected_mime, $targeted_mime ) {
     375                $attachment_id = $this->factory->attachment->create_upload_object( $file_location );
     376
     377                $metadata = wp_get_attachment_metadata( $attachment_id );
     378
     379                $this->assertIsArray( $metadata );
     380                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     381                        $this->assertArrayHasKey( 'sources', $properties );
     382                        $this->assertIsArray( $properties['sources'] );
     383                        $this->assertArrayHasKey( $expected_mime, $properties['sources'] );
     384                        $this->assertArrayHasKey( 'filesize', $properties['sources'][ $expected_mime ] );
     385                        $this->assertArrayHasKey( 'file', $properties['sources'][ $expected_mime ] );
     386                        $this->assertArrayHasKey( $targeted_mime, $properties['sources'] );
     387                        $this->assertArrayHasKey( 'filesize', $properties['sources'][ $targeted_mime ] );
     388                        $this->assertArrayHasKey( 'file', $properties['sources'][ $targeted_mime ] );
     389                }
     390        }
     391
     392        /**
     393         * Data provider for it_should_create_the_original_image_mime_type_when_the_image_is_uploaded.
     394         */
     395        public function provider_image_with_default_behaviors_during_upload() {
     396                yield 'JPEG image' => array(
     397                        DIR_TESTDATA . '/images/test-image.jpg',
     398                        'image/jpeg',
     399                        'image/webp',
     400                );
     401
     402                yield 'WebP image' => array(
     403                        DIR_TESTDATA . '/images/webp-lossy.webp',
     404                        'image/webp',
     405                        'image/jpeg',
     406                );
     407        }
     408
     409        /**
     410         * Test Do not create the sources property if no transform is provided.
     411         *
     412         * @ticket 55443
     413         */
     414        public function it_should_not_create_the_sources_property_if_no_transform_is_provided() {
     415                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     416
     417                $attachment_id = $this->factory->attachment->create_upload_object(
     418                        DIR_TESTDATA . '/images/test-image.jpg'
     419                );
     420
     421                $metadata = wp_get_attachment_metadata( $attachment_id );
     422
     423                $this->assertIsArray( $metadata );
     424                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     425                        $this->assertArrayNotHasKey( 'sources', $properties );
     426                }
     427        }
     428
     429        /**
     430         * Test creating the sources property when no transform is available.
     431         *
     432         * @ticket 55443
     433         */
     434        public function it_should_create_the_sources_property_when_no_transform_is_available() {
     435                add_filter(
     436                        'wp_upload_image_mime_transforms',
     437                        function () {
     438                                return array( 'image/jpeg' => array() );
     439                        }
     440                );
     441
     442                $attachment_id = $this->factory->attachment->create_upload_object(
     443                        DIR_TESTDATA . '/images/test-image.jpg'
     444                );
     445
     446                $metadata = wp_get_attachment_metadata( $attachment_id );
     447
     448                $this->assertIsArray( $metadata );
     449                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     450                        $this->assertArrayHasKey( 'sources', $properties );
     451                        $this->assertIsArray( $properties['sources'] );
     452                        $this->assertArrayHasKey( 'image/jpeg', $properties['sources'] );
     453                        $this->assertArrayHasKey( 'filesize', $properties['sources']['image/jpeg'] );
     454                        $this->assertArrayHasKey( 'file', $properties['sources']['image/jpeg'] );
     455                        $this->assertArrayNotHasKey( 'image/webp', $properties['sources'] );
     456                }
     457        }
     458
     459        /**
     460         * Test not creating the sources property if the mime is not specified on the transforms images.
     461         *
     462         * @ticket 55443
     463         */
     464        public function it_should_not_create_the_sources_property_if_the_mime_is_not_specified_on_the_transforms_images() {
     465                add_filter(
     466                        'wp_upload_image_mime_transforms',
     467                        function () {
     468                                return array( 'image/jpeg' => array() );
     469                        }
     470                );
     471
     472                $attachment_id = $this->factory->attachment->create_upload_object(
     473                        DIR_TESTDATA . '/images/webp-lossy.webp'
     474                );
     475
     476                $metadata = wp_get_attachment_metadata( $attachment_id );
     477
     478                $this->assertIsArray( $metadata );
     479                foreach ( $metadata['sizes'] as $size_name => $properties ) {
     480                        $this->assertArrayNotHasKey( 'sources', $properties );
     481                }
     482        }
     483
     484
     485        /**
     486         * Test creating a WebP version with all the required properties.
     487         *
     488         * @ticket 55443
     489         */
     490        public function it_should_create_a_webp_version_with_all_the_required_properties() {
     491                $attachment_id = $this->factory->attachment->create_upload_object(
     492                        DIR_TESTDATA . '/images/test-image.jpg'
     493                );
     494
     495                $metadata = wp_get_attachment_metadata( $attachment_id );
     496                $this->assertArrayHasKey( 'sources', $metadata['sizes']['thumbnail'] );
     497                $this->assertArrayHasKey( 'image/jpeg', $metadata['sizes']['thumbnail']['sources'] );
     498                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     499                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/jpeg'] );
     500                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['thumbnail']['sources'] );
     501                $this->assertArrayHasKey( 'filesize', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     502                $this->assertArrayHasKey( 'file', $metadata['sizes']['thumbnail']['sources']['image/webp'] );
     503                $this->assertStringEndsNotWith( '.jpeg', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     504                $this->assertStringEndsWith( '.webp', $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] );
     505        }
     506
     507        /**
     508         * Test removing `scaled` suffix from the generated filename.
     509         *
     510         * @ticket 55443
     511         */
     512        public function it_should_remove_scaled_suffix_from_the_generated_filename() {
     513                // The leafs image is 1080 pixels wide with this filter we ensure a -scaled version is created.
     514                add_filter(
     515                        'big_image_size_threshold',
     516                        function () {
     517                                return 850;
     518                        }
     519                );
     520
     521                $attachment_id = $this->factory->attachment->create_upload_object(
     522                        DIR_TESTDATA . '/images/test-image.jpg'
     523                );
     524                $metadata      = wp_get_attachment_metadata( $attachment_id );
     525                $this->assertStringEndsWith( '-scaled.jpg', get_attached_file( $attachment_id ) );
     526                $this->assertArrayHasKey( 'image/webp', $metadata['sizes']['medium']['sources'] );
     527                $this->assertStringEndsNotWith( '-scaled.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     528                $this->assertStringEndsWith( '-300x200.webp', $metadata['sizes']['medium']['sources']['image/webp']['file'] );
     529        }
     530
     531        /**
     532         * Test removing the generated webp images when the attachment is deleted.
     533         *
     534         * @ticket 55443
     535         */
     536        public function it_should_remove_the_generated_webp_images_when_the_attachment_is_deleted() {
     537                // Make sure no editor is available.
     538                $attachment_id = $this->factory->attachment->create_upload_object(
     539                        DIR_TESTDATA . '/images/test-image.jpg'
     540                );
     541
     542                $file    = get_attached_file( $attachment_id, true );
     543                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     544
     545                $this->assertIsString( $file );
     546                $this->assertFileExists( $file );
     547
     548                $metadata = wp_get_attachment_metadata( $attachment_id );
     549                $sizes    = array( 'thumbnail', 'medium' );
     550
     551                foreach ( $sizes as $size_name ) {
     552                        $this->assertArrayHasKey( 'image/webp', $metadata['sizes'][ $size_name ]['sources'] );
     553                        $this->assertArrayHasKey( 'file', $metadata['sizes'][ $size_name ]['sources']['image/webp'] );
     554                        $this->assertFileExists(
     555                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     556                        );
     557                }
     558
     559                wp_delete_attachment( $attachment_id );
     560
     561                foreach ( $sizes as $size_name ) {
     562                        $this->assertFileDoesNotExist(
     563                                path_join( $dirname, $metadata['sizes'][ $size_name ]['sources']['image/webp']['file'] )
     564                        );
     565                }
     566        }
     567
     568        /**
     569         * Test removing the attached WebP version if the attachment is force deleted but empty trash day is not defined.
     570         *
     571         * @ticket 55443
     572         */
     573        public function it_should_remove_the_attached_webp_version_if_the_attachment_is_force_deleted_but_empty_trash_day_is_not_defined() {
     574                // Make sure no editor is available.
     575                $attachment_id = $this->factory->attachment->create_upload_object(
     576                        DIR_TESTDATA . '/images/test-image.jpg'
     577                );
     578
     579                $file    = get_attached_file( $attachment_id, true );
     580                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     581
     582                $this->assertIsString( $file );
     583                $this->assertFileExists( $file );
     584
     585                $metadata = wp_get_attachment_metadata( $attachment_id );
     586
     587                $this->assertFileExists(
     588                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     589                );
     590
     591                wp_delete_attachment( $attachment_id, true );
     592
     593                $this->assertFileDoesNotExist(
     594                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     595                );
     596        }
     597
     598        /**
     599         * Test removing the WebP version of the image if the image is force deleted and empty trash days is set to zero.
     600         *
     601         * @ticket 55443
     602         */
     603        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() {
     604                // Make sure no editor is available.
     605                $attachment_id = $this->factory->attachment->create_upload_object(
     606                        DIR_TESTDATA . '/images/test-image.jpg'
     607                );
     608
     609                $file    = get_attached_file( $attachment_id, true );
     610                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     611
     612                $this->assertIsString( $file );
     613                $this->assertFileExists( $file );
     614
     615                $metadata = wp_get_attachment_metadata( $attachment_id );
     616
     617                $this->assertFileExists(
     618                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     619                );
     620
     621                define( 'EMPTY_TRASH_DAYS', 0 );
     622
     623                wp_delete_attachment( $attachment_id, true );
     624
     625                $this->assertFileDoesNotExist(
     626                        path_join( $dirname, $metadata['sizes']['thumbnail']['sources']['image/webp']['file'] )
     627                );
     628        }
     629
     630        /**
     631         * Test avoiding the change of URLs of images that are not part of the media library.
     632         *
     633         * @ticket 55443
     634         */
     635        public function it_should_avoid_the_change_of_urls_of_images_that_are_not_part_of_the_media_library() {
     636                $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>';
     637
     638                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     639        }
     640
     641        /**
     642         * Test avoiding replacing not existing attachment IDs.
     643         *
     644         * @ticket 55443
     645         */
     646        public function it_should_avoid_replacing_not_existing_attachment_i_ds() {
     647                $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>';
     648
     649                $this->assertSame( $paragraph, webp_uploads_update_image_references( $paragraph ) );
     650        }
     651
     652        /**
     653         * Test preventing replacing a WebP image.
     654         *
     655         * @ticket 55443
     656         */
     657        public function it_should_Test preventing_replacing_a_webp_image() {
     658                $attachment_id = $this->factory->attachment->create_upload_object(
     659                        DIR_TESTDATA . '/images/webp-lossy.webp'
     660                );
     661
     662                $tag = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     663
     664                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     665        }
     666
     667        /**
     668         * Test preventing replacing a jpg image if the image does not have the target class name.
     669         *
     670         * @ticket 55443
     671         */
     672        public function it_should_Test preventing_replacing_a_jpg_image_if_the_image_does_not_have_the_target_class_name() {
     673                $attachment_id = $this->factory->attachment->create_upload_object(
     674                        DIR_TESTDATA . '/images/test-image.jpg'
     675                );
     676
     677                $tag = wp_get_attachment_image( $attachment_id, 'medium' );
     678
     679                $this->assertSame( $tag, webp_uploads_update_image_references( $tag ) );
     680        }
     681
     682        /**
     683         * Test replacing the references to a JPG image to a WebP version.
     684         *
     685         * @dataProvider provider_replace_images_with_different_extensions
     686         *
     687         * @ticket 55443
     688         */
     689        public function it_should_replace_the_references_to_a_jpg_image_to_a_webp_version( $image_path ) {
     690                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     691
     692                $tag          = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     693                $expected_tag = $tag;
     694                $metadata     = wp_get_attachment_metadata( $attachment_id );
     695                foreach ( $metadata['sizes'] as $size => $properties ) {
     696                        $expected_tag = str_replace( $properties['sources']['image/jpeg']['file'], $properties['sources']['image/webp']['file'], $expected_tag );
     697                }
     698
     699                $this->assertNotEmpty( $expected_tag );
     700                $this->assertNotSame( $tag, $expected_tag );
     701                $this->assertSame( $expected_tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     702        }
     703
     704        public function provider_replace_images_with_different_extensions() {
     705                yield 'An image with a .jpg extension' => array( DIR_TESTDATA . '/images/test-image.jpg' );
     706                yield 'An image with a .jpeg extension' => array( DIR_TESTDATA . '/images/test-image.jpeg' );
     707        }
     708
     709        /**
     710         * Test the full image size from the original mime type.
     711         *
     712         * @ticket 55443
     713         */
     714        public function it_should_contain_the_full_image_size_from_the_original_mime() {
     715                $attachment_id = $this->factory->attachment->create_upload_object(
     716                        DIR_TESTDATA . '/images/test-image.jpg'
     717                );
     718
     719                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     720
     721                $expected = array(
     722                        'ext'  => 'jpg',
     723                        'type' => 'image/jpeg',
     724                );
     725                $this->assertSame( $expected, wp_check_filetype( get_attached_file( $attachment_id ) ) );
     726                $this->assertContains( wp_basename( get_attached_file( $attachment_id ) ), webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     727        }
     728
     729        /**
     730         * Test preventing replacing an image with no available sources.
     731         *
     732         * @ticket 55443
     733         */
     734        public function it_should_prevent_replacing_an_image_with_no_available_sources() {
     735                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     736
     737                $attachment_id = $this->factory->attachment->create_upload_object( DIR_TESTDATA . '/images/test-image.jpg' );
     738
     739                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     740                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     741        }
     742
     743        /**
     744         * Test preventing update not supported images with no available sources.
     745         *
     746         * @dataProvider provider_it_should_prevent_update_not_supported_images_with_no_available_sources
     747         *
     748         * @ticket 55443
     749         */
     750        public function it_should_prevent_update_not_supported_images_with_no_available_sources( $image_path ) {
     751                $attachment_id = $this->factory->attachment->create_upload_object( $image_path );
     752
     753                $this->assertIsNumeric( $attachment_id );
     754                $tag = wp_get_attachment_image( $attachment_id, 'full', false, array( 'class' => "wp-image-{$attachment_id}" ) );
     755
     756                $this->assertSame( $tag, webp_uploads_img_tag_update_mime_type( $tag, 'the_content', $attachment_id ) );
     757        }
     758
     759        /**
     760         * Data provider for it_should_prevent_update_not_supported_images_with_no_available_sources.
     761         */
     762        public function provider_it_should_prevent_update_not_supported_images_with_no_available_sources() {
     763                yield 'PNG image' => array( DIR_TESTDATA . '/images/test-image.png' );
     764                yield 'GIFT image' => array( DIR_TESTDATA . '/images/test-image.gif' );
     765        }
     766
    365767}
  • tests/phpunit/tests/image/functions.php

    diff --git tests/phpunit/tests/image/functions.php tests/phpunit/tests/image/functions.php
    index 86d559145e..7e4734bf0f 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 );
     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 );
     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                add_filter(
     1123                        'wp_upload_image_mime_transforms',
     1124                        function( $transforms, $param1 ) use ( &$attachment_id ) {
     1125                                $attachment_id = $param1;
     1126                                return $transforms;
     1127                        },
     1128                        10,
     1129                        2
     1130                );
     1131
     1132                wp_upload_image_mime_transforms( 23 );
     1133                $this->assertSame( 23, $attachment_id );
     1134        }
     1135
     1136        /**
     1137         * @ticket 55443
     1138         */
     1139        public function test_wp_upload_image_mime_transforms_filter_with_empty_array() {
     1140                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1141                $result = wp_upload_image_mime_transforms( 42 );
     1142                $this->assertSame( array(), $result );
     1143        }
     1144
     1145        /**
     1146         * @ticket 55443
     1147         */
     1148        public function test_wp_upload_image_mime_transforms_filter_with_invalid_usage() {
     1149                $default = wp_upload_image_mime_transforms( 42 );
     1150
     1151                add_filter( 'wp_upload_image_mime_transforms', '__return_false' );
     1152                $result = wp_upload_image_mime_transforms( 42 );
     1153                $this->assertSame( $default, $result );
     1154        }
     1155
     1156        /**
     1157         * @ticket 55443
     1158         */
     1159        public function test__wp_get_primary_and_additional_mime_types_default() {
     1160                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1161
     1162                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1163                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1164
     1165                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1166                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1167                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1168                } else {
     1169                        $this->assertSame( array(), $additional_mime_types );
     1170                }
     1171        }
     1172
     1173        /**
     1174         * @ticket 55443
     1175         */
     1176        public function test__wp_get_primary_and_additional_mime_types_prefer_original_mime() {
     1177                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1178
     1179                // Set 'image/jpeg' only as secondary output MIME type.
     1180                // Still, because it is the original, it should be chosen as primary over 'image/webp'.
     1181                add_filter(
     1182                        'wp_upload_image_mime_transforms',
     1183                        function( $transforms ) {
     1184                                $transforms['image/jpeg'] = array( 'image/webp', 'image/jpeg' );
     1185                                return $transforms;
     1186                        }
     1187                );
     1188
     1189                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1190                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1191
     1192                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1193                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1194                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1195                } else {
     1196                        $this->assertSame( array(), $additional_mime_types );
     1197                }
     1198        }
     1199
     1200        /**
     1201         * @ticket 55443
     1202         */
     1203        public function test__wp_get_primary_and_additional_mime_types_use_original_mime_when_no_transformation_rules() {
     1204                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1205
     1206                // Strip all transformation rules.
     1207                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1208
     1209                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1210                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1211                $this->assertSame( array(), $additional_mime_types );
     1212        }
     1213
     1214        /**
     1215         * @ticket 55443
     1216         */
     1217        public function test__wp_get_primary_and_additional_mime_types_different_output_mime() {
     1218                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1219
     1220                // Set 'image/webp' as the only output MIME type.
     1221                // In that case, JPEG is not generated at all, so WebP becomes the primary MIME type.
     1222                add_filter(
     1223                        'wp_upload_image_mime_transforms',
     1224                        function( $transforms ) {
     1225                                $transforms['image/jpeg'] = array( 'image/webp' );
     1226                                return $transforms;
     1227                        }
     1228                );
     1229
     1230                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1231
     1232                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1233                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1234                        $this->assertSame( 'image/webp', $primary_mime_type );
     1235                } else {
     1236                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1237                }
     1238
     1239                $this->assertSame( array(), $additional_mime_types );
     1240        }
     1241
     1242        /**
     1243         * @ticket 55443
     1244         */
     1245        public function test__wp_get_primary_and_additional_mime_types_different_output_mimes() {
     1246                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1247
     1248                // Set 'image/webp' and 'image/avif' as output MIME types.
     1249                // In that case, JPEG is not generated at all, with WebP being the primary MIME type and AVIF the secondary.
     1250                add_filter(
     1251                        'wp_upload_image_mime_transforms',
     1252                        function( $transforms ) {
     1253                                $transforms['image/jpeg'] = array( 'image/webp', 'image/avif' );
     1254                                return $transforms;
     1255                        }
     1256                );
     1257
     1258                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1259
     1260                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1261                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1262                        $this->assertSame( 'image/webp', $primary_mime_type );
     1263                } else {
     1264                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1265                }
     1266
     1267                // AVIF may not be supported by the server, in which case it will be stripped from the results.
     1268                if ( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
     1269                        $this->assertSame( array( 'image/avif' ), $additional_mime_types );
     1270                } else {
     1271                        $this->assertSame( array(), $additional_mime_types );
     1272                }
     1273        }
     1274
     1275        /**
     1276         * @ticket 55443
     1277         * @dataProvider data__wp_filter_image_sizes_additional_mime_type_support
     1278         */
     1279        public function test__wp_filter_image_sizes_additional_mime_type_support( $input_size_data, $filter_callback, $expected_size_names ) {
     1280                remove_all_filters( 'wp_image_sizes_with_additional_mime_type_support' );
     1281                if ( $filter_callback ) {
     1282                        add_filter( 'wp_image_sizes_with_additional_mime_type_support', $filter_callback );
     1283                }
     1284
     1285                $expected_size_data = array_intersect_key( $input_size_data, array_flip( $expected_size_names ) );
     1286
     1287                $output_size_data = _wp_filter_image_sizes_additional_mime_type_support( $input_size_data, 42 );
     1288                $this->assertEqualSetsWithIndex( $expected_size_data, $output_size_data );
     1289        }
     1290
     1291        public function data__wp_filter_image_sizes_additional_mime_type_support() {
     1292                $thumbnail_data    = array(
     1293                        'width'  => 150,
     1294                        'height' => 150,
     1295                        'crop'   => true,
     1296                );
     1297                $medium_data       = array(
     1298                        'width'  => 300,
     1299                        'height' => 300,
     1300                        'crop'   => false,
     1301                );
     1302                $medium_large_data = array(
     1303                        'width'  => 768,
     1304                        'height' => 0,
     1305                        'crop'   => false,
     1306                );
     1307                $large_data        = array(
     1308                        'width'  => 1024,
     1309                        'height' => 1024,
     1310                        'crop'   => false,
     1311                );
     1312                $custom_data       = array(
     1313                        'width'  => 512,
     1314                        'height' => 512,
     1315                        'crop'   => true,
     1316                );
     1317
     1318                return array(
     1319                        array(
     1320                                array(
     1321                                        'thumbnail'    => $thumbnail_data,
     1322                                        'medium'       => $medium_data,
     1323                                        'medium_large' => $medium_large_data,
     1324                                        'large'        => $large_data,
     1325                                ),
     1326                                null,
     1327                                array( 'thumbnail', 'medium', 'medium_large', 'large' ),
     1328                        ),
     1329                        array(
     1330                                array(
     1331                                        'thumbnail' => $thumbnail_data,
     1332                                        'medium'    => $medium_data,
     1333                                        'custom'    => $custom_data,
     1334                                ),
     1335                                null,
     1336                                array( 'thumbnail', 'medium' ),
     1337                        ),
     1338                        array(
     1339                                array(
     1340                                        'thumbnail'    => $thumbnail_data,
     1341                                        'medium'       => $medium_data,
     1342                                        'medium_large' => $medium_large_data,
     1343                                        'large'        => $large_data,
     1344                                ),
     1345                                function( $enabled_sizes ) {
     1346                                        unset( $enabled_sizes['medium_large'], $enabled_sizes['large'] );
     1347                                        return $enabled_sizes;
     1348                                },
     1349                                array( 'thumbnail', 'medium' ),
     1350                        ),
     1351                        array(
     1352                                array(
     1353                                        'thumbnail'    => $thumbnail_data,
     1354                                        'medium'       => $medium_data,
     1355                                        'medium_large' => $medium_large_data,
     1356                                        'large'        => $large_data,
     1357                                ),
     1358                                function( $enabled_sizes ) {
     1359                                        $enabled_sizes['medium_large'] = false;
     1360                                        $enabled_sizes['large']        = false;
     1361                                        return $enabled_sizes;
     1362                                },
     1363                                array( 'thumbnail', 'medium' ),
     1364                        ),
     1365                        array(
     1366                                array(
     1367                                        'thumbnail' => $thumbnail_data,
     1368                                        'medium'    => $medium_data,
     1369                                        'custom'    => $custom_data,
     1370                                ),
     1371                                function( $enabled_sizes ) {
     1372                                        unset( $enabled_sizes['medium'] );
     1373                                        $enabled_sizes['custom'] = true;
     1374                                        return $enabled_sizes;
     1375                                },
     1376                                array( 'thumbnail', 'custom' ),
     1377                        ),
     1378                );
     1379        }
     1380
     1381        /**
     1382         * Test the `_wp_maybe_scale_and_rotate_image()` function.
     1383         *
     1384         * @dataProvider data_test__wp_maybe_scale_and_rotate_image
     1385         *
     1386         * @ticket 55443
     1387         */
     1388        public function test__wp_maybe_scale_and_rotate_image( $file, $imagesize, $mime_type, $expected ) {
     1389                if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
     1390                        $this->markTestSkipped( sprintf( 'This test requires %s support.', $mime_type ) );
     1391                }
     1392
     1393                $attributes    = array( 'post_mime_type' => $mime_type );
     1394                $attachment_id = $this->factory->attachment->create_object( $file, 0, $attributes );
     1395                $exif_meta     = wp_read_image_metadata( $file );
     1396
     1397                list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
     1398
     1399                $this->assertSame( $expected['rotated'], $rotated );
     1400                $this->assertSame( $expected['resized'], $resized );
     1401                $this->assertSame( $expected['size'], $editor->get_size() );
     1402        }
     1403
     1404        /**
     1405         * Data provider for the `test__wp_maybe_scale_and_rotate_image()` test.
     1406         *
     1407         * @return array
     1408         */
     1409        public function data_test__wp_maybe_scale_and_rotate_image() {
     1410                return array(
     1411
     1412                        // Image that will be scaled.
     1413                        array(
     1414                                DIR_TESTDATA . '/images/test-image-large.jpg',
     1415                                array( 3000, 2250 ),
     1416                                'image/jpeg',
     1417                                array(
     1418                                        'rotated' => false,
     1419                                        'resized' => true,
     1420                                        'size'    => array(
     1421                                                'width'  => 2560,
     1422                                                'height' => 1920,
     1423                                        ),
     1424                                ),
     1425                        ),
     1426
     1427                        // Image that will not be scaled.
     1428                        array(
     1429                                DIR_TESTDATA . '/images/canola.jpg',
     1430                                array( 640, 480 ),
     1431                                'image/jpeg',
     1432                                array(
     1433                                        'rotated' => false,
     1434                                        'resized' => false,
     1435                                        'size'    => array(
     1436                                                'width'  => 640,
     1437                                                'height' => 480,
     1438                                        ),
     1439                                ),
     1440                        ),
     1441
     1442                        // Image that will be flipped.
     1443                        array(
     1444                                DIR_TESTDATA . '/images/test-image-upside-down.jpg',
     1445                                array( 600, 450 ),
     1446                                'image/jpeg',
     1447                                array(
     1448                                        'rotated' => true,
     1449                                        'resized' => false,
     1450                                        'size'    => array(
     1451                                                'width'  => 600,
     1452                                                'height' => 450,
     1453                                        ),
     1454                                ),
     1455                        ),
     1456
     1457                        // Image that will be rotated.
     1458                        array(
     1459                                DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg',
     1460                                array( 1200, 1800 ),
     1461                                'image/jpeg',
     1462                                array(
     1463                                        'rotated' => true,
     1464                                        'resized' => false,
     1465                                        'size'    => array(
     1466                                                'width'  => 1800,
     1467                                                'height' => 1200,
     1468                                        ),
     1469                                ),
     1470                        ),
     1471
     1472                        // Image that will not be rotated - WebP Exif is not supported in PHP.
     1473                        array(
     1474                                DIR_TESTDATA . '/images/test-image-rotated-90cw.webp',
     1475                                array( 1024, 768 ),
     1476                                'image/webp',
     1477                                array(
     1478                                        'rotated' => false,
     1479                                        'resized' => false,
     1480                                        'size'    => array(
     1481                                                'width'  => 1024,
     1482                                                'height' => 768,
     1483                                        ),
     1484                                ),
     1485                        ),
     1486
     1487                );
     1488        }
     1489
     1490        /**
     1491         * Test the `_wp_get_image_suffix()` function.
     1492         * @dataProvider data_test__wp_get_image_suffix
     1493         *
     1494         * @ticket 55443
     1495         */
     1496        public function test__wp_get_image_suffix( $resized, $rotated, $expected ) {
     1497                $this->assertSame( $expected, _wp_get_image_suffix( $resized, $rotated ) );
     1498        }
     1499
     1500        /**
     1501         * Data provider for the `test__wp_get_image_suffix()` test.
     1502         */
     1503        public function data_test__wp_get_image_suffix() {
     1504                return array(
     1505                        array( false, false, '' ),
     1506                        array( true, false, 'scaled' ),
     1507                        array( false, true, 'rotated' ),
     1508                        array( true, true, 'scaled' ),
     1509                );
     1510        }
    10291511}
  • 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/**