diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 794aec9120..ffed0e0436 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() ) { @@ -228,7 +228,30 @@ 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. The while loop will by + * 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() && 1 === $wp_query->post_count && have_posts() ) { + 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' ); diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index 16c0978813..3344b895bb 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -184,4 +184,118 @@ class Tests_Block_Template extends WP_UnitTestCase { $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 + * @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; + + // 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 = '