Make WordPress Core


Ignore:
Timestamp:
07/21/2022 06:01:01 PM (11 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-includes/media.php

    r53715 r53751  
    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.
     
    18971902
    18981903    return $content;
     1904}
     1905
     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' );
    18992015}
    19002016
Note: See TracChangeset for help on using the changeset viewer.