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/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.