<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[56507] trunk: Editor: Ensure main query loop is entered for singular content in block themes.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/56507">56507</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/56507","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-09-01 17:30:02 +0000 (Fri, 01 Sep 2023)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/ticket/58154">#58154</a>.
See <a href="https://core.trac.wordpress.org/ticket/59225">#59225</a>, <a href="https://core.trac.wordpress.org/ticket/58027">#58027</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocktemplatephp">trunk/src/wp-includes/block-template.php</a></li>
<li><a href="#trunktestsphpunittestsblocktemplatephp">trunk/tests/phpunit/tests/block-template.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesblocktemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/block-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-template.php 2023-09-01 17:24:56 UTC (rev 56506)
+++ trunk/src/wp-includes/block-template.php 2023-09-01 17:30:02 UTC (rev 56507)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -210,12 +210,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @global string $_wp_current_template_content
</span><span class="cx" style="display: block; padding: 0 10px"> * @global WP_Embed $wp_embed
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @global WP_Query $wp_query
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @return string Block template markup.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function get_the_block_template_html() {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- global $_wp_current_template_content;
- global $wp_embed;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ global $_wp_current_template_content, $wp_embed, $wp_query;
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! $_wp_current_template_content ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_user_logged_in() ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -228,7 +228,30 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $content = $wp_embed->autoembed( $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $content = shortcode_unautop( $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $content = do_shortcode( $content );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $content = do_blocks( $content );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /*
+ * 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 );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $content = wptexturize( $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $content = convert_smilies( $content );
</span><span class="cx" style="display: block; padding: 0 10px"> $content = wp_filter_content_tags( $content, 'template' );
</span></span></pre></div>
<a id="trunktestsphpunittestsblocktemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/block-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/block-template.php 2023-09-01 17:24:56 UTC (rev 56506)
+++ trunk/tests/phpunit/tests/block-template.php 2023-09-01 17:30:02 UTC (rev 56507)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -184,4 +184,118 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $resolved_template_path = locate_block_template( '', 'search', array() );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertSame( '', $resolved_template_path );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * 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 = '<!-- wp:post-title /--><!-- wp:test/in-the-loop-logger /-->';
+
+ $expected = '<div class="wp-site-blocks">';
+ $expected .= '<h2 class="wp-block-post-title">A single post</h2>';
+ $expected .= '</div>';
+
+ $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
+ * @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;
+
+ // 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:test/in-the-loop-logger /-->';
+ $_wp_current_template_content .= '<!-- wp:query {"query":{"inherit":true}} -->';
+ $_wp_current_template_content .= '<!-- wp:post-template -->';
+ $_wp_current_template_content .= '<!-- wp:post-title /-->';
+ $_wp_current_template_content .= '<!-- wp:post-content /--><!-- wp:test/in-the-loop-logger /-->';
+ $_wp_current_template_content .= '<!-- /wp:post-template -->';
+ $_wp_current_template_content .= '<!-- /wp:query -->';
+
+ $expected = '<div class="wp-site-blocks">';
+ $expected .= '<ul class="wp-block-post-template is-layout-flow wp-block-post-template-is-layout-flow wp-block-query-is-layout-flow">';
+ $expected .= '<li class="wp-block-post post-' . $post_id . ' post type-post status-publish format-standard hentry category-uncategorized">';
+ $expected .= '<h2 class="wp-block-post-title">A single post</h2>';
+ $expected .= '<div class="entry-content wp-block-post-content is-layout-flow wp-block-post-content-is-layout-flow">' . wpautop( 'The content.' ) . '</div>';
+ $expected .= '</li>';
+ $expected .= '</ul>';
+ $expected .= '</div>';
+
+ $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' );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>
</body>
</html>