Make WordPress Core

Changeset 56214


Ignore:
Timestamp:
07/11/2023 01:56:55 PM (17 months ago)
Author:
joemcgill
Message:

Media: Optimize images created in shortcodes.

This fixes an issue where images dynamically created during shortcode rendering (e.g., shortcode image galleries), were not getting image optimizations like loading="lazy" or fetchpriority="hight" applied. Note that even after this commit, shortcodes are processed after the main content images, which can affect the order in which optimizations are applied in content areas.

Follow-up to [56037].

Props spacedmonkey, flixos90, thekt12, swissspidy, joemcgill.
Fixes #58681.

Location:
trunk
Files:
3 edited

Legend:

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

    r56191 r56214  
    57265726    /*
    57275727     * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
    5728      * as they are likely above the fold.
    5729      */
    5730     if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
     5728     * as they are likely above the fold. Shortcodes are processed after content images, so if
     5729     * thresholds haven't already been met, apply the same logic to those as well.
     5730     */
     5731    if ( 'the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context ) {
    57315732        // Only elements within the main query loop have special handling.
    57325733        if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
  • trunk/src/wp-includes/shortcodes.php

    r56194 r56214  
    222222    }
    223223
     224    // Ensure this context is only added once if shortcodes are nested.
     225    $has_filter = has_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
     226    $filter_added = false;
     227
     228    if ( ! $has_filter ) {
     229        $filter_added = add_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
     230    }
     231
    224232    $content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );
    225233
     
    230238    $content = unescape_invalid_shortcodes( $content );
    231239
     240    // Only remove the filter if it was added in this scope.
     241    if ( $filter_added ) {
     242        remove_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
     243    }
     244
    232245    return $content;
     246}
     247
     248/**
     249 * Filter the `wp_get_attachment_image_context` hook during shortcode rendering.
     250 *
     251 * When wp_get_attachment_image() is called during shortcode rendering, we need to make clear
     252 * that the context is a shortcode and not part of the theme's template rendering logic.
     253 *
     254 * @since 6.3.0
     255 * @access private
     256 *
     257 * @return string The filtered context value for wp_get_attachment_images when doing shortcodes.
     258 */
     259function _filter_do_shortcode_context() {
     260    return 'do_shortcode';
    233261}
    234262
  • trunk/tests/phpunit/tests/media.php

    r56164 r56214  
    7979     * Ensures that the static content media count, fetchpriority element flag and related filter are reset between tests.
    8080     */
    81     public function set_up() {
    82         parent::set_up();
     81    public function tear_down() {
     82        parent::tear_down();
    8383
    8484        $this->reset_content_media_count();
     
    50035003            wp_get_loading_optimization_attributes( 'img', $attr, 'test' ),
    50045004            'loading optimization attr array should be empty.'
     5005        );
     5006    }
     5007
     5008    /**
     5009     * @ticket 58681
     5010     *
     5011     * @dataProvider data_wp_get_loading_optimization_attributes_in_shortcodes
     5012     */
     5013    public function test_wp_get_loading_optimization_attributes_in_shortcodes( $setup, $expected, $message ) {
     5014        $attr = $this->get_width_height_for_high_priority();
     5015        $setup();
     5016
     5017        // The first image processed in a shortcode should have fetchpriority set to high.
     5018        $this->assertSame(
     5019            $expected,
     5020            wp_get_loading_optimization_attributes( 'img', $attr, 'do_shortcode' ),
     5021            $message
     5022        );
     5023    }
     5024
     5025    public function data_wp_get_loading_optimization_attributes_in_shortcodes() {
     5026        return array(
     5027            'main_shortcode_image_should_have_fetchpriority_high'  => array(
     5028                'setup'    => function () {
     5029                    global $wp_query;
     5030
     5031                    // Set WP_Query to be in the loop and the main query.
     5032                    $wp_query->in_the_loop = true;
     5033                    $this->set_main_query( $wp_query );
     5034                },
     5035                'expected' => array(
     5036                    'fetchpriority' => 'high',
     5037                ),
     5038                'message'  => 'Fetch priority not applied to during shortcode rendering.',
     5039            ),
     5040            'main_shortcode_image_after_threshold_is_loading_lazy' => array(
     5041                'setup'    => function () {
     5042                    global $wp_query;
     5043
     5044                    // Set WP_Query to be in the loop and the main query.
     5045                    $wp_query->in_the_loop = true;
     5046                    $this->set_main_query( $wp_query );
     5047
     5048                    // Set internal flags so lazy should be applied.
     5049                    wp_high_priority_element_flag( false );
     5050                    wp_increase_content_media_count( 3 );
     5051                },
     5052                'expected' => array(
     5053                    'loading' => 'lazy',
     5054                ),
     5055                'message'  => 'Lazy-loading not applied to during shortcode rendering.',
     5056            ),
     5057            'shortcode_image_outside_of_the_loop_are_loaded_lazy'  => array(
     5058                'setup'    => function () {
     5059                    // Avoid setting up the WP_Query object for the loop.
     5060                    return;
     5061                },
     5062                'expected' => array(
     5063                    'loading' => 'lazy',
     5064                ),
     5065                'message'  => 'Lazy-loading not applied to shortcodes outside the loop.',
     5066            ),
     5067        );
     5068    }
     5069
     5070    /**
     5071     * @ticket 58681
     5072     */
     5073    public function test_content_rendering_with_shortcodes() {
     5074        // The gallery shortcode will dynamically create image markup that should be optimized.
     5075        $content = "[gallery ids='" . self::$large_id . "' size='large']";
     5076        $actual  = apply_filters( 'the_content', $content );
     5077
     5078        $this->assertStringContainsString(
     5079            // Since the main query and loop isn't set, this should be lazily loaded.
     5080            'loading="lazy"',
     5081            $actual,
     5082            'Could not confirm shortcodes get optimizations applied.'
     5083        );
     5084    }
     5085
     5086    /**
     5087     * @ticket 58681
     5088     */
     5089    public function test_content_rendering_with_shortcodes_nested() {
     5090        global $wp_query;
     5091
     5092        // Set WP_Query to be in the loop and the main query.
     5093        $wp_query->in_the_loop = true;
     5094        $this->set_main_query( $wp_query );
     5095
     5096        add_shortcode(
     5097            'div',
     5098            function ( $atts, $content = null ) {
     5099                $parsed_atts = shortcode_atts(
     5100                    array(
     5101                        'class' => '',
     5102                    ),
     5103                    $atts
     5104                );
     5105
     5106                $class = ! empty( $parsed_atts['class'] ) ? sprintf( ' class="%s"', $parsed_atts['class'] ) : null;
     5107
     5108                return sprintf( '<div %s>%s</div>', $class, do_shortcode( $content ) );
     5109            }
     5110        );
     5111
     5112        // The gallery shortcode will dynamically create image markup that should be optimized.
     5113        $content = "[div][gallery ids='" . self::$large_id . "' size='large'][div]";
     5114        $actual  = apply_filters( 'the_content', $content );
     5115
     5116        $this->assertStringContainsString(
     5117            // Since this is in the loop, it should have a high fetchpriority.
     5118            'fetchpriority="high"',
     5119            $actual,
     5120            'Could not confirm shortcodes get optimizations applied.'
    50055121        );
    50065122    }
Note: See TracChangeset for help on using the changeset viewer.