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.


git-svn-id: https://develop.svn.wordpress.org/trunk@56214 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Joe McGill 2023-07-11 13:56:55 +00:00
parent baec3f39c1
commit ed174730d3
3 changed files with 149 additions and 4 deletions

View File

@ -5725,9 +5725,10 @@ function wp_get_loading_optimization_attributes( $tag_name, $attr, $context ) {
/*
* The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
* as they are likely above the fold.
* as they are likely above the fold. Shortcodes are processed after content images, so if
* thresholds haven't already been met, apply the same logic to those as well.
*/
if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
if ( 'the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context ) {
// Only elements within the main query loop have special handling.
if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
$loading_attrs['loading'] = 'lazy';

View File

@ -221,6 +221,14 @@ function do_shortcode( $content, $ignore_html = false ) {
return $content;
}
// Ensure this context is only added once if shortcodes are nested.
$has_filter = has_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
$filter_added = false;
if ( ! $has_filter ) {
$filter_added = add_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
}
$content = do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames );
$pattern = get_shortcode_regex( $tagnames );
@ -229,9 +237,29 @@ function do_shortcode( $content, $ignore_html = false ) {
// Always restore square braces so we don't break things like <!--[if IE ]>.
$content = unescape_invalid_shortcodes( $content );
// Only remove the filter if it was added in this scope.
if ( $filter_added ) {
remove_filter( 'wp_get_attachment_image_context', '_filter_do_shortcode_context' );
}
return $content;
}
/**
* Filter the `wp_get_attachment_image_context` hook during shortcode rendering.
*
* When wp_get_attachment_image() is called during shortcode rendering, we need to make clear
* that the context is a shortcode and not part of the theme's template rendering logic.
*
* @since 6.3.0
* @access private
*
* @return string The filtered context value for wp_get_attachment_images when doing shortcodes.
*/
function _filter_do_shortcode_context() {
return 'do_shortcode';
}
/**
* Retrieves the shortcode regular expression for searching.
*

View File

@ -78,8 +78,8 @@ CAP;
/**
* Ensures that the static content media count, fetchpriority element flag and related filter are reset between tests.
*/
public function set_up() {
parent::set_up();
public function tear_down() {
parent::tear_down();
$this->reset_content_media_count();
$this->reset_omit_loading_attr_filter();
@ -5005,6 +5005,122 @@ EOF;
);
}
/**
* @ticket 58681
*
* @dataProvider data_wp_get_loading_optimization_attributes_in_shortcodes
*/
public function test_wp_get_loading_optimization_attributes_in_shortcodes( $setup, $expected, $message ) {
$attr = $this->get_width_height_for_high_priority();
$setup();
// The first image processed in a shortcode should have fetchpriority set to high.
$this->assertSame(
$expected,
wp_get_loading_optimization_attributes( 'img', $attr, 'do_shortcode' ),
$message
);
}
public function data_wp_get_loading_optimization_attributes_in_shortcodes() {
return array(
'main_shortcode_image_should_have_fetchpriority_high' => array(
'setup' => function () {
global $wp_query;
// Set WP_Query to be in the loop and the main query.
$wp_query->in_the_loop = true;
$this->set_main_query( $wp_query );
},
'expected' => array(
'fetchpriority' => 'high',
),
'message' => 'Fetch priority not applied to during shortcode rendering.',
),
'main_shortcode_image_after_threshold_is_loading_lazy' => array(
'setup' => function () {
global $wp_query;
// Set WP_Query to be in the loop and the main query.
$wp_query->in_the_loop = true;
$this->set_main_query( $wp_query );
// Set internal flags so lazy should be applied.
wp_high_priority_element_flag( false );
wp_increase_content_media_count( 3 );
},
'expected' => array(
'loading' => 'lazy',
),
'message' => 'Lazy-loading not applied to during shortcode rendering.',
),
'shortcode_image_outside_of_the_loop_are_loaded_lazy' => array(
'setup' => function () {
// Avoid setting up the WP_Query object for the loop.
return;
},
'expected' => array(
'loading' => 'lazy',
),
'message' => 'Lazy-loading not applied to shortcodes outside the loop.',
),
);
}
/**
* @ticket 58681
*/
public function test_content_rendering_with_shortcodes() {
// The gallery shortcode will dynamically create image markup that should be optimized.
$content = "[gallery ids='" . self::$large_id . "' size='large']";
$actual = apply_filters( 'the_content', $content );
$this->assertStringContainsString(
// Since the main query and loop isn't set, this should be lazily loaded.
'loading="lazy"',
$actual,
'Could not confirm shortcodes get optimizations applied.'
);
}
/**
* @ticket 58681
*/
public function test_content_rendering_with_shortcodes_nested() {
global $wp_query;
// Set WP_Query to be in the loop and the main query.
$wp_query->in_the_loop = true;
$this->set_main_query( $wp_query );
add_shortcode(
'div',
function ( $atts, $content = null ) {
$parsed_atts = shortcode_atts(
array(
'class' => '',
),
$atts
);
$class = ! empty( $parsed_atts['class'] ) ? sprintf( ' class="%s"', $parsed_atts['class'] ) : null;
return sprintf( '<div %s>%s</div>', $class, do_shortcode( $content ) );
}
);
// The gallery shortcode will dynamically create image markup that should be optimized.
$content = "[div][gallery ids='" . self::$large_id . "' size='large'][div]";
$actual = apply_filters( 'the_content', $content );
$this->assertStringContainsString(
// Since this is in the loop, it should have a high fetchpriority.
'fetchpriority="high"',
$actual,
'Could not confirm shortcodes get optimizations applied.'
);
}
/**
* @ticket 58235
*