Make WordPress Core


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

    r53558 r53751  
    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 ) );
     
    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
     
    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
     
    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(
     
    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' );
     
    24692477
    24702478        $this->assertSame( $expected, $actual );
     2479        remove_filter( 'wp_content_image_mimes', '__return_empty_array' );
    24712480    }
    24722481
     
    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 ) );
     
    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
     
    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 ) );
     
    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
     
    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
     
    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
     
    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' );
     
    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.
     
    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
Note: See TracChangeset for help on using the changeset viewer.