Make WordPress Core

Changeset 56507


Ignore:
Timestamp:
09/01/2023 05:30:02 PM (10 months ago)
Author:
flixos90
Message:

Editor: Ensure main query loop is entered for singular content in block themes.

Block themes currently lack the means to trigger the main query loop for singular content, since they cannot reasonably use the core/query and core/post-template blocks which are intended only for displaying a list of posts. So far, the missing main query loop on singular block templates has been worked around by enforcing the loop in certain core/post-* blocks, which however causes other bugs.

This changeset ensures that the main query loop is still started for singular block theme templates, by wrapping the entire template into the loop, which will by definition only have a single cycle as it only encompasses a single post. This is currently the most reliable solution, since even if there were blocks to properly trigger the main query loop on singular content, it would be unrealistic to expect all existing block themes to update their templates accordingly. It may be revisited in the future.

Props gziolo, youknowriad, joemcgill, costdev, mukesh27, flixos90.
Fixes #58154.
See #59225, #58027.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-template.php

    r56209 r56507  
    211211 * @global string   $_wp_current_template_content
    212212 * @global WP_Embed $wp_embed
     213 * @global WP_Query $wp_query
    213214 *
    214215 * @return string Block template markup.
    215216 */
    216217function get_the_block_template_html() {
    217     global $_wp_current_template_content;
    218     global $wp_embed;
     218    global $_wp_current_template_content, $wp_embed, $wp_query;
    219219
    220220    if ( ! $_wp_current_template_content ) {
     
    229229    $content = shortcode_unautop( $content );
    230230    $content = do_shortcode( $content );
    231     $content = do_blocks( $content );
     231
     232    /*
     233     * Most block themes omit the `core/query` and `core/post-template` blocks in their singular content templates.
     234     * While this technically still works since singular content templates are always for only one post, it results in
     235     * the main query loop never being entered which causes bugs in core and the plugin ecosystem.
     236     *
     237     * The workaround below ensures that the loop is started even for those singular templates. The while loop will by
     238     * definition only go through a single iteration, i.e. `do_blocks()` is only called once. Additional safeguard
     239     * checks are included to ensure the main query loop has not been tampered with and really only encompasses a
     240     * single post.
     241     *
     242     * Even if the block template contained a `core/query` and `core/post-template` block referencing the main query
     243     * loop, it would not cause errors since it would use a cloned instance and go through the same loop of a single
     244     * post, within the actual main query loop.
     245     */
     246    if ( is_singular() && 1 === $wp_query->post_count && have_posts() ) {
     247        while ( have_posts() ) {
     248            the_post();
     249            $content = do_blocks( $content );
     250        }
     251    } else {
     252        $content = do_blocks( $content );
     253    }
     254
    232255    $content = wptexturize( $content );
    233256    $content = convert_smilies( $content );
  • trunk/tests/phpunit/tests/block-template.php

    r55457 r56507  
    185185        $this->assertSame( '', $resolved_template_path );
    186186    }
     187
     188    /**
     189     * Tests that `get_the_block_template_html()` wraps block parsing into the query loop when on a singular template.
     190     *
     191     * This is necessary since block themes do not include the necessary blocks to trigger the main query loop, and
     192     * since there is only a single post in the main query loop in such cases anyway.
     193     *
     194     * @ticket 58154
     195     * @covers ::get_the_block_template_html
     196     */
     197    public function test_get_the_block_template_html_enforces_singular_query_loop() {
     198        global $_wp_current_template_content, $wp_query, $wp_the_query;
     199
     200        // Register test block to log `in_the_loop()` results.
     201        $in_the_loop_logs = array();
     202        $this->register_in_the_loop_logger_block( $in_the_loop_logs );
     203
     204        // Set main query to single post.
     205        $post_id      = self::factory()->post->create( array( 'post_title' => 'A single post' ) );
     206        $wp_query     = new WP_Query( array( 'p' => $post_id ) );
     207        $wp_the_query = $wp_query;
     208
     209        // Use block template that just renders post title and the above test block.
     210        $_wp_current_template_content = '<!-- wp:post-title /--><!-- wp:test/in-the-loop-logger /-->';
     211
     212        $expected  = '<div class="wp-site-blocks">';
     213        $expected .= '<h2 class="wp-block-post-title">A single post</h2>';
     214        $expected .= '</div>';
     215
     216        $output = get_the_block_template_html();
     217        $this->unregister_in_the_loop_logger_block();
     218        $this->assertSame( $expected, $output, 'Unexpected block template output' );
     219        $this->assertSame( array( true ), $in_the_loop_logs, 'Main query loop was not triggered' );
     220    }
     221
     222    /**
     223     * Tests that `get_the_block_template_html()` does not start the main query loop generally.
     224     *
     225     * @ticket 58154
     226     * @covers ::get_the_block_template_html
     227     */
     228    public function test_get_the_block_template_html_does_not_generally_enforce_loop() {
     229        global $_wp_current_template_content, $wp_query, $wp_the_query;
     230
     231        // Register test block to log `in_the_loop()` results.
     232        $in_the_loop_logs = array();
     233        $this->register_in_the_loop_logger_block( $in_the_loop_logs );
     234
     235        // Set main query to a general post query (i.e. not for a specific post).
     236        $post_id      = self::factory()->post->create(
     237            array(
     238                'post_title'   => 'A single post',
     239                'post_content' => 'The content.',
     240            )
     241        );
     242        $wp_query     = new WP_Query(
     243            array(
     244                'post_type'   => 'post',
     245                'post_status' => 'publish',
     246            )
     247        );
     248        $wp_the_query = $wp_query;
     249
     250        /*
     251         * Use block template that renders the above test block, followed by a main query loop.
     252         * `get_the_block_template_html()` should not start the loop, but the `core/query` and `core/post-template`
     253         * blocks should.
     254         */
     255        $_wp_current_template_content  = '<!-- wp:test/in-the-loop-logger /-->';
     256        $_wp_current_template_content .= '<!-- wp:query {"query":{"inherit":true}} -->';
     257        $_wp_current_template_content .= '<!-- wp:post-template -->';
     258        $_wp_current_template_content .= '<!-- wp:post-title /-->';
     259        $_wp_current_template_content .= '<!-- wp:post-content /--><!-- wp:test/in-the-loop-logger /-->';
     260        $_wp_current_template_content .= '<!-- /wp:post-template -->';
     261        $_wp_current_template_content .= '<!-- /wp:query -->';
     262
     263        $expected  = '<div class="wp-site-blocks">';
     264        $expected .= '<ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow wp-block-query-is-layout-flow">';
     265        $expected .= '<li class="wp-block-post post-' . $post_id . ' post type-post status-publish format-standard hentry category-uncategorized">';
     266        $expected .= '<h2 class="wp-block-post-title">A single post</h2>';
     267        $expected .= '<div class="entry-content wp-block-post-content is-layout-flow wp-block-post-content-is-layout-flow">' . wpautop( 'The content.' ) . '</div>';
     268        $expected .= '</li>';
     269        $expected .= '</ul>';
     270        $expected .= '</div>';
     271
     272        $output = get_the_block_template_html();
     273        $this->unregister_in_the_loop_logger_block();
     274        $this->assertSame( $expected, $output, 'Unexpected block template output' );
     275        $this->assertSame( array( false, true ), $in_the_loop_logs, 'Main query loop was triggered incorrectly' );
     276    }
     277
     278    /**
     279     * Registers a test block to log `in_the_loop()` results.
     280     *
     281     * @param array $in_the_loop_logs Array to log function results in. Passed by reference.
     282     */
     283    private function register_in_the_loop_logger_block( array &$in_the_loop_logs ) {
     284        register_block_type(
     285            'test/in-the-loop-logger',
     286            array(
     287                'render_callback' => function() use ( &$in_the_loop_logs ) {
     288                    $in_the_loop_logs[] = in_the_loop();
     289                    return '';
     290                },
     291            )
     292        );
     293    }
     294
     295    /**
     296     * Unregisters the test block registered by the `register_in_the_loop_logger_block()` method.
     297     */
     298    private function unregister_in_the_loop_logger_block() {
     299        unregister_block_type( 'test/in-the-loop-logger' );
     300    }
    187301}
Note: See TracChangeset for help on using the changeset viewer.