diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 111a99d02f..27dd4dfb3a 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5506,6 +5506,16 @@ function wp_get_loading_attr_default( $context ) { return false; } + /* + * Skip programmatically created images within post content as they need to be handled together with the other + * images within the post content. + * Without this clause, they would already be counted below which skews the number and can result in the first + * post content image being lazy-loaded only because there are images elsewhere in the post content. + */ + if ( ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) && doing_filter( 'the_content' ) ) { + return false; + } + /* * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded, * as they are likely above the fold. diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 92d09f8f1a..cf7c72b06d 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -3853,6 +3853,101 @@ EOF; $this->assertSame( '
' . $expected_template_content . '
', $html ); } + /** + * @ticket 58089 + * + * @covers ::wp_filter_content_tags + * @covers ::wp_get_loading_attr_default + */ + public function test_wp_filter_content_tags_does_not_lazy_load_special_images_within_the_content() { + global $wp_query, $wp_the_query; + + // Force no lazy-loading on the image tag expected in the content. + $expected_content = wpautop( wp_get_attachment_image( self::$large_id, 'large', false, array( 'loading' => false ) ) ); + + // Overwrite post content with an image. + add_filter( + 'the_content', + static function() { + // Replace content with an image tag, i.e. the 'wp_get_attachment_image' context is used while running 'the_content' filter. + return wp_get_attachment_image( self::$large_id, 'large', false ); + }, + 9 // Run before wp_filter_content_tags(). + ); + + /* + * We have to run a main query loop so that the first 'the_content' context image is not + * lazy-loaded. + * Without the fix from 58089, the image would still be lazy-loaded since the check for the + * separately invoked 'wp_get_attachment_image' context would lead to that. + */ + $wp_query = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) ); + $wp_the_query = $wp_query; + $this->reset_content_media_count(); + $this->reset_omit_loading_attr_filter(); + $content = ''; + while ( have_posts() ) { + the_post(); + $content = get_echo( 'the_content' ); + } + + // Ensure that parsed content has the image without lazy-loading. + $this->assertSame( $expected_content, $content ); + } + + /** + * Tests that wp_get_loading_attr_default() returns 'lazy' for special contexts when they're used outside of 'the_content' filter. + * + * @ticket 58089 + * + * @covers ::wp_get_loading_attr_default + * + * @dataProvider data_special_contexts_for_the_content + * + * @param string $context Context for the element for which the `loading` attribute value is requested. + */ + public function test_wp_get_loading_attr_default_should_return_lazy_for_special_contexts_outside_of_the_content( $context ) { + $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) ); + } + + /** + * Tests that wp_get_loading_attr_default() returns false for special contexts when they're used within 'the_content' filter. + * + * @ticket 58089 + * + * @covers ::wp_get_loading_attr_default + * + * @dataProvider data_special_contexts_for_the_content + * + * @param string $context Context for the element for which the `loading` attribute value is requested. + */ + public function test_wp_get_loading_attr_default_should_return_false_for_special_contexts_within_the_content( $context ) { + remove_all_filters( 'the_content' ); + + $result = null; + add_filter( + 'the_content', + function( $content ) use ( &$result, $context ) { + $result = wp_get_loading_attr_default( $context ); + return $content; + } + ); + apply_filters( 'the_content', '' ); + $this->assertFalse( $result ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_special_contexts_for_the_content() { + return array( + 'the_post_thumbnail' => array( 'context' => 'the_post_thumbnail' ), + 'wp_get_attachment_image' => array( 'context' => 'wp_get_attachment_image' ), + ); + } + private function reset_content_media_count() { // Get current value without increasing. $content_media_count = wp_increase_content_media_count( 0 );