From abba2ae09a5b836eba62ca2c89fcb1f02ee29960 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 1 Nov 2022 16:09:41 -0700 Subject: [PATCH 1/7] Avoid double-processing post content when parsing block template HTML. --- src/wp-includes/block-template.php | 33 +++++++++++++++++++++++-- src/wp-includes/blocks/post-content.php | 7 ++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 0aa571117e899..c277358ac311e 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -225,7 +225,7 @@ function _block_template_render_title_tag() { * @return string Block template markup. */ function get_the_block_template_html() { - global $_wp_current_template_content; + global $_wp_current_template_content, $_wp_template_parsed_post_content; global $wp_embed; if ( ! $_wp_current_template_content ) { @@ -238,13 +238,42 @@ function get_the_block_template_html() { $content = $wp_embed->run_shortcode( $_wp_current_template_content ); $content = $wp_embed->autoembed( $content ); $content = do_blocks( $content ); + + // Write the global into a local variable so that it cannot be modified after this. + $parsed_post_content = $_wp_template_parsed_post_content; + + // Replace all post content within the template with a temporary placeholder so that the post content is not + // unnecessarily processed again. See https://core.trac.wordpress.org/ticket/55996. + if ( is_array( $parsed_post_content ) ) { + foreach ( $parsed_post_content as $i => $post_content ) { + $pos = strpos( $content, $post_content ); + if ( $pos !== false ) { + $content = substr_replace( $content, "", $pos, strlen( $post_content ) ); + } + } + } + $content = wptexturize( $content ); $content = convert_smilies( $content ); $content = shortcode_unautop( $content ); - $content = wp_filter_content_tags( $content ); + $content = wp_filter_content_tags( $content, 'the_block_template' ); $content = do_shortcode( $content ); $content = str_replace( ']]>', ']]>', $content ); + // Now that the template has been fully processed, add back in all post content. + if ( is_array( $parsed_post_content ) ) { + $content = str_replace( + array_map( + function( $i ) { + return ""; + }, + array_keys( $parsed_post_content ) + ), + $parsed_post_content, + $content + ); + } + // Wrap block template in .wp-site-blocks to allow for specific descendant styles // (e.g. `.wp-site-blocks > *`). return '
' . $content . '
'; diff --git a/src/wp-includes/blocks/post-content.php b/src/wp-includes/blocks/post-content.php index 228a0d52f4069..3bf00be270e52 100644 --- a/src/wp-includes/blocks/post-content.php +++ b/src/wp-includes/blocks/post-content.php @@ -14,6 +14,8 @@ * @return string Returns the filtered post content of the current post. */ function render_block_core_post_content( $attributes, $content, $block ) { + global $_wp_template_parsed_post_content; + static $seen_ids = array(); if ( ! isset( $block->context['postId'] ) ) { @@ -59,6 +61,11 @@ function render_block_core_post_content( $attributes, $content, $block ) { return ''; } + if ( ! is_array( $_wp_template_parsed_post_content ) ) { + $_wp_template_parsed_post_content = array(); + } + $_wp_template_parsed_post_content[] = $content; + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'entry-content' ) ); return ( From a810c7e2430d85afe82322ac6c0b239111de439f Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 1 Nov 2022 16:34:56 -0700 Subject: [PATCH 2/7] Yoda I use OK. --- src/wp-includes/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index c277358ac311e..72caf0d929c23 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -247,7 +247,7 @@ function get_the_block_template_html() { if ( is_array( $parsed_post_content ) ) { foreach ( $parsed_post_content as $i => $post_content ) { $pos = strpos( $content, $post_content ); - if ( $pos !== false ) { + if ( false !== $pos ) { $content = substr_replace( $content, "", $pos, strlen( $post_content ) ); } } From 3a89988dbfea3864dcacafd549e02bc4cb56f448 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 2 Nov 2022 12:24:55 -0700 Subject: [PATCH 3/7] Ensure placeholder uniqueness and prevent tampering with global. --- src/wp-includes/block-template.php | 34 ++++++++++++++++++------- src/wp-includes/blocks/post-content.php | 7 ----- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 72caf0d929c23..bab09d1dafd58 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -225,7 +225,7 @@ function _block_template_render_title_tag() { * @return string Block template markup. */ function get_the_block_template_html() { - global $_wp_current_template_content, $_wp_template_parsed_post_content; + global $_wp_current_template_content; global $wp_embed; if ( ! $_wp_current_template_content ) { @@ -235,20 +235,36 @@ function get_the_block_template_html() { return; } + // Record all post content strings into this list to avoid double parsing. + $parsed_post_content = array(); + add_filter( + 'render_block_core/post-content', + function( $block_content ) use ( &$parsed_post_content ) { + $parsed_post_content[] = $block_content; + return $block_content; + }, + PHP_INT_MAX + ); + $content = $wp_embed->run_shortcode( $_wp_current_template_content ); $content = $wp_embed->autoembed( $content ); $content = do_blocks( $content ); - // Write the global into a local variable so that it cannot be modified after this. - $parsed_post_content = $_wp_template_parsed_post_content; - // Replace all post content within the template with a temporary placeholder so that the post content is not // unnecessarily processed again. See https://core.trac.wordpress.org/ticket/55996. - if ( is_array( $parsed_post_content ) ) { + if ( ! empty( $parsed_post_content ) ) { + // Ensure a placeholder is used that is not already present in the content. + $content_placeholder = ''; + $existing_placeholder_count = 0; + while ( preg_match( '/' . sprintf( $content_placeholder, '[0-9]+' ) . '/', $content ) ) { + $existing_placeholder_count++; + $content_placeholder = ''; + } + foreach ( $parsed_post_content as $i => $post_content ) { $pos = strpos( $content, $post_content ); if ( false !== $pos ) { - $content = substr_replace( $content, "", $pos, strlen( $post_content ) ); + $content = substr_replace( $content, sprintf( $content_placeholder, $i ), $pos, strlen( $post_content ) ); } } } @@ -261,11 +277,11 @@ function get_the_block_template_html() { $content = str_replace( ']]>', ']]>', $content ); // Now that the template has been fully processed, add back in all post content. - if ( is_array( $parsed_post_content ) ) { + if ( ! empty( $parsed_post_content ) ) { $content = str_replace( array_map( - function( $i ) { - return ""; + function( $i ) use ( $content_placeholder ) { + return sprintf( $content_placeholder, $i ); }, array_keys( $parsed_post_content ) ), diff --git a/src/wp-includes/blocks/post-content.php b/src/wp-includes/blocks/post-content.php index 3bf00be270e52..228a0d52f4069 100644 --- a/src/wp-includes/blocks/post-content.php +++ b/src/wp-includes/blocks/post-content.php @@ -14,8 +14,6 @@ * @return string Returns the filtered post content of the current post. */ function render_block_core_post_content( $attributes, $content, $block ) { - global $_wp_template_parsed_post_content; - static $seen_ids = array(); if ( ! isset( $block->context['postId'] ) ) { @@ -61,11 +59,6 @@ function render_block_core_post_content( $attributes, $content, $block ) { return ''; } - if ( ! is_array( $_wp_template_parsed_post_content ) ) { - $_wp_template_parsed_post_content = array(); - } - $_wp_template_parsed_post_content[] = $content; - $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'entry-content' ) ); return ( From ae0a332bfccb3a6e2f6f768a6c8c5ee7f130627b Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 2 Nov 2022 12:25:07 -0700 Subject: [PATCH 4/7] Add comprehensive test coverage. --- tests/phpunit/tests/block-template.php | 145 ++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index b53aa496b0300..49fb78c371718 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -11,10 +11,48 @@ * @group block-templates */ class Tests_Block_Template extends WP_UnitTestCase { - private static $post; + private static $post_id; private static $template_canvas_path = ABSPATH . WPINC . '/template-canvas.php'; + private static $demo_block_template; + private static $demo_post_content; + private static $demo_parsed_template; + private static $demo_parsed_content; + + public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + self::$demo_block_template = '
'; + self::$demo_block_template .= ''; + self::$demo_block_template .= ' '; + self::$demo_block_template .= ''; + self::$demo_block_template .= '
'; + + self::$demo_post_content = ''; + self::$demo_post_content .= '
'; + self::$demo_post_content .= ''; + + self::$post_id = $factory->post->create( + array( + 'post_title' => 'Demo Block Editor Post', + 'post_content' => self::$demo_post_content, + 'post_type' => 'post', + 'post_status' => 'publish', + ) + ); + + self::$demo_parsed_content = '
'; + + self::$demo_parsed_template = '
'; + self::$demo_parsed_template .= '

