<!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>[48224] trunk: Editor: Introduce block context</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/48224">48224</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/48224","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>gziolo</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-06-30 11:02:22 +0000 (Tue, 30 Jun 2020)</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: Introduce block context

Backports a new block context feature from Gutenberg. The purpose of this feature is to be able to establish values in a block hierarchy which can be consumed by blocks anywhere lower in the same hierarchy. These values can be established either by the framework, or by other blocks which provide these values. See documentation: https://github.com/WordPress/gutenberg/blob/master/docs/designers-developers/developers/block-api/block-context.md

Props aduth, epiqueras.
Fixes <a href="https://core.trac.wordpress.org/ticket/49927">#49927</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocksphp">trunk/src/wp-includes/blocks.php</a></li>
<li><a href="#trunksrcwpincludesclasswpblockphp">trunk/src/wp-includes/class-wp-block.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsblocksblockcontextphp">trunk/tests/phpunit/tests/blocks/block-context.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesblocksphp"></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/blocks.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/blocks.php  2020-06-30 01:02:52 UTC (rev 48223)
+++ trunk/src/wp-includes/blocks.php    2020-06-30 11:02:22 UTC (rev 48224)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -649,13 +649,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.0.0
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @global WP_Post $post The post to edit.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @global WP_Post  $post         The post to edit.
+ * @global WP_Query $wp_the_query WordPress Query object.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @param array $block A single parsed block object.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param array $parsed_block A single parsed block object.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return string String of rendered HTML.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function render_block( $block ) {
-       global $post;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function render_block( $parsed_block ) {
+       global $post, $wp_query;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Allows render_block() to be short-circuited, by returning a non-null value.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -662,15 +663,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.1.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param string|null $pre_render The pre-rendered content. Default null.
-        * @param array       $block      The block being rendered.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param string|null $pre_render   The pre-rendered content. Default null.
+        * @param array       $parsed_block The block being rendered.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $pre_render = apply_filters( 'pre_render_block', null, $block );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $pre_render = apply_filters( 'pre_render_block', null, $parsed_block );
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( ! is_null( $pre_render ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $pre_render;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $source_block = $block;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $source_block = $parsed_block;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Filters the block being rendered in render_block(), before it's processed.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -677,39 +678,45 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 5.1.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param array $block        The block being rendered.
-        * @param array $source_block An un-modified copy of $block, as it appeared in the source content.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param array $parsed_block The block being rendered.
+        * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $block = apply_filters( 'render_block_data', $block, $source_block );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $block_type    = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
-       $is_dynamic    = $block['blockName'] && null !== $block_type && $block_type->is_dynamic();
-       $block_content = '';
-       $index         = 0;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $context = array();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        foreach ( $block['innerContent'] as $chunk ) {
-               $block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] );
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! empty( $post ) ) {
+               $context['postId'] = $post->ID;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! is_array( $block['attrs'] ) ) {
-               $block['attrs'] = array();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /*
+               * The `postType` context is largely unnecessary server-side, since the
+               * ID is usually sufficient on its own. That being said, since a block's
+               * manifest is expected to be shared between the server and the client,
+               * it should be included to consistently fulfill the expectation.
+               */
+               $context['postType'] = $post->post_type;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( $is_dynamic ) {
-               $global_post   = $post;
-               $block_content = $block_type->render( $block['attrs'], $block_content );
-               $post          = $global_post;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( isset( $wp_query->tax_query->queried_terms['category'] ) ) {
+               $context['query'] = array( 'categoryIds' => array() );
+               foreach ( $wp_query->tax_query->queried_terms['category']['terms'] as $category_slug_or_id ) {
+                       $context['query']['categoryIds'][] = 'slug' === $wp_query->tax_query->queried_terms['category']['field'] ? get_cat_ID( $category_slug_or_id ) : $category_slug_or_id;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Filters the content of a single block.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * Filters the default context provided to a rendered block.
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @since 5.0.0
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @since 5.5.0
</ins><span class="cx" style="display: block; padding: 0 10px">          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param string $block_content The block content about to be appended.
-        * @param array  $block         The full block, including name and attributes.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param array $context      Default context.
+        * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        return apply_filters( 'render_block', $block_content, $block );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $context = apply_filters( 'render_block_context', $context, $parsed_block );
+
+       $block = new WP_Block( $parsed_block, $context );
+
+       return $block->render();
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpblockphp"></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/class-wp-block.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-block.php  2020-06-30 01:02:52 UTC (rev 48223)
+++ trunk/src/wp-includes/class-wp-block.php    2020-06-30 11:02:22 UTC (rev 48224)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -226,7 +226,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        wp_enqueue_style( $this->block_type->style );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /** This filter is documented in src/wp-includes/blocks.php */
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /**
+                * Filters the content of a single block.
+                *
+                * @since 5.0.0
+                *
+                * @param string $block_content The block content about to be appended.
+                * @param array  $block         The full block, including name and attributes.
+                */
</ins><span class="cx" style="display: block; padding: 0 10px">                 return apply_filters( 'render_block', $block_content, $this->parsed_block );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunktestsphpunittestsblocksblockcontextphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/blocks/block-context.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/block-context.php                                (rev 0)
+++ trunk/tests/phpunit/tests/blocks/block-context.php  2020-06-30 11:02:22 UTC (rev 48224)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,215 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * WP_Block_Context Tests
+ *
+ * @package WordPress
+ * @subpackage Blocks
+ * @since 5.5.0
+ */
+
+/**
+ * Tests for WP_Block_Context
+ *
+ * @since 5.5.0
+ *
+ * @group blocks
+ */
+class WP_Block_Context_Test extends WP_UnitTestCase {
+
+       /**
+        * Registered block names.
+        *
+        * @var string[]
+        */
+       private $registered_block_names = array();
+
+       /**
+        * Sets up each test method.
+        */
+       public function setUp() {
+               global $post;
+
+               parent::setUp();
+
+               $args = array(
+                       'post_content' => 'example',
+                       'post_excerpt' => '',
+               );
+
+               $post = $this->factory()->post->create_and_get( $args );
+               setup_postdata( $post );
+       }
+
+       /**
+        * Tear down each test method.
+        */
+       public function tearDown() {
+               parent::tearDown();
+
+               while ( ! empty( $this->registered_block_names ) ) {
+                       $block_name = array_pop( $this->registered_block_names );
+                       unregister_block_type( $block_name );
+               }
+       }
+
+       /**
+        * Registers a block type.
+        *
+        * @param string|WP_Block_Type $name Block type name including namespace, or alternatively a
+        *                                   complete WP_Block_Type instance. In case a WP_Block_Type
+        *                                   is provided, the $args parameter will be ignored.
+        * @param array                $args {
+        *     Optional. Array of block type arguments. Any arguments may be defined, however the
+        *     ones described below are supported by default. Default empty array.
+        *
+        *     @type callable $render_callback Callback used to render blocks of this block type.
+        * }
+        */
+       protected function register_block_type( $name, $args ) {
+               register_block_type( $name, $args );
+
+               $this->registered_block_names[] = $name;
+       }
+
+       /**
+        * Tests that a block which provides context makes that context available to
+        * its inner blocks.
+        *
+        * @ticket 49927
+        */
+       function test_provides_block_context() {
+               $provided_context = array();
+
+               $this->register_block_type(
+                       'gutenberg/test-context-provider',
+                       array(
+                               'attributes'       => array(
+                                       'contextWithAssigned'   => array(
+                                               'type' => 'number',
+                                       ),
+                                       'contextWithDefault'    => array(
+                                               'type'    => 'number',
+                                               'default' => 0,
+                                       ),
+                                       'contextWithoutDefault' => array(
+                                               'type' => 'number',
+                                       ),
+                                       'contextNotRequested'   => array(
+                                               'type' => 'number',
+                                       ),
+                               ),
+                               'provides_context' => array(
+                                       'gutenberg/contextWithAssigned'   => 'contextWithAssigned',
+                                       'gutenberg/contextWithDefault'    => 'contextWithDefault',
+                                       'gutenberg/contextWithoutDefault' => 'contextWithoutDefault',
+                                       'gutenberg/contextNotRequested'   => 'contextNotRequested',
+                               ),
+                       )
+               );
+
+               $this->register_block_type(
+                       'gutenberg/test-context-consumer',
+                       array(
+                               'uses_context'    => array(
+                                       'gutenberg/contextWithDefault',
+                                       'gutenberg/contextWithAssigned',
+                                       'gutenberg/contextWithoutDefault',
+                               ),
+                               'render_callback' => function( $attributes, $content, $block ) use ( &$provided_context ) {
+                                       $provided_context[] = $block->context;
+
+                                       return '';
+                               },
+                       )
+               );
+
+               $parsed_blocks = parse_blocks(
+                       '<!-- wp:gutenberg/test-context-provider {"contextWithAssigned":10} -->' .
+                       '<!-- wp:gutenberg/test-context-consumer /-->' .
+                       '<!-- /wp:gutenberg/test-context-provider -->'
+               );
+
+               render_block( $parsed_blocks[0] );
+
+               $this->assertEquals(
+                       array(
+                               'gutenberg/contextWithDefault'  => 0,
+                               'gutenberg/contextWithAssigned' => 10,
+                       ),
+                       $provided_context[0]
+               );
+       }
+
+       /**
+        * Tests that a block can receive default-provided context through
+        * render_block.
+        *
+        * @ticket 49927
+        */
+       function test_provides_default_context() {
+               global $post;
+
+               $provided_context = array();
+
+               $this->register_block_type(
+                       'gutenberg/test-context-consumer',
+                       array(
+                               'uses_context'    => array( 'postId', 'postType' ),
+                               'render_callback' => function( $attributes, $content, $block ) use ( &$provided_context ) {
+                                       $provided_context[] = $block->context;
+
+                                       return '';
+                               },
+                       )
+               );
+
+               $parsed_blocks = parse_blocks( '<!-- wp:gutenberg/test-context-consumer /-->' );
+
+               render_block( $parsed_blocks[0] );
+
+               $this->assertEquals(
+                       array(
+                               'postId'   => $post->ID,
+                               'postType' => $post->post_type,
+                       ),
+                       $provided_context[0]
+               );
+       }
+
+       /**
+        * Tests that default block context can be filtered.
+        *
+        * @ticket 49927
+        */
+       function test_default_context_is_filterable() {
+               $provided_context = array();
+
+               $this->register_block_type(
+                       'gutenberg/test-context-consumer',
+                       array(
+                               'uses_context'    => array( 'example' ),
+                               'render_callback' => function( $attributes, $content, $block ) use ( &$provided_context ) {
+                                       $provided_context[] = $block->context;
+
+                                       return '';
+                               },
+                       )
+               );
+
+               $filter_block_context = function( $context ) {
+                       $context['example'] = 'ok';
+                       return $context;
+               };
+
+               $parsed_blocks = parse_blocks( '<!-- wp:gutenberg/test-context-consumer /-->' );
+
+               add_filter( 'render_block_context', $filter_block_context );
+
+               render_block( $parsed_blocks[0] );
+
+               remove_filter( 'render_block_context', $filter_block_context );
+
+               $this->assertEquals( array( 'example' => 'ok' ), $provided_context[0] );
+       }
+
+}
</ins></span></pre>
</div>
</div>

</body>
</html>