Skip to content

Commit

Permalink
Block API: Block Context: Filter content, prepare attributes at rende…
Browse files Browse the repository at this point in the history
…r, pass block to render (#21925)

* Block API: WP_Block: Run filter on rendered content

* Block API: WP_Block: Prepare attributes for render at render

* Block API: WP_Block: Pass block as third argument of render_callback

* Block API: WP_Block: Use magic method for lazy attributes initialization

* Block API: Implement iterable WP_Block_List

* Block API: Unskip block context test

* Block Library: Post Title: Document function attributes

* Block API: WP_Block_List: Format constructor to prepare defaults

* Block API: WP_Block_List: Replace reference of initialized block

* Block API: WP_Block_List: Implement Countable

* Block API: WP_Block_List: Add unit tests for array access, iterable

* Block API: WP_Block: Add test case for render_block filtering

Include inner blocks as a noted previous regression

* Docs: Restore documentation for block context PHP API

* Block API: WP_Block: Remove unnecessary isset

* Framework: Fix formatting for multi-line comment

* Framework: Include postType context server-side

* Framework: Improve postType comment grammar

Co-authored-by: Enrique Piqueras <[email protected]>

* Framework: PHPCS formatting

Co-authored-by: Enrique Piqueras <[email protected]>
  • Loading branch information
aduth and epiqueras authored May 5, 2020
1 parent 433236b commit 43372c9
Show file tree
Hide file tree
Showing 10 changed files with 472 additions and 64 deletions.
12 changes: 11 additions & 1 deletion docs/designers-developers/developers/block-api/block-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,14 @@ registerBlockType( 'my-plugin/record-title', {

### PHP

_The PHP implementation of block context is currently experimental and subject to breaking changes. It will be documented in the future once the API has stabilized._
A block's context values are available from the `context` property of the `$block` argument passed as the third argument to the `render_callback` function.

`record-title/index.php`

```php
register_block_type( 'my-plugin/record-title', array(
'render_callback' => function( $attributes, $content, $block ) {
return 'The current record ID is: ' . $block->context['my-plugin/recordId'];
},
) );
```
189 changes: 189 additions & 0 deletions lib/class-wp-block-list.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<?php
/**
* Blocks API: WP_Block_List class
*
* @package Gutenberg
*/

/**
* Class representing a list of block instances.
*/
class WP_Block_List implements Iterator, ArrayAccess, Countable {

/**
* Original array of parsed block data.
*
* @var array|WP_Block[]
* @access protected
*/
protected $blocks;

/**
* All available context of the current hierarchy.
*
* @var array
* @access protected
*/
protected $available_context;

/**
* Block type registry to use in constructing block instances.
*
* @var WP_Block_Type_Registry
* @access protected
*/
protected $registry;

/**
* Constructor.
*
* Populates object properties from the provided block instance argument.
*
* @param array|WP_Block[] $blocks Array of parsed block data, or block instances.
* @param array $available_context Optional array of ancestry context values.
* @param WP_Block_Type_Registry $registry Optional block type registry.
*/
public function __construct( $blocks, $available_context = array(), $registry = null ) {
if ( is_null( $registry ) ) {
$registry = WP_Block_Type_Registry::get_instance();
}

$this->blocks = $blocks;
$this->available_context = $available_context;
$this->registry = $registry;
}

/*
* ArrayAccess interface methods.
*/

/**
* Returns true if a block exists by the specified block index, or false
* otherwise.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetexists.php
*
* @param string $index Index of block to check.
*
* @return bool Whether block exists.
*/
public function offsetExists( $index ) {
return isset( $this->blocks[ $index ] );
}

/**
* Returns the value by the specified block index.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetget.php
*
* @param string $index Index of block value to retrieve.
*
* @return mixed|null Block value if exists, or null.
*/
public function offsetGet( $index ) {
$block = $this->blocks[ $index ];

if ( isset( $block ) && is_array( $block ) ) {
$block = new WP_Block( $block, $this->available_context, $this->registry );
$this->blocks[ $index ] = $block;
}

return $block;
}

/**
* Assign a block value by the specified block index.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetset.php
*
* @param string $index Index of block value to set.
* @param mixed $value Block value.
*/
public function offsetSet( $index, $value ) {
if ( is_null( $index ) ) {
$this->blocks[] = $value;
} else {
$this->blocks[ $index ] = $value;
}
}

/**
* Unset a block.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetunset.php
*
* @param string $index Index of block value to unset.
*/
public function offsetUnset( $index ) {
unset( $this->blocks[ $index ] );
}

/*
* Iterator interface methods.
*/

/**
* Rewinds back to the first element of the Iterator.
*
* @link https://www.php.net/manual/en/iterator.rewind.php
*/
public function rewind() {
reset( $this->blocks );
}

/**
* Returns the current element of the block list.
*
* @link https://www.php.net/manual/en/iterator.current.php
*
* @return mixed Current element.
*/
public function current() {
return $this->offsetGet( $this->key() );
}

/**
* Returns the key of the current element of the block list.
*
* @link https://www.php.net/manual/en/iterator.key.php
*
* @return mixed Key of the current element.
*/
public function key() {
return key( $this->blocks );
}

/**
* Moves the current position of the block list to the next element.
*
* @link https://www.php.net/manual/en/iterator.next.php
*/
public function next() {
next( $this->blocks );
}

/**
* Checks if current position is valid.
*
* @link https://www.php.net/manual/en/iterator.valid.php
*/
public function valid() {
return null !== key( $this->blocks );
}

/*
* Countable interface methods.
*/

/**
* Returns the count of blocks in the list.
*
* @link https://www.php.net/manual/en/countable.count.php
*
* @return int Block count.
*/
public function count() {
return count( $this->blocks );
}

}
74 changes: 44 additions & 30 deletions lib/class-wp-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
*/
class WP_Block {

/**
* Original parsed array representation of block.
*
* @var array
*/
public $parsed_block;

/**
* Name of block.
*
Expand Down Expand Up @@ -41,13 +48,6 @@ class WP_Block {
*/
protected $available_context;

/**
* Block attribute values.
*
* @var array
*/
public $attributes = array();

/**
* List of inner blocks (of this same class)
*
Expand Down Expand Up @@ -94,22 +94,15 @@ class WP_Block {
* @param WP_Block_Type_Registry $registry Optional block type registry.
*/
public function __construct( $block, $available_context = array(), $registry = null ) {
$this->name = $block['blockName'];
$this->parsed_block = $block;
$this->name = $block['blockName'];

if ( is_null( $registry ) ) {
$registry = WP_Block_Type_Registry::get_instance();
}

$this->block_type = $registry->get_registered( $this->name );

if ( ! empty( $block['attrs'] ) ) {
$this->attributes = $block['attrs'];
}

if ( ! is_null( $this->block_type ) ) {
$this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes );
}

$this->available_context = $available_context;

if ( ! empty( $this->block_type->context ) ) {
Expand All @@ -133,12 +126,7 @@ public function __construct( $block, $available_context = array(), $registry = n
}
/* phpcs:enable */

$this->inner_blocks = array_map(
function( $inner_block ) use ( $child_context, $registry ) {
return new WP_Block( $inner_block, $child_context, $registry );
},
$block['innerBlocks']
);
$this->inner_blocks = new WP_Block_List( $block['innerBlocks'], $child_context, $registry );
}

if ( ! empty( $block['innerHTML'] ) ) {
Expand All @@ -150,13 +138,41 @@ function( $inner_block ) use ( $child_context, $registry ) {
}
}

/**
* Returns a value from an inaccessible property.
*
* This is used to lazily initialize the `attributes` property of a block,
* such that it is only prepared with default attributes at the time that
* the property is accessed. For all other inaccessible properties, a `null`
* value is returned.
*
* @param string $name Property name.
*
* @return array|null Prepared attributes, or null.
*/
public function __get( $name ) {
if ( 'attributes' === $name ) {
$this->attributes = isset( $this->parsed_block['attrs'] ) ?
$this->parsed_block['attrs'] :
array();

if ( ! is_null( $this->block_type ) ) {
$this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes );
}

return $this->attributes;
}

return null;
}

/**
* Generates the render output for the block.
*
* @return string Rendered block output.
*/
public function render() {
global $post, $_experimental_block;
global $post;

$is_dynamic = $this->name && null !== $this->block_type && $this->block_type->is_dynamic();
$block_content = '';
Expand All @@ -169,15 +185,13 @@ public function render() {
}

if ( $is_dynamic ) {
$global_post = $post;
$global_block = $_experimental_block;
$_experimental_block = $this;
$block_content = (string) call_user_func( $this->block_type->render_callback, $this->attributes, $block_content );
$_experimental_block = $global_block;
$post = $global_post;
$global_post = $post;
$block_content = (string) call_user_func( $this->block_type->render_callback, $this->attributes, $block_content, $this );
$post = $global_post;
}

return $block_content;
/** This filter is documented in src/wp-includes/blocks.php */
return apply_filters( 'render_block', $block_content, $this->parsed_block );
}

}
45 changes: 45 additions & 0 deletions lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,48 @@ function gutenberg_get_post_from_context() {
}
return get_post();
}

/**
* Shim that hooks into `pre_render_block` so as to override `render_block` with
* a function that assigns block context.
*
* This can be removed when plugin support requires WordPress 5.5.0+.
*
* @see https://core.trac.wordpress.org/ticket/49927
*
* @param string|null $pre_render The pre-rendered content. Defaults to null.
* @param array $parsed_block The parsed block being rendered.
*
* @return string String of rendered HTML.
*/
function gutenberg_render_block_with_assigned_block_context( $pre_render, $parsed_block ) {
global $post;

/*
* If a non-null value is provided, a filter has run at an earlier priority
* and has already handled custom rendering and should take precedence.
*/
if ( null !== $pre_render ) {
return $pre_render;
}

$source_block = $parsed_block;

/** This filter is documented in src/wp-includes/blocks.php */
$parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
$context = array(
'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.
*/
'postType' => $post->post_type,
);
$block = new WP_Block( $parsed_block, $context );

return $block->render();
}
add_filter( 'pre_render_block', 'gutenberg_render_block_with_assigned_block_context', 9, 2 );
4 changes: 4 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ function gutenberg_is_experiment_enabled( $name ) {
require dirname( __FILE__ ) . '/class-wp-block.php';
}

if ( ! class_exists( 'WP_Block_List' ) ) {
require dirname( __FILE__ ) . '/class-wp-block-list.php';
}

require dirname( __FILE__ ) . '/compat.php';

require dirname( __FILE__ ) . '/blocks.php';
Expand Down
Loading

0 comments on commit 43372c9

Please sign in to comment.