Make WordPress Core

Ticket #55443: 55443.8.diff

File 55443.8.diff, 91.3 KB (added by adamsilverstein, 20 months 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..98689672b3 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        }
    376477
    377         return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
     478        if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
     479                // TODO: Log errors.
     480        }
     481        return '';
     482}
     483
     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        if ( ! $mime_type ) {
     524                $mime_type = wp_get_image_mime( $file );
     525        }
     526
    401527        // Check if any of the new sizes already exist.
    402528        if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
    403529                foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    407533                         * To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
    408534                         */
    409535                        if ( array_key_exists( $size_name, $new_sizes ) ) {
    410                                 unset( $new_sizes[ $size_name ] );
     536                                // Unset the size if it is either the required mime type already exists either as main mime type or
     537                                // within sources.
     538                                if ( $size_meta['mime-type'] === $mime_type || isset( $size_meta['sources'][ $mime_type ] ) ) {
     539                                        unset( $new_sizes[ $size_name ] );
     540                                }
    411541                        }
    412542                }
    413543        } else {
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    433563
    434564        $new_sizes = array_filter( array_merge( $priority, $new_sizes ) );
    435565
    436         $editor = wp_get_image_editor( $file );
     566        $editor = wp_get_image_editor( $file, array( 'mime_type' => $mime_type ) );
    437567
    438568        if ( is_wp_error( $editor ) ) {
    439569                // The image cannot be edited.
    440570                return $image_meta;
    441571        }
    442572
     573        $editor->set_output_mime_type( $mime_type );
     574
    443575        // If stored EXIF data exists, rotate the source image before creating sub-sizes.
    444576        if ( ! empty( $image_meta['image_meta'] ) ) {
    445577                $rotated = $editor->maybe_exif_rotate();
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    457589                                // TODO: Log errors.
    458590                        } else {
    459591                                // Save the size meta value.
    460                                 $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     592                                if ( ! isset( $image_meta['sizes'][ $new_size_name ] ) ) {
     593                                        $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     594                                } else {
     595                                        // Remove any newly generated images that are larger than the primary mime type.
     596                                        $new_size     = isset( $new_size_meta['filesize'] ) ? $new_size_meta['filesize'] : 0;
     597                                        $primary_size = isset( $image_meta['sizes'][ $new_size_name ]['filesize'] ) ? $image_meta['sizes'][ $new_size_name ]['filesize'] : 0;
     598
     599                                        if ( $new_size && $primary_size && $new_size >= $primary_size ) {
     600                                                wp_delete_file( dirname( $file ) . '/' . $new_size_meta['file'] );
     601                                                continue;
     602                                        }
     603                                }
     604                                if ( ! isset( $image_meta['sizes'][ $new_size_name ]['sources'] ) ) {
     605                                        $image_meta['sizes'][ $new_size_name ]['sources'] = array();
     606                                }
     607                                $image_meta['sizes'][ $new_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
    461608                                wp_update_attachment_metadata( $attachment_id, $image_meta );
    462609                        }
    463610                }
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    466613                $created_sizes = $editor->multi_resize( $new_sizes );
    467614
    468615                if ( ! empty( $created_sizes ) ) {
    469                         $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
     616                        foreach ( $created_sizes as $created_size_name => $created_size_meta ) {
     617
     618                                // Primary mime type is set in 'sizes' array.
     619                                if ( ! isset( $image_meta['sizes'][ $created_size_name ] ) ) {
     620                                        $image_meta['sizes'][ $created_size_name ] = $created_size_meta;
     621                                } else {
     622                                        // Remove any newly generated images that are larger than the primary mime type.
     623                                        $new_size     = isset( $created_size_meta['filesize'] ) ? $created_size_meta['filesize'] : 0;
     624                                        $primary_size = isset( $image_meta['sizes'][ $created_size_name ]['filesize'] ) ? $image_meta['sizes'][ $created_size_name ]['filesize'] : 0;
     625
     626                                        if ( $new_size && $primary_size && $new_size >= $primary_size ) {
     627                                                wp_delete_file( dirname( $file ) . '/' . $created_size_meta['file'] );
     628                                                continue;
     629                                        }
     630                                }
     631                                if ( ! isset( $image_meta['sizes'][ $created_size_name ]['sources'] ) ) {
     632                                        $image_meta['sizes'][ $created_size_name ]['sources'] = array();
     633                                }
     634                                $image_meta['sizes'][ $created_size_name ]['sources'][ $mime_type ] = _wp_get_sources_from_meta( $new_size_meta );
     635                        }
     636                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     637                }
     638        }
     639
     640        return $image_meta;
     641}
     642
     643/**
     644 * Filters the list of image size objects that support secondary mime type output.
     645 *
     646 * @since 6.1.0
     647 *
     648 * @param array $sizes         Associative array of image sizes.
     649 * @param int   $attachment_id Attachment ID.
     650 * @return array $sizes Filtered $sizes with only those that support secondary mime type output.
     651 */
     652function _wp_filter_image_sizes_additional_mime_type_support( $sizes, $attachment_id ) {
     653
     654        // Include only the core sizes that do not rely on add_image_size(). Additional image sizes are opt-in.
     655        $enabled_sizes = array(
     656                'thumbnail'      => true,
     657                'medium'         => true,
     658                'medium_large'   => true,
     659                'large'          => true,
     660                'post-thumbnail' => true,
     661        );
     662
     663        /**
     664         * Filter the sizes that support secondary mime type output. Developers can use this
     665         * to control the output of additional mime type sub-sized images.
     666         *
     667         * @since 6.1.0
     668         *
     669         * @param array $enabled_sizes Map of size names and whether they support secondary mime type output.
     670         * @param int   $attachment_id Attachment ID.
     671         */
     672        $enabled_sizes = apply_filters( 'wp_image_sizes_with_additional_mime_type_support', $enabled_sizes, $attachment_id );
     673
     674        // Filter supported sizes to only include enabled sizes.
     675        return array_intersect_key( $sizes, array_filter( $enabled_sizes ) );
     676}
     677
     678/**
     679 * Low-level function to create full-size images in additional mime types.
     680 *
     681 * Updates the image meta after each mime type image is created.
     682 *
     683 * @since 6.1.0
     684 * @access private
     685 *
     686 * @param array  $new_mime_types Array defining what mime types to create.
     687 * @param string $file           Full path to the image file.
     688 * @param array  $image_meta     The attachment meta data array.
     689 * @param int    $attachment_id  Attachment ID to process.
     690 * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
     691 */
     692function _wp_make_additional_mime_types( $new_mime_types, $file, $image_meta, $attachment_id ) {
     693        $imagesize          = array(
     694                $image_meta['width'],
     695                $image_meta['height'],
     696        );
     697        $exif_meta          = isset( $image_meta['image_meta'] ) ? $image_meta['image_meta'] : null;
     698        $original_file_size = isset( $image_meta['filesize'] ) ? $image_meta['filesize'] : wp_filesize( $file );
     699
     700        foreach ( $new_mime_types as $mime_type ) {
     701                list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
     702                if ( is_wp_error( $editor ) ) {
     703                        // The image cannot be edited.
     704                        continue;
     705                }
     706
     707                $suffix    = _wp_get_image_suffix( $resized, $rotated );
     708                $extension = wp_get_default_extension_for_mime_type( $mime_type );
     709
     710                $saved = $editor->save( $editor->generate_filename( $suffix, null, $extension ) );
     711
     712                if ( is_wp_error( $saved ) ) {
     713                        // TODO: Log errors.
     714                } else {
     715                        // If the saved image is larger than the original, discard it.
     716                        $filesize = isset( $saved['filesize'] ) ? $saved['filesize'] : wp_filesize( $saved['path'] );
     717                        if ( $filesize && $original_file_size && $filesize > $original_file_size ) {
     718                                wp_delete_file( $saved['path'] );
     719                                continue;
     720                        }
     721                        $image_meta['sources'][ $mime_type ] = _wp_get_sources_from_meta( $saved );
    470722                        wp_update_attachment_metadata( $attachment_id, $image_meta );
    471723                }
    472724        }
    function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) { 
    474726        return $image_meta;
    475727}
    476728
     729
     730/**
     731 * Check if an image belongs to an attachment.
     732 *
     733 * @since 6.1.0
     734 * @access private
     735 *
     736 * @param string $filename     Full path to the image file.
     737 * @param int   $attachment_id Attachment ID to check.
     738 * @return bool True if the image belongs to the attachment, false otherwise.
     739 */
     740function _wp_image_belongs_to_attachment( $filename, $attachment_id ) {
     741        $meta_data = wp_get_attachment_metadata( $attachment_id );
     742
     743        if ( ! isset( $image_meta['sizes'] ) ) {
     744                return false;
     745        }
     746        $sizes = $image_meta['sizes'];
     747        foreach ( $sizes as $size ) {
     748                if ( $size['file'] === $filename ) {
     749                        return true;
     750                }
     751                if ( isset( $size['sources'] ) && is_array( $size['sources'] ) ) {
     752                        foreach ( $size['sources'] as $source ) {
     753                                if ( $source['file'] === $filename ) {
     754                                        return true;
     755                                }
     756                        }
     757                }
     758        }
     759        return false;
     760}
     761
    477762/**
    478763 * Generate attachment meta data and create image sub-sizes for images.
    479764 *
    function wp_generate_attachment_metadata( $attachment_id, $file ) { 
    630915                                        wp_update_attachment_metadata( $attachment_id, $metadata );
    631916
    632917                                        // Create sub-sizes saving the image meta after each.
    633                                         $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id );
     918                                        $metadata = _wp_make_subsizes( $merged_sizes, $image_file, $metadata, $attachment_id, '' );
    634919                                }
    635920                        }
    636921                }
    function _copy_image_file( $attachment_id ) { 
    11571442
    11581443        return $dst_file;
    11591444}
     1445
     1446/**
     1447 * Returns an array with the list of valid mime types that a specific mime type should be converted into.
     1448 * For example an `image/jpeg` should be converted into an `image/jpeg` and `image/webp`. The first type
     1449 * is considered the primary output type for this image.
     1450 *
     1451 * Called for each uploaded image to determine the list of mime types that should be converted into. Then,
     1452 * called again for each image size as they are generated to check if the image should be converted into the mime type
     1453 * for that size.
     1454 *
     1455 * @since 6.1.0
     1456 *
     1457 * @param int    $attachment_id  The attachment ID.
     1458 * @return array An array of valid mime types, where the key is the source file mime type and the list of mime types to
     1459 *               generate.
     1460 */
     1461function wp_upload_image_mime_transforms( $attachment_id ) {
     1462        $default_image_mime_transforms = array(
     1463                'image/jpeg' => array( 'image/jpeg', 'image/webp' ),
     1464                'image/webp' => array( 'image/webp', 'image/jpeg' ),
     1465        );
     1466        $image_mime_transforms         = $default_image_mime_transforms;
     1467
     1468        /**
     1469         * Filter the output mime types for a given input mime type and image size.
     1470         *
     1471         * @since 6.1.0
     1472         *
     1473         * @param array  $image_mime_transforms A map with the valid mime transforms where the key is the source file mime type
     1474         *                                      and the value is one or more mime file types to generate.
     1475         * @param int    $attachment_id         The ID of the attachment where the hook was dispatched.
     1476         */
     1477        $image_mime_transforms = apply_filters( 'wp_upload_image_mime_transforms', $image_mime_transforms, $attachment_id );
     1478
     1479        if ( ! is_array( $image_mime_transforms ) ) {
     1480                return $default_image_mime_transforms;
     1481        }
     1482
     1483        return array_map(
     1484                function( $transforms_list ) {
     1485                        return (array) $transforms_list;
     1486                },
     1487                $image_mime_transforms
     1488        );
     1489}
     1490
     1491/**
     1492 * Extract the primary and additional mime output types for an image from the $image_mime_transforms.
     1493 *
     1494 * @since 6.1.0
     1495 * @access private
     1496 *
     1497 * @param string $file          Full path to the image file.
     1498 * @param int    $attachment_id Attachment ID to process.
     1499 * @return array An array with two entries, the primary mime type and the list of additional mime types.
     1500 */
     1501function _wp_get_primary_and_additional_mime_types( $file, $attachment_id ) {
     1502        $image_mime_transforms = wp_upload_image_mime_transforms( $attachment_id );
     1503        $original_mime_type    = wp_get_image_mime( $file );
     1504        $output_mime_types     = isset( $image_mime_transforms[ $original_mime_type ] ) ? $image_mime_transforms[ $original_mime_type ] : array( $original_mime_type );
     1505
     1506        // Exclude any output mime types that the system doesn't support.
     1507        $output_mime_types = array_values(
     1508                array_filter(
     1509                        $output_mime_types,
     1510                        function( $mime_type ) {
     1511                                return wp_image_editor_supports(
     1512                                        array(
     1513                                                'mime_type' => $mime_type,
     1514                                        )
     1515                                );
     1516                        }
     1517                )
     1518        );
     1519
     1520        // Handle an empty value for $output_mime_types: only output the original type.
     1521        if ( empty( $output_mime_types ) ) {
     1522                return array( $original_mime_type, array() );
     1523        }
     1524
     1525        // Use original mime type as primary mime type, or alternatively the first one.
     1526        $primary_mime_type_key = array_search( $original_mime_type, $output_mime_types, true );
     1527        if ( false === $primary_mime_type_key ) {
     1528                $primary_mime_type_key = 0;
     1529        }
     1530        // Split output mime types into primary mime type and additional mime types.
     1531        $additional_mime_types     = $output_mime_types;
     1532        list( $primary_mime_type ) = array_splice( $additional_mime_types, $primary_mime_type_key, 1 );
     1533
     1534        return array(
     1535                $primary_mime_type,
     1536                $additional_mime_types,
     1537        );
     1538}
  • 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..ccf43402a6 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, .jpeg and .jpe 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..c051ffec2b 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..cb5a7971c9 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 { 
    677680                                        'height'    => 300,
    678681                                        'mime-type' => 'image/jpeg',
    679682                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     683                                        'sources'   => array(
     684                                                'image/jpeg' => array(
     685                                                        'file'     => 'wordpress-gsoc-flyer-pdf-232x300.jpg',
     686                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-232x300.jpg' ),
     687                                                ),
     688                                        ),
    680689                                ),
    681690                                'large'     => array(
    682691                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    684693                                        'height'    => 1024,
    685694                                        'mime-type' => 'image/jpeg',
    686695                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     696                                        'sources'   => array(
     697                                                'image/jpeg' => array(
     698                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     699                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     700                                                ),
     701                                        ),
    687702                                ),
    688703                                'thumbnail' => array(
    689704                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    691706                                        'height'    => 150,
    692707                                        'mime-type' => 'image/jpeg',
    693708                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     709                                        'sources'   => array(
     710                                                'image/jpeg' => array(
     711                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     712                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     713                                                ),
     714                                        ),
    694715                                ),
    695716                        ),
    696717                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    702723                foreach ( $metadata['sizes'] as $size ) {
    703724                        unlink( $temp_dir . $size['file'] );
    704725                }
     726                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    705727        }
    706728
    707729        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    716738
    717739                update_option( 'medium_crop', 1 );
    718740
     741                // Use legacy JPEG output.
     742                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     743
    719744                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    720745                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    721746                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    754779                                        'height'    => 300,
    755780                                        'mime-type' => 'image/jpeg',
    756781                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     782                                        'sources'   => array(
     783                                                'image/jpeg' => array(
     784                                                        'file'     => 'wordpress-gsoc-flyer-pdf-300x300.jpg',
     785                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-300x300.jpg' ),
     786                                                ),
     787                                        ),
    757788                                ),
    758789                                'large'     => array(
    759790                                        'file'      => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    761792                                        'height'    => 1024,
    762793                                        'mime-type' => 'image/jpeg',
    763794                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     795                                        'sources'   => array(
     796                                                'image/jpeg' => array(
     797                                                        'file'     => 'wordpress-gsoc-flyer-pdf-791x1024.jpg',
     798                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-791x1024.jpg' ),
     799                                                ),
     800                                        ),
     801
    764802                                ),
    765803                                'thumbnail' => array(
    766804                                        'file'      => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
    class Tests_Image_Functions extends WP_UnitTestCase { 
    768806                                        'height'    => 150,
    769807                                        'mime-type' => 'image/jpeg',
    770808                                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     809                                        'sources'   => array(
     810                                                'image/jpeg' => array(
     811                                                        'file'     => 'wordpress-gsoc-flyer-pdf-116x150.jpg',
     812                                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-116x150.jpg' ),
     813                                                ),
     814                                        ),
    771815                                ),
    772816                        ),
    773817                        'filesize' => wp_filesize( $test_file ),
    class Tests_Image_Functions extends WP_UnitTestCase { 
    779823                foreach ( $metadata['sizes'] as $size ) {
    780824                        unlink( $temp_dir . $size['file'] );
    781825                }
     826                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     827
    782828        }
    783829
    784830        /**
    class Tests_Image_Functions extends WP_UnitTestCase { 
    789835                        $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' );
    790836                }
    791837
     838                // Use legacy JPEG output.
     839                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     840
    792841                $orig_file = DIR_TESTDATA . '/images/wordpress-gsoc-flyer.pdf';
    793842                $test_file = get_temp_dir() . 'wordpress-gsoc-flyer.pdf';
    794843                copy( $orig_file, $test_file );
    class Tests_Image_Functions extends WP_UnitTestCase { 
    821870                        'height'    => 100,
    822871                        'mime-type' => 'image/jpeg',
    823872                        'filesize'  => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     873                        'sources'   => array(
     874                                'image/jpeg' => array(
     875                                        'file'     => 'wordpress-gsoc-flyer-pdf-77x100.jpg',
     876                                        'filesize' => wp_filesize( $temp_dir . 'wordpress-gsoc-flyer-pdf-77x100.jpg' ),
     877                                ),
     878                        ),
    824879                );
    825880
    826881                // Different environments produce slightly different filesize results.
    class Tests_Image_Functions extends WP_UnitTestCase { 
    836891                foreach ( $metadata['sizes'] as $size ) {
    837892                        unlink( $temp_dir . $size['file'] );
    838893                }
     894                remove_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
    839895        }
    840896
    841897        public function filter_fallback_intermediate_image_sizes( $fallback_sizes, $metadata ) {
    class Tests_Image_Functions extends WP_UnitTestCase { 
    10261082                        ),
    10271083                );
    10281084        }
     1085
     1086        /**
     1087         * @ticket 55443
     1088         */
     1089        public function test_wp_upload_image_mime_transforms_generates_webp_and_jpeg_for_both_by_default() {
     1090                $result = wp_upload_image_mime_transforms( 42 );
     1091                $this->assertArrayHasKey( 'image/jpeg', $result );
     1092                $this->assertArrayHasKey( 'image/webp', $result );
     1093                $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/jpeg'] );
     1094                $this->assertSameSets( array( 'image/jpeg', 'image/webp' ), $result['image/webp'] );
     1095        }
     1096
     1097        /**
     1098         * @ticket 55443
     1099         */
     1100        public function test_wp_upload_image_mime_transforms_filter_always_use_webp_instead_of_jpeg() {
     1101                add_filter(
     1102                        'wp_upload_image_mime_transforms',
     1103                        function( $transforms ) {
     1104                                // Ensure JPG only results in WebP files.
     1105                                $transforms['image/jpeg'] = array( 'image/webp' );
     1106                                // Unset WebP since it does not need any transformation in that case.
     1107                                unset( $transforms['image/webp'] );
     1108                                return $transforms;
     1109                        }
     1110                );
     1111
     1112                $result = wp_upload_image_mime_transforms( 42 );
     1113                $this->assertArrayHasKey( 'image/jpeg', $result );
     1114                $this->assertArrayNotHasKey( 'image/webp', $result );
     1115                $this->assertSameSets( array( 'image/webp' ), $result['image/jpeg'] );
     1116        }
     1117
     1118        /**
     1119         * @ticket 55443
     1120         */
     1121        public function test_wp_upload_image_mime_transforms_filter_receives_parameters() {
     1122                $attachment_id = null;
     1123                add_filter(
     1124                        'wp_upload_image_mime_transforms',
     1125                        function( $transforms, $param1 ) use ( &$attachment_id ) {
     1126                                $attachment_id = $param1;
     1127                                return $transforms;
     1128                        },
     1129                        10,
     1130                        2
     1131                );
     1132
     1133                wp_upload_image_mime_transforms( 23 );
     1134                $this->assertSame( 23, $attachment_id );
     1135        }
     1136
     1137        /**
     1138         * @ticket 55443
     1139         */
     1140        public function test_wp_upload_image_mime_transforms_filter_with_empty_array() {
     1141                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1142                $result = wp_upload_image_mime_transforms( 42 );
     1143                $this->assertSame( array(), $result );
     1144        }
     1145
     1146        /**
     1147         * @ticket 55443
     1148         */
     1149        public function test_wp_upload_image_mime_transforms_filter_with_invalid_usage() {
     1150                $default = wp_upload_image_mime_transforms( 42 );
     1151
     1152                add_filter( 'wp_upload_image_mime_transforms', '__return_false' );
     1153                $result = wp_upload_image_mime_transforms( 42 );
     1154                $this->assertSame( $default, $result );
     1155        }
     1156
     1157        /**
     1158         * @ticket 55443
     1159         */
     1160        public function test__wp_get_primary_and_additional_mime_types_default() {
     1161                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1162
     1163                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1164                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1165
     1166                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1167                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1168                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1169                } else {
     1170                        $this->assertSame( array(), $additional_mime_types );
     1171                }
     1172        }
     1173
     1174        /**
     1175         * @ticket 55443
     1176         */
     1177        public function test__wp_get_primary_and_additional_mime_types_prefer_original_mime() {
     1178                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1179
     1180                // Set 'image/jpeg' only as secondary output MIME type.
     1181                // Still, because it is the original, it should be chosen as primary over 'image/webp'.
     1182                add_filter(
     1183                        'wp_upload_image_mime_transforms',
     1184                        function( $transforms ) {
     1185                                $transforms['image/jpeg'] = array( 'image/webp', 'image/jpeg' );
     1186                                return $transforms;
     1187                        }
     1188                );
     1189
     1190                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1191                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1192
     1193                // WebP may not be supported by the server, in which case it will be stripped from the results.
     1194                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1195                        $this->assertSame( array( 'image/webp' ), $additional_mime_types );
     1196                } else {
     1197                        $this->assertSame( array(), $additional_mime_types );
     1198                }
     1199        }
     1200
     1201        /**
     1202         * @ticket 55443
     1203         */
     1204        public function test__wp_get_primary_and_additional_mime_types_use_original_mime_when_no_transformation_rules() {
     1205                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1206
     1207                // Strip all transformation rules.
     1208                add_filter( 'wp_upload_image_mime_transforms', '__return_empty_array' );
     1209
     1210                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1211                $this->assertSame( 'image/jpeg', $primary_mime_type );
     1212                $this->assertSame( array(), $additional_mime_types );
     1213        }
     1214
     1215        /**
     1216         * @ticket 55443
     1217         */
     1218        public function test__wp_get_primary_and_additional_mime_types_different_output_mime() {
     1219                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1220
     1221                // Set 'image/webp' as the only output MIME type.
     1222                // In that case, JPEG is not generated at all, so WebP becomes the primary MIME type.
     1223                add_filter(
     1224                        'wp_upload_image_mime_transforms',
     1225                        function( $transforms ) {
     1226                                $transforms['image/jpeg'] = array( 'image/webp' );
     1227                                return $transforms;
     1228                        }
     1229                );
     1230
     1231                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1232
     1233                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1234                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1235                        $this->assertSame( 'image/webp', $primary_mime_type );
     1236                } else {
     1237                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1238                }
     1239
     1240                $this->assertSame( array(), $additional_mime_types );
     1241        }
     1242
     1243        /**
     1244         * @ticket 55443
     1245         */
     1246        public function test__wp_get_primary_and_additional_mime_types_different_output_mimes() {
     1247                $jpeg_file = DIR_TESTDATA . '/images/test-image-large.jpg';
     1248
     1249                // Set 'image/webp' and 'image/avif' as output MIME types.
     1250                // In that case, JPEG is not generated at all, with WebP being the primary MIME type and AVIF the secondary.
     1251                add_filter(
     1252                        'wp_upload_image_mime_transforms',
     1253                        function( $transforms ) {
     1254                                $transforms['image/jpeg'] = array( 'image/webp', 'image/avif' );
     1255                                return $transforms;
     1256                        }
     1257                );
     1258
     1259                list( $primary_mime_type, $additional_mime_types ) = _wp_get_primary_and_additional_mime_types( $jpeg_file, 42 );
     1260
     1261                // WebP may not be supported by the server, in which case it will fall back to the original MIME type.
     1262                if ( wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
     1263                        $this->assertSame( 'image/webp', $primary_mime_type );
     1264                } else {
     1265                        $this->assertSame( 'image/jpeg', $primary_mime_type );
     1266                }
     1267
     1268                // AVIF may not be supported by the server, in which case it will be stripped from the results.
     1269                if ( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
     1270                        $this->assertSame( array( 'image/avif' ), $additional_mime_types );
     1271                } else {
     1272                        $this->assertSame( array(), $additional_mime_types );
     1273                }
     1274        }
     1275
     1276        /**
     1277         * @ticket 55443
     1278         * @dataProvider data__wp_filter_image_sizes_additional_mime_type_support
     1279         */
     1280        public function test__wp_filter_image_sizes_additional_mime_type_support( $input_size_data, $filter_callback, $expected_size_names ) {
     1281                remove_all_filters( 'wp_image_sizes_with_additional_mime_type_support' );
     1282                if ( $filter_callback ) {
     1283                        add_filter( 'wp_image_sizes_with_additional_mime_type_support', $filter_callback );
     1284                }
     1285
     1286                $expected_size_data = array_intersect_key( $input_size_data, array_flip( $expected_size_names ) );
     1287
     1288                $output_size_data = _wp_filter_image_sizes_additional_mime_type_support( $input_size_data, 42 );
     1289                $this->assertEqualSetsWithIndex( $expected_size_data, $output_size_data );
     1290        }
     1291
     1292        public function data__wp_filter_image_sizes_additional_mime_type_support() {
     1293                $thumbnail_data    = array(
     1294                        'width'  => 150,
     1295                        'height' => 150,
     1296                        'crop'   => true,
     1297                );
     1298                $medium_data       = array(
     1299                        'width'  => 300,
     1300                        'height' => 300,
     1301                        'crop'   => false,
     1302                );
     1303                $medium_large_data = array(
     1304                        'width'  => 768,
     1305                        'height' => 0,
     1306                        'crop'   => false,
     1307                );
     1308                $large_data        = array(
     1309                        'width'  => 1024,
     1310                        'height' => 1024,
     1311                        'crop'   => false,
     1312                );
     1313                $custom_data       = array(
     1314                        'width'  => 512,
     1315                        'height' => 512,
     1316                        'crop'   => true,
     1317                );
     1318
     1319                return array(
     1320                        array(
     1321                                array(
     1322                                        'thumbnail'    => $thumbnail_data,
     1323                                        'medium'       => $medium_data,
     1324                                        'medium_large' => $medium_large_data,
     1325                                        'large'        => $large_data,
     1326                                ),
     1327                                null,
     1328                                array( 'thumbnail', 'medium', 'medium_large', 'large' ),
     1329                        ),
     1330                        array(
     1331                                array(
     1332                                        'thumbnail' => $thumbnail_data,
     1333                                        'medium'    => $medium_data,
     1334                                        'custom'    => $custom_data,
     1335                                ),
     1336                                null,
     1337                                array( 'thumbnail', 'medium' ),
     1338                        ),
     1339                        array(
     1340                                array(
     1341                                        'thumbnail'    => $thumbnail_data,
     1342                                        'medium'       => $medium_data,
     1343                                        'medium_large' => $medium_large_data,
     1344                                        'large'        => $large_data,
     1345                                ),
     1346                                function( $enabled_sizes ) {
     1347                                        unset( $enabled_sizes['medium_large'], $enabled_sizes['large'] );
     1348                                        return $enabled_sizes;
     1349                                },
     1350                                array( 'thumbnail', 'medium' ),
     1351                        ),
     1352                        array(
     1353                                array(
     1354                                        'thumbnail'    => $thumbnail_data,
     1355                                        'medium'       => $medium_data,
     1356                                        'medium_large' => $medium_large_data,
     1357                                        'large'        => $large_data,
     1358                                ),
     1359                                function( $enabled_sizes ) {
     1360                                        $enabled_sizes['medium_large'] = false;
     1361                                        $enabled_sizes['large']        = false;
     1362                                        return $enabled_sizes;
     1363                                },
     1364                                array( 'thumbnail', 'medium' ),
     1365                        ),
     1366                        array(
     1367                                array(
     1368                                        'thumbnail' => $thumbnail_data,
     1369                                        'medium'    => $medium_data,
     1370                                        'custom'    => $custom_data,
     1371                                ),
     1372                                function( $enabled_sizes ) {
     1373                                        unset( $enabled_sizes['medium'] );
     1374                                        $enabled_sizes['custom'] = true;
     1375                                        return $enabled_sizes;
     1376                                },
     1377                                array( 'thumbnail', 'custom' ),
     1378                        ),
     1379                );
     1380        }
     1381
     1382        /**
     1383         * Test the `_wp_maybe_scale_and_rotate_image()` function.
     1384         *
     1385         * @dataProvider data_test__wp_maybe_scale_and_rotate_image
     1386         *
     1387         * @ticket 55443
     1388         */
     1389        public function test__wp_maybe_scale_and_rotate_image( $file, $imagesize, $mime_type, $expected ) {
     1390                if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
     1391                        $this->markTestSkipped( sprintf( 'This test requires %s support.', $mime_type ) );
     1392                }
     1393
     1394                $attributes    = array( 'post_mime_type' => $mime_type );
     1395                $attachment_id = $this->factory->attachment->create_object( $file, 0, $attributes );
     1396                $exif_meta     = wp_read_image_metadata( $file );
     1397
     1398                list( $editor, $resized, $rotated ) = _wp_maybe_scale_and_rotate_image( $file, $attachment_id, $imagesize, $exif_meta, $mime_type );
     1399
     1400                $this->assertSame( $expected['rotated'], $rotated );
     1401                $this->assertSame( $expected['resized'], $resized );
     1402                $this->assertSame( $expected['size'], $editor->get_size() );
     1403        }
     1404
     1405        /**
     1406         * Data provider for the `test__wp_maybe_scale_and_rotate_image()` test.
     1407         *
     1408         * @return array
     1409         */
     1410        public function data_test__wp_maybe_scale_and_rotate_image() {
     1411                return array(
     1412
     1413                        // Image that will be scaled.
     1414                        array(
     1415                                DIR_TESTDATA . '/images/test-image-large.jpg',
     1416                                array( 3000, 2250 ),
     1417                                'image/jpeg',
     1418                                array(
     1419                                        'rotated' => false,
     1420                                        'resized' => true,
     1421                                        'size'    => array(
     1422                                                'width'  => 2560,
     1423                                                'height' => 1920,
     1424                                        ),
     1425                                ),
     1426                        ),
     1427
     1428                        // Image that will not be scaled.
     1429                        array(
     1430                                DIR_TESTDATA . '/images/canola.jpg',
     1431                                array( 640, 480 ),
     1432                                'image/jpeg',
     1433                                array(
     1434                                        'rotated' => false,
     1435                                        'resized' => false,
     1436                                        'size'    => array(
     1437                                                'width'  => 640,
     1438                                                'height' => 480,
     1439                                        ),
     1440                                ),
     1441                        ),
     1442
     1443                        // Image that will be flipped.
     1444                        array(
     1445                                DIR_TESTDATA . '/images/test-image-upside-down.jpg',
     1446                                array( 600, 450 ),
     1447                                'image/jpeg',
     1448                                array(
     1449                                        'rotated' => true,
     1450                                        'resized' => false,
     1451                                        'size'    => array(
     1452                                                'width'  => 600,
     1453                                                'height' => 450,
     1454                                        ),
     1455                                ),
     1456                        ),
     1457
     1458                        // Image that will be rotated.
     1459                        array(
     1460                                DIR_TESTDATA . '/images/test-image-rotated-90ccw.jpg',
     1461                                array( 1200, 1800 ),
     1462                                'image/jpeg',
     1463                                array(
     1464                                        'rotated' => true,
     1465                                        'resized' => false,
     1466                                        'size'    => array(
     1467                                                'width'  => 1800,
     1468                                                'height' => 1200,
     1469                                        ),
     1470                                ),
     1471                        ),
     1472
     1473                        // Image that will not be rotated - WebP Exif is not supported in PHP.
     1474                        array(
     1475                                DIR_TESTDATA . '/images/test-image-rotated-90cw.webp',
     1476                                array( 1024, 768 ),
     1477                                'image/webp',
     1478                                array(
     1479                                        'rotated' => false,
     1480                                        'resized' => false,
     1481                                        'size'    => array(
     1482                                                'width'  => 1024,
     1483                                                'height' => 768,
     1484                                        ),
     1485                                ),
     1486                        ),
     1487
     1488                );
     1489        }
     1490
     1491        /**
     1492         * Test the `_wp_get_image_suffix()` function.
     1493         * @dataProvider data_test__wp_get_image_suffix
     1494         *
     1495         * @ticket 55443
     1496         */
     1497        public function test__wp_get_image_suffix( $resized, $rotated, $expected ) {
     1498                $this->assertSame( $expected, _wp_get_image_suffix( $resized, $rotated ) );
     1499        }
     1500
     1501        /**
     1502         * Data provider for the `test__wp_get_image_suffix()` test.
     1503         */
     1504        public function data_test__wp_get_image_suffix() {
     1505                return array(
     1506                        array( false, false, '' ),
     1507                        array( true, false, 'scaled' ),
     1508                        array( false, true, 'rotated' ),
     1509                        array( true, true, 'scaled' ),
     1510                );
     1511        }
    10291512}
  • 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/**