Demo Block Editor Post

'; + self::$demo_parsed_template .= ' '; + self::$demo_parsed_template .= '
' . self::$demo_parsed_content . '
'; + self::$demo_parsed_template .= '
'; + } + + public static function wpTearDownAfterClass() { + wp_delete_post( self::$post_id, true ); + } + public function set_up() { parent::set_up(); switch_theme( 'block-theme' ); @@ -226,4 +264,109 @@ public function test_resolve_home_block_template_no_resolution() { $this->assertNull( $template ); } + + /** + * @ticket 55996 + */ + public function test_get_the_block_template_html_parses_template_content() { + global $_wp_current_template_content, $wp_query, $wp_the_query; + + $wp_query = new WP_Query( array( 'p' => self::$post_id ) ); + $wp_the_query = $wp_query; + the_post(); + + $_wp_current_template_content = self::$demo_block_template; + + $html = get_the_block_template_html(); + $this->assertSame( '
' . self::$demo_parsed_template . '
', $html ); + } + + /** + * @ticket 55996 + */ + public function test_get_the_block_template_html_parses_post_content_only_once() { + global $_wp_current_template_content, $wp_query, $wp_the_query; + + // This filter should only be run once per image (and the post here only has a single image). + add_filter( + 'wp_content_img_tag', + function( $img ) { + return str_replace( ' self::$post_id ) ); + $wp_the_query = $wp_query; + the_post(); + + $_wp_current_template_content = self::$demo_block_template; + + $html = get_the_block_template_html(); + $this->assertStringContainsString( 'assertStringNotContainsString( ' self::$post_id ) ); + $wp_the_query = $wp_query; + the_post(); + + $_wp_current_template_content = self::$demo_block_template; + + $html = get_the_block_template_html(); + $this->assertStringContainsString( 'assertStringNotContainsString( ' self::$post_id, + 'post_content' => $post_content, + 'post_content_filtered' => $post_content, + ) + ); + + $expected_parsed_template = str_replace( self::$demo_parsed_content, $post_content, '' . self::$demo_parsed_template ); + + $wp_query = new WP_Query( array( 'p' => self::$post_id ) ); + $wp_the_query = $wp_query; + the_post(); + + $_wp_current_template_content = $demo_block_template; + + $html = get_the_block_template_html(); + $this->assertSame( '
' . $expected_parsed_template . '
', $html ); + } } From f49d5326f4e3e7a4d0d9912253dee6190ed9a3dc Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 2 Nov 2022 12:31:09 -0700 Subject: [PATCH 5/7] Fix PHPCS warning. --- tests/phpunit/tests/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index 49fb78c371718..d36153cd59f56 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -40,7 +40,7 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { ) ); - self::$demo_parsed_content = '
'; + self::$demo_parsed_content = '
'; self::$demo_parsed_template = '
'; self::$demo_parsed_template .= '

Demo Block Editor Post

'; From ac75d2fc555de950835e23ce897f7d13ba84339b Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 2 Nov 2022 12:53:35 -0700 Subject: [PATCH 6/7] Add test to also verify https://core.trac.wordpress.org/ticket/56930 specifically. --- tests/phpunit/tests/media.php | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 0d5ac7586369b..ec3ff03907576 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -3577,6 +3577,48 @@ function() { $this->assertSame( $content_expected, $content_filtered ); } + /** + * @ticket 55996 + * @ticket 56930 + */ + public function test_wp_filter_content_tags_lazy_load_first_image_in_block_theme() { + global $_wp_current_template_content, $wp_query, $wp_the_query; + + $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 ); + + // 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' ); + + // 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; + the_post(); + $this->reset_content_media_count(); + $this->reset_omit_loading_attr_filter(); + + $_wp_current_template_content = ''; + + // This function must ensure it does not process post content twice, as that leads to the loading="lazy" + // attribute being added even on the first image due to the subsequent run. + $html = get_the_block_template_html(); + $this->assertSame( '
' . $expected_content . '
', $html ); + } + /** * @ticket 53675 */ From 0ea23a3cd57e268b1ef4b6aa84f553ea7fae4353 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 3 Nov 2022 15:02:45 -0700 Subject: [PATCH 7/7] Remove lazy-loading specific test now that 56930 is being fixed separately. --- tests/phpunit/tests/media.php | 42 ----------------------------------- 1 file changed, 42 deletions(-) diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index ec3ff03907576..0d5ac7586369b 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -3577,48 +3577,6 @@ function() { $this->assertSame( $content_expected, $content_filtered ); } - /** - * @ticket 55996 - * @ticket 56930 - */ - public function test_wp_filter_content_tags_lazy_load_first_image_in_block_theme() { - global $_wp_current_template_content, $wp_query, $wp_the_query; - - $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 ); - - // 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' ); - - // 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; - the_post(); - $this->reset_content_media_count(); - $this->reset_omit_loading_attr_filter(); - - $_wp_current_template_content = ''; - - // This function must ensure it does not process post content twice, as that leads to the loading="lazy" - // attribute being added even on the first image due to the subsequent run. - $html = get_the_block_template_html(); - $this->assertSame( '
' . $expected_content . '
', $html ); - } - /** * @ticket 53675 */