Make WordPress Core


Ignore:
Timestamp:
07/21/2022 06:01:01 PM (4 years ago)
Author:
adamsilverstein
Message:

Media: enable generating multiple mime types for image uploads; specifically WebP versions for JPEG images by default.

This changeset adds the capability for core media uploads to generate sub sized images in more than a single mime type. The output formats for each mime type can be controlled through a filter. WebP is used as an additional output format for JPEG images by default to improve front end performance.

When generating additional mime types, only images which are smaller than the respective original are retained. By default, additional mime type images are only generated for the built-in core image sizes and any custom sizes that have opted in.

Image meta is updated with a new 'sources' array containing file details for each mime type. Each image size in the 'sizes' array also gets a new 'sources' array that contains the image file details for each mime type.

This change also increases image upload retries to accommodate additional image sizes. It also adds a $mime_type parameter to the wp_get_missing_image_subsizes function and filter.

This change adds three new filters to enable full control of secondary mime image generation and output:

  • A new filter wp_image_sizes_with_additional_mime_type_support that filters the sizes that support secondary mime type output. Developers can use this to control the output of additional mime type sub-sized images on a per size basis.
  • A new filter wp_upload_image_mime_transforms that filters the output mime types for a given input mime type. Developers can use this to control generation of additional mime types for a given input mime type or even override the original mime type.
  • A new filter wp_content_image_mimes which controls image mime type output selection and order for frontend content. Developers can use this to control the mime type output preference order for content images. Content images inserted from the media library will use the available image versions based on the order from this filter.

Thanks to the many contributors who helped develop, test and give feedback on this feature.

A haiku to summarize:

Upload a JPEG
Images of all sizes
Output as WebPs

Props flixos90, MatthiasReinholz, studiolxv, markhowellsmead, eatingrules, pbiron, mukesh27, joegrainger, mehulkaklotar, tweetythierry, akshitsethi, peterwilsoncc, eugenemanuilov, mitogh, shetheliving, clarkeemily, codekraft, mikeschroder, clorith, kasparsd, spacedmonkey, trevorpfromsandee, jb510, scofennellgmailcom, seedsca, cagsmith, karinclimber, dainemawer, baxbridge, grapplerulrich, sobatkras, chynnabenton, tonylocalword, barneydavey, kwillmorth, garymatthews919, olliejones, imarkinteractive, jeffpaul, feastdesignco, webbeetle, masteradhoc.

See #55443.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/image.php

    r53547 r53751  
    7878 *
    7979 * @since 5.3.0
    80  *
    81  * @param int $attachment_id The image attachment post ID.
     80 * @since 6.1.0 The $mime_type parameter was added.
     81 *
     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();
     90        }
     91
     92        $primary_mime_type = get_post_mime_type( get_post( $attachment_id ) );
     93        if ( ! $mime_type ) {
     94                $mime_type = $primary_mime_type;
    8895        }
    8996
     
    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        /**
     
    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
     
    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
     
    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.
     
    168195                }
    169196        } else {
    170                 $missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
    171 
    172                 if ( empty( $missing_sizes ) ) {
    173                         return $image_meta;
    174                 }
    175 
    176                 // This also updates the image meta.
    177                 $image_meta = _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $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 );
     199
     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                        }
     210                }
     211
     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
     
    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
     
    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.
     
    249296                'filesize' => wp_filesize( $file ),
    250297                'sizes'    => array(),
     298                'sources'  => array(),
    251299        );
    252300
     
    258306        }
    259307
     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 );
     335
     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
    260412        // 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'] ) {
    262 
     413        if ( 'image/png' !== $mime_type ) {
    263414                /**
    264415                 * Filters the "BIG image" threshold value.
     
    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                         }
    352                 }
    353         }
    354 
    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 );
    361 
    362         $new_sizes = wp_get_registered_image_subsizes();
    363 
    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 );
    376 
    377         return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
     450                }
     451        }
     452
     453        return array( $editor, $resized, $rotated );
     454}
     455
     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        }
     472
     473        if ( true === $rotated ) {
     474                // Append `-rotated` to the image file name.
     475                return 'rotated';
     476        }
     477
     478        if ( is_wp_error( $resized ) || is_wp_error( $rotated ) ) {
     479                // TODO: Log errors.
     480        }
     481        return '';
     482}
     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
     
    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();
     521        }
     522
     523        if ( ! $mime_type ) {
     524                $mime_type = wp_get_image_mime( $file );
    399525        }
    400526
     
    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                }
     
    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 ) ) {
     
    440570                return $image_meta;
    441571        }
     572
     573        $editor->set_output_mime_type( $mime_type );
    442574
    443575        // If stored EXIF data exists, rotate the source image before creating sub-sizes.
     
    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                        }
     
    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                        }
    470636                        wp_update_attachment_metadata( $attachment_id, $image_meta );
    471637                }
     
    473639
    474640        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 );
     722                        wp_update_attachment_metadata( $attachment_id, $image_meta );
     723                }
     724        }
     725
     726        return $image_meta;
     727}
     728
     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;
    475760}
    476761
     
    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                        }
     
    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}
Note: See TracChangeset for help on using the changeset viewer.