Make WordPress Core


Ignore:
Timestamp:
07/21/2022 06:01:01 PM (22 months 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.