diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 19f3b5fb140ec..4d5742a4b91a1 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -241,7 +241,7 @@ function get_the_block_template_html() { $content = wptexturize( $content ); $content = convert_smilies( $content ); $content = shortcode_unautop( $content ); - $content = wp_filter_content_tags( $content ); + $content = wp_filter_content_tags( $content, 'template' ); $content = do_shortcode( $content ); $content = str_replace( ']]>', ']]>', $content ); diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 01062855a1da8..95836d98a24b2 100644 --- a/src/wp-includes/media.php +++ b/src/wp-includes/media.php @@ -5444,25 +5444,40 @@ function wp_get_webp_info( $filename ) { * that the `loading` attribute should be skipped. */ function wp_get_loading_attr_default( $context ) { - // Only elements with 'the_content' or 'the_post_thumbnail' context have special handling. - if ( 'the_content' !== $context && 'the_post_thumbnail' !== $context ) { - return 'lazy'; + // Skip lazy-loading for the overall block template, as it is handled more granularly. + if ( 'template' === $context ) { + return false; } - // Only elements within the main query loop have special handling. - if ( is_admin() || ! in_the_loop() || ! is_main_query() ) { - return 'lazy'; + // Do not lazy-load images in the header block template part, as they are likely above the fold. + $header_area = WP_TEMPLATE_PART_AREA_HEADER; + if ( "template_part_{$header_area}" === $context ) { + return false; } - // Increase the counter since this is a main query content element. - $content_media_count = wp_increase_content_media_count(); + /* + * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded, + * as they are likely above the fold. + */ + if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) { + // Only elements within the main query loop have special handling. + if ( is_admin() || ! in_the_loop() || ! is_main_query() ) { + return 'lazy'; + } - // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted. - if ( $content_media_count <= wp_omit_loading_attr_threshold() ) { - return false; + // Increase the counter since this is a main query content element. + $content_media_count = wp_increase_content_media_count(); + + // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted. + if ( $content_media_count <= wp_omit_loading_attr_threshold() ) { + return false; + } + + // For elements after the threshold, lazy-load them as usual. + return 'lazy'; } - // For elements after the threshold, lazy-load them as usual. + // Lazy-load by default for any unknown context. return 'lazy'; } diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index fb93d781c3054..8e76c43807fcd 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -3547,7 +3547,13 @@ public function data_attachment_permalinks_based_on_parent_status() { } /** + * Tests that wp_get_loading_attr_default() returns the expected loading attribute value. + * * @ticket 53675 + * @ticket 56930 + * + * @covers ::wp_get_loading_attr_default + * * @dataProvider data_wp_get_loading_attr_default * * @param string $context @@ -3588,6 +3594,10 @@ public function test_wp_get_loading_attr_default( $context ) { // Yes, for all subsequent elements. $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) ); } + + // Exceptions: In the following contexts, images shouldn't be lazy-loaded by default. + $this->assertFalse( wp_get_loading_attr_default( 'template' ), 'Images run through the overall block template filter should not be lazy-loaded.' ); + $this->assertFalse( wp_get_loading_attr_default( 'template_part_' . WP_TEMPLATE_PART_AREA_HEADER ), 'Images in the footer block template part should not be lazy-loaded.' ); } public function data_wp_get_loading_attr_default() { @@ -3700,6 +3710,159 @@ function() { $this->assertSame( 3, $omit_threshold ); } + /** + * Tests that wp_filter_content_tags() does not add loading="lazy" to the first + * image in the loop when using a block theme. + * + * @ticket 56930 + * + * @covers ::wp_filter_content_tags + * @covers ::wp_get_loading_attr_default + */ + public function test_wp_filter_content_tags_does_not_lazy_load_first_image_in_block_theme() { + global $_wp_current_template_content, $wp_query, $wp_the_query, $post; + + // Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' ); + + $img1 = get_image_tag( self::$large_id, '', '', '', 'large' ); + $img2 = get_image_tag( self::$large_id, '', '', '', 'medium' ); + $lazy_img2 = wp_img_tag_add_loading_attr( $img2, 'the_content' ); + + // Only the second image should be lazy-loaded. + $post_content = $img1 . $img2; + $expected_content = wpautop( $img1 . $lazy_img2 ); + + // Update the post to test with so that it has the above post content. + wp_update_post( + array( + 'ID' => self::$post_ids['publish'], + 'post_content' => $post_content, + 'post_content_filtered' => $post_content, + ) + ); + + $wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) ); + $wp_the_query = $wp_query; + $post = get_post( self::$post_ids['publish'] ); + $this->reset_content_media_count(); + $this->reset_omit_loading_attr_filter(); + + $_wp_current_template_content = ''; + + $html = get_the_block_template_html(); + $this->assertSame( '
' . $expected_content . '
', $html ); + } + + /** + * Tests that wp_filter_content_tags() does not add loading="lazy" + * to the featured image when using a block theme. + * + * @ticket 56930 + * + * @covers ::wp_filter_content_tags + * @covers ::wp_get_loading_attr_default + */ + public function test_wp_filter_content_tags_does_not_lazy_load_first_featured_image_in_block_theme() { + global $_wp_current_template_content, $wp_query, $wp_the_query, $post; + + // Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' ); + add_filter( + 'wp_get_attachment_image_attributes', + function( $attr ) { + unset( $attr['srcset'], $attr['sizes'], $attr['decoding'] ); + return $attr; + } + ); + + $content_img = get_image_tag( self::$large_id, '', '', '', 'large' ); + $lazy_content_img = wp_img_tag_add_loading_attr( $content_img, 'the_content' ); + + // The featured image should not be lazy-loaded as it is the first image. + $featured_image_id = self::$large_id; + update_post_meta( self::$post_ids['publish'], '_thumbnail_id', $featured_image_id ); + $expected_featured_image = '
' . get_the_post_thumbnail( self::$post_ids['publish'], 'post-thumbnail', array( 'loading' => false ) ) . '
'; + + // The post content image should be lazy-loaded since the featured image appears above. + $post_content = $content_img; + $expected_content = wpautop( $lazy_content_img ); + + // Update the post to test with so that it has the above post content. + wp_update_post( + array( + 'ID' => self::$post_ids['publish'], + 'post_content' => $post_content, + 'post_content_filtered' => $post_content, + ) + ); + + $wp_query = new WP_Query( array( 'p' => self::$post_ids['publish'] ) ); + $wp_the_query = $wp_query; + $post = get_post( self::$post_ids['publish'] ); + $this->reset_content_media_count(); + $this->reset_omit_loading_attr_filter(); + + $_wp_current_template_content = ' '; + + $html = get_the_block_template_html(); + $this->assertSame( '
' . $expected_featured_image . '
' . $expected_content . '
', $html ); + } + + /** + * Tests that wp_filter_content_tags() does not add loading="lazy" to images + * in a "Header" template part. + * + * @ticket 56930 + * + * @covers ::wp_filter_content_tags + * @covers ::wp_get_loading_attr_default + */ + public function test_wp_filter_content_tags_does_not_lazy_load_images_in_header() { + global $_wp_current_template_content; + + // Do not add srcset, sizes, or decoding attributes as they are irrelevant for this test. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + add_filter( 'wp_img_tag_add_decoding_attr', '__return_false' ); + + // Use a single image for each header and footer template parts. + $header_img = get_image_tag( self::$large_id, '', '', '', 'large' ); + $footer_img = get_image_tag( self::$large_id, '', '', '', 'medium' ); + + // Create header and footer template parts. + $header_post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_template_part', + 'post_status' => 'publish', + 'post_name' => 'header', + 'post_content' => $header_img, + ) + ); + wp_set_post_terms( $header_post_id, WP_TEMPLATE_PART_AREA_HEADER, 'wp_template_part_area' ); + wp_set_post_terms( $header_post_id, get_stylesheet(), 'wp_theme' ); + $footer_post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_template_part', + 'post_status' => 'publish', + 'post_name' => 'footer', + 'post_content' => $footer_img, + ) + ); + wp_set_post_terms( $footer_post_id, WP_TEMPLATE_PART_AREA_FOOTER, 'wp_template_part_area' ); + wp_set_post_terms( $footer_post_id, get_stylesheet(), 'wp_theme' ); + + $_wp_current_template_content = ''; + + // Header image should not be lazy-loaded, footer image should be lazy-loaded. + $expected_template_content = '
' . $header_img . '
'; + $expected_template_content .= ''; + + $html = get_the_block_template_html(); + $this->assertSame( '
' . $expected_template_content . '
', $html ); + } + private function reset_content_media_count() { // Get current value without increasing. $content_media_count = wp_increase_content_media_count( 0 );