From d0905a6cf1b4f9a608bca8145baeb4e73d800caa Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Mon, 28 Aug 2023 10:50:38 -0700 Subject: [PATCH 1/5] Ensure main query loop is entered for singular content in block themes. --- src/wp-includes/block-template.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 794aec91203aa..1b9bccfc5105c 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -228,7 +228,25 @@ function get_the_block_template_html() { $content = $wp_embed->autoembed( $content ); $content = shortcode_unautop( $content ); $content = do_shortcode( $content ); - $content = do_blocks( $content ); + + /* + * Most block themes omit the `core/query` and `core/post-template` blocks in their singular content templates. + * While this technically still works since singular content templates are always for only one post, it results in + * the main query loop never being entered which causes bugs in core and the plugin ecosystem. + * + * The workaround below ensures that the loop is started even for those singular templates without the relevant + * blocks. The while loop will by definition only go through a single iteration, i.e. `do_blocks()` is only called + * once. + */ + if ( is_singular() && ! has_block( 'core/post-template', $content ) ) { + while ( have_posts() ) { + the_post(); + $content = do_blocks( $content ); + } + } else { + $content = do_blocks( $content ); + } + $content = wptexturize( $content ); $content = convert_smilies( $content ); $content = wp_filter_content_tags( $content, 'template' ); From 0726f9b85cd4f6b6ae616bb93994d48f5b4980ff Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 29 Aug 2023 10:43:48 -0700 Subject: [PATCH 2/5] Remove unreliable check for core/post-template block presence as it would most likely be for a non-inherit query. --- src/wp-includes/block-template.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 1b9bccfc5105c..316d9e98fd6a9 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -234,11 +234,14 @@ function get_the_block_template_html() { * While this technically still works since singular content templates are always for only one post, it results in * the main query loop never being entered which causes bugs in core and the plugin ecosystem. * - * The workaround below ensures that the loop is started even for those singular templates without the relevant - * blocks. The while loop will by definition only go through a single iteration, i.e. `do_blocks()` is only called - * once. + * The workaround below ensures that the loop is started even for those singular templates. The while loop will by + * definition only go through a single iteration, i.e. `do_blocks()` is only called once. + * + * Even if the block template contained a `core/query` and `core/post-template` block referencing the main query + * loop, it would not cause errors since it would use a cloned instance and go through the same loop of a single + * post, within the actual main query loop. */ - if ( is_singular() && ! has_block( 'core/post-template', $content ) ) { + if ( is_singular() ) { while ( have_posts() ) { the_post(); $content = do_blocks( $content ); From 6343c7eaa73b1f219cd22a52f46895c6bbfae211 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 31 Aug 2023 15:50:25 -0700 Subject: [PATCH 3/5] Add test coverage. --- tests/phpunit/tests/block-template.php | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index 16c0978813ff1..cdffc38e4ed11 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -184,4 +184,116 @@ public function test_template_remains_unchanged_if_templates_array_is_empty() { $resolved_template_path = locate_block_template( '', 'search', array() ); $this->assertSame( '', $resolved_template_path ); } + + /** + * Tests that `get_the_block_template_html()` wraps block parsing into the query loop when on a singular template. + * + * This is necessary since block themes do not include the necessary blocks to trigger the main query loop, and + * since there is only a single post in the main query loop in such cases anyway. + * + * @ticket 58154 + */ + public function test_get_the_block_template_html_enforces_singular_query_loop() { + global $_wp_current_template_content, $wp_query, $wp_the_query; + + // Register test block to log `in_the_loop()` results. + $in_the_loop_logs = array(); + $this->register_in_the_loop_logger_block( $in_the_loop_logs ); + + // Set main query to single post. + $post_id = self::factory()->post->create( array( 'post_title' => 'A single post' ) ); + $wp_query = new WP_Query( array( 'p' => $post_id ) ); + $wp_the_query = $wp_query; + + // Use block template that just renders post title and the above test block. + $_wp_current_template_content = ''; + + $expected = '
'; + $expected .= '

A single post

'; + $expected .= '
'; + + $output = get_the_block_template_html(); + $this->unregister_in_the_loop_logger_block(); + $this->assertSame( $expected, $output, 'Unexpected block template output' ); + $this->assertSame( array( true ), $in_the_loop_logs, 'Main query loop was not triggered' ); + } + + /** + * Tests that `get_the_block_template_html()` does not start the main query loop generally. + * + * @ticket 58154 + */ + public function test_get_the_block_template_html_does_not_generally_enforce_loop() { + global $_wp_current_template_content, $wp_query, $wp_the_query; + + // Register test block to log `in_the_loop()` results. + $in_the_loop_logs = array(); + $this->register_in_the_loop_logger_block( $in_the_loop_logs ); + + // Set main query to a general post query (i.e. not for a specific post). + $post_id = self::factory()->post->create( + array( + 'post_title' => 'A single post', + 'post_content' => 'The content.', + ) + ); + $wp_query = new WP_Query( + array( + 'post_type' => 'post', + 'post_status' => 'publish', + ) + ); + $wp_the_query = $wp_query; + + /* + * Use block template that renders the above test block, followed by a main query loop. + * `get_the_block_template_html()` should not start the loop, but the `core/query` and `core/post-template` + * blocks should. + */ + $_wp_current_template_content = ''; + $_wp_current_template_content .= ''; + $_wp_current_template_content .= ''; + $_wp_current_template_content .= ''; + $_wp_current_template_content .= ''; + $_wp_current_template_content .= ''; + $_wp_current_template_content .= ''; + + $expected = '
'; + $expected .= '
    '; + $expected .= '
  • '; + $expected .= '

    A single post

    '; + $expected .= '
    ' . wpautop( 'The content.' ) . '
    '; + $expected .= '
  • '; + $expected .= '
'; + $expected .= '
'; + + $output = get_the_block_template_html(); + $this->unregister_in_the_loop_logger_block(); + $this->assertSame( $expected, $output, 'Unexpected block template output' ); + $this->assertSame( array( false, true ), $in_the_loop_logs, 'Main query loop was triggered incorrectly' ); + } + + /** + * Registers a test block to log `in_the_loop()` results. + * + * @param array $in_the_loop_logs Array to log function results in. Passed by reference. + */ + private function register_in_the_loop_logger_block( array &$in_the_loop_logs ) { + register_block_type( + 'test/in-the-loop-logger', + array( + 'render_callback' => function() use ( &$in_the_loop_logs ) { + $in_the_loop_logs[] = in_the_loop(); + return ''; + }, + ) + ); + } + + /** + * Unregisters the test block registered by the `register_in_the_loop_logger_block()` method. + */ + private function unregister_in_the_loop_logger_block() { + unregister_block_type( 'test/in-the-loop-logger' ); + } } From 766d453fbff73a99391f4f512126654f6e796bc2 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 31 Aug 2023 16:09:00 -0700 Subject: [PATCH 4/5] Add safeguards to is_singular() clause and clarify in docs. --- src/wp-includes/block-template.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 316d9e98fd6a9..ffed0e04364c0 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -210,12 +210,12 @@ function _block_template_render_title_tag() { * * @global string $_wp_current_template_content * @global WP_Embed $wp_embed + * @global WP_Query $wp_query * * @return string Block template markup. */ function get_the_block_template_html() { - global $_wp_current_template_content; - global $wp_embed; + global $_wp_current_template_content, $wp_embed, $wp_query; if ( ! $_wp_current_template_content ) { if ( is_user_logged_in() ) { @@ -235,13 +235,15 @@ function get_the_block_template_html() { * the main query loop never being entered which causes bugs in core and the plugin ecosystem. * * The workaround below ensures that the loop is started even for those singular templates. The while loop will by - * definition only go through a single iteration, i.e. `do_blocks()` is only called once. + * definition only go through a single iteration, i.e. `do_blocks()` is only called once. Additional safeguard + * checks are included to ensure the main query loop has not been tampered with and really only encompasses a + * single post. * * Even if the block template contained a `core/query` and `core/post-template` block referencing the main query * loop, it would not cause errors since it would use a cloned instance and go through the same loop of a single * post, within the actual main query loop. */ - if ( is_singular() ) { + if ( is_singular() && 1 === $wp_query->post_count && have_posts() ) { while ( have_posts() ) { the_post(); $content = do_blocks( $content ); From 17fda605edba3030220713ff86a2904b53da784e Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Thu, 31 Aug 2023 16:15:58 -0700 Subject: [PATCH 5/5] Add covers annotations. --- tests/phpunit/tests/block-template.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index cdffc38e4ed11..3344b895bb4e6 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -192,6 +192,7 @@ public function test_template_remains_unchanged_if_templates_array_is_empty() { * since there is only a single post in the main query loop in such cases anyway. * * @ticket 58154 + * @covers ::get_the_block_template_html */ public function test_get_the_block_template_html_enforces_singular_query_loop() { global $_wp_current_template_content, $wp_query, $wp_the_query; @@ -222,6 +223,7 @@ public function test_get_the_block_template_html_enforces_singular_query_loop() * Tests that `get_the_block_template_html()` does not start the main query loop generally. * * @ticket 58154 + * @covers ::get_the_block_template_html */ public function test_get_the_block_template_html_does_not_generally_enforce_loop() { global $_wp_current_template_content, $wp_query, $wp_the_query;