diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 9b612d68bd..4881ab4d87 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -649,67 +649,74 @@ function _excerpt_render_inner_columns_blocks( $columns, $allowed_blocks ) { * * @since 5.0.0 * - * @global WP_Post $post The post to edit. + * @global WP_Post $post The post to edit. + * @global WP_Query $wp_the_query WordPress Query object. * - * @param array $block A single parsed block object. + * @param array $parsed_block A single parsed block object. * @return string String of rendered HTML. */ -function render_block( $block ) { - global $post; +function render_block( $parsed_block ) { + global $post, $wp_query; /** * Allows render_block() to be short-circuited, by returning a non-null value. * * @since 5.1.0 * - * @param string|null $pre_render The pre-rendered content. Default null. - * @param array $block The block being rendered. + * @param string|null $pre_render The pre-rendered content. Default null. + * @param array $parsed_block The block being rendered. */ - $pre_render = apply_filters( 'pre_render_block', null, $block ); + $pre_render = apply_filters( 'pre_render_block', null, $parsed_block ); if ( ! is_null( $pre_render ) ) { return $pre_render; } - $source_block = $block; + $source_block = $parsed_block; /** * Filters the block being rendered in render_block(), before it's processed. * * @since 5.1.0 * - * @param array $block The block being rendered. - * @param array $source_block An un-modified copy of $block, as it appeared in the source content. + * @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. */ - $block = apply_filters( 'render_block_data', $block, $source_block ); + $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block ); - $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; + $context = array(); - foreach ( $block['innerContent'] as $chunk ) { - $block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] ); + if ( ! empty( $post ) ) { + $context['postId'] = $post->ID; + + /* + * 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; } - if ( ! is_array( $block['attrs'] ) ) { - $block['attrs'] = array(); - } - - if ( $is_dynamic ) { - $global_post = $post; - $block_content = $block_type->render( $block['attrs'], $block_content ); - $post = $global_post; + 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; + } } /** - * Filters the content of a single block. + * Filters the default context provided to a rendered block. * - * @since 5.0.0 + * @since 5.5.0 * - * @param string $block_content The block content about to be appended. - * @param array $block The full block, including name and attributes. + * @param array $context Default context. + * @param array $parsed_block Block being rendered, filtered by `render_block_data`. */ - return apply_filters( 'render_block', $block_content, $block ); + $context = apply_filters( 'render_block_context', $context, $parsed_block ); + + $block = new WP_Block( $parsed_block, $context ); + + return $block->render(); } /** diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 979fca9070..325ca6e65c 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -226,7 +226,14 @@ class WP_Block { wp_enqueue_style( $this->block_type->style ); } - /** This filter is documented in src/wp-includes/blocks.php */ + /** + * 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. + */ return apply_filters( 'render_block', $block_content, $this->parsed_block ); } diff --git a/tests/phpunit/tests/blocks/block-context.php b/tests/phpunit/tests/blocks/block-context.php new file mode 100644 index 0000000000..61baf3b611 --- /dev/null +++ b/tests/phpunit/tests/blocks/block-context.php @@ -0,0 +1,215 @@ + '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( + '' . + '' . + '' + ); + + 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( '' ); + + 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( '' ); + + 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] ); + } + +}