Skip to content

Commit

Permalink
Framework: Skip server grammar parse, seek dynamic blocks explicitly (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth authored Jan 30, 2018
1 parent c6e8798 commit 49f05e5
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 81 deletions.
101 changes: 94 additions & 7 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ function gutenberg_parse_blocks( $content ) {
return $parser->parse( _gutenberg_utf8_split( $content ) );
}

/**
* Returns an array of the names of all registered dynamic block types.
*
* @return array Array of dynamic block names.
*/
function get_dynamic_block_names() {
$dynamic_block_names = array();

$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
foreach ( $block_types as $block_type ) {
if ( $block_type->is_dynamic() ) {
$dynamic_block_names[] = $block_type->name;
}
}

return $dynamic_block_names;
}

/**
* Renders a single block into a HTML string.
*
Expand All @@ -85,8 +103,8 @@ function gutenberg_render_block( $block ) {

if ( $block_name ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
if ( null !== $block_type ) {
return $block_type->render( $attributes, $raw_content );
if ( null !== $block_type && $block_type->is_dynamic() ) {
return $block_type->render( $attributes );
}
}

Expand All @@ -106,12 +124,81 @@ function gutenberg_render_block( $block ) {
* @return string Updated post content.
*/
function do_blocks( $content ) {
$blocks = gutenberg_parse_blocks( $content );
$rendered_content = '';

$dynamic_block_names = get_dynamic_block_names();
$dynamic_block_pattern = (
'/<!--\s+wp:(' .
str_replace( '/', '\/', // Escape namespace, not handled by preg_quote.
str_replace( 'core/', '(?:core/)?', // Allow implicit core namespace, but don't capture.
implode( '|', // Join block names into capture group alternation.
array_map( 'preg_quote', // Escape block name for regular expression.
$dynamic_block_names
)
)
)
) .
')(\s+(\{.*?\}))?\s+(\/)?-->/'
);

while ( preg_match( $dynamic_block_pattern, $content, $block_match, PREG_OFFSET_CAPTURE ) ) {
$opening_tag = $block_match[0][0];
$offset = $block_match[0][1];
$block_name = $block_match[1][0];
$is_self_closing = isset( $block_match[4] );

// Reset attributes JSON to prevent scope bleed from last iteration.
$block_attributes_json = null;
if ( isset( $block_match[3] ) ) {
$block_attributes_json = $block_match[3][0];
}

$content_after_blocks = '';
foreach ( $blocks as $block ) {
$content_after_blocks .= gutenberg_render_block( $block );
// Since content is a working copy since the last match, append to
// rendered content up to the matched offset...
$rendered_content .= substr( $content, 0, $offset );

// ...then update the working copy of content.
$content = substr( $content, $offset + strlen( $opening_tag ) );

// Make implicit core namespace explicit.
$is_implicit_core_namespace = ( false === strpos( $block_name, '/' ) );
$normalized_block_name = $is_implicit_core_namespace ? 'core/' . $block_name : $block_name;

// Find registered block type. We can assume it exists since we use the
// `get_dynamic_block_names` function as a source for pattern matching.
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $normalized_block_name );

// Attempt to parse attributes JSON, if available.
$attributes = array();
if ( ! empty( $block_attributes_json ) ) {
$decoded_attributes = json_decode( $block_attributes_json, true );
if ( ! is_null( $decoded_attributes ) ) {
$attributes = $decoded_attributes;
}
}

// Replace dynamic block with server-rendered output.
$rendered_content .= $block_type->render( $attributes );

if ( ! $is_self_closing ) {
$end_tag_pattern = '/<!--\s+\/wp:' . str_replace( '/', '\/', preg_quote( $block_name ) ) . '\s+-->/';
if ( ! preg_match( $end_tag_pattern, $content, $block_match_end, PREG_OFFSET_CAPTURE ) ) {
// If no closing tag is found, abort all matching, and continue
// to append remainder of content to rendered output.
break;
}

// Update content to omit text up to and including closing tag.
$end_tag = $block_match_end[0][0];
$end_offset = $block_match_end[0][1];

$content = substr( $content, $end_offset + strlen( $end_tag ) );
}
}
return $content_after_blocks;

// Append remaining unmatched content.
$rendered_content .= $content;

return $rendered_content;
}
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode().
27 changes: 16 additions & 11 deletions lib/class-wp-block-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,32 @@ public function __construct( $block_type, $args = array() ) {
}

/**
* Renders the block type output for given attributes and content.
* Renders the block type output for given attributes.
*
* @since 0.6.0
* @access public
*
* @param array $attributes Optional. Block attributes. Default empty array.
* @param string|null $content Optional. Raw block content, or null if none set. Default null.
* @param array $attributes Optional. Block attributes. Default empty array.
* @return string Rendered block type output.
*/
public function render( $attributes = array(), $content = null ) {
if ( ! is_callable( $this->render_callback ) ) {
if ( ! $content ) {
return '';
}

return $content;
public function render( $attributes = array() ) {
if ( ! $this->is_dynamic() ) {
return '';
}

$attributes = $this->prepare_attributes_for_render( $attributes );

return call_user_func( $this->render_callback, $attributes, $content );
return call_user_func( $this->render_callback, $attributes );
}

/**
* Returns true if the block type is dynamic, or false otherwise. A dynamic
* block is one which defers its rendering to occur on-demand at runtime.
*
* @returns boolean Whether block type is dynamic.
*/
public function is_dynamic() {
return is_callable( $this->render_callback );
}

/**
Expand Down
36 changes: 15 additions & 21 deletions phpunit/class-block-type-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,25 @@ function test_render() {
$this->assertEquals( $attributes, json_decode( $output, true ) );
}

function test_render_with_content() {
$attributes = array(
'foo' => 'bar',
'bar' => 'foo',
);
$content = '<p>Test content.</p>';
function test_render_for_static_block() {
$block_type = new WP_Block_Type( 'core/dummy', array() );
$output = $block_type->render();

$block_type = new WP_Block_Type( 'core/dummy', array(
'render_callback' => array( $this, 'render_dummy_block_with_content' ),
) );
$output = $block_type->render( $attributes, $content );
$attributes['_content'] = $content;
$this->assertSame( $attributes, json_decode( $output, true ) );
$this->assertEquals( '', $output );
}

function test_render_without_callback() {
$attributes = array(
'foo' => 'bar',
'bar' => 'foo',
);
$content = '<p>Test content.</p>';
function test_is_dynamic_for_static_block() {
$block_type = new WP_Block_Type( 'core/dummy', array() );

$this->assertFalse( $block_type->is_dynamic() );
}

function test_is_dynamic_for_dynamic_block() {
$block_type = new WP_Block_Type( 'core/dummy', array(
'render_callback' => array( $this, 'render_dummy_block' ),
) );

$block_type = new WP_Block_Type( 'core/dummy' );
$output = $block_type->render( $attributes, $content );
$this->assertSame( $content, $output );
$this->assertTrue( $block_type->is_dynamic() );
}

function test_prepare_attributes() {
Expand Down
43 changes: 6 additions & 37 deletions phpunit/class-dynamic-blocks-render-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ class Dynamic_Blocks_Render_Test extends WP_UnitTestCase {
* Dummy block rendering function.
*
* @param array $attributes Block attributes.
* @param array $content Content.
*
* @return string Block output.
*/
function render_dummy_block( $attributes, $content ) {
function render_dummy_block( $attributes ) {
$this->dummy_block_instance_number += 1;
return $this->dummy_block_instance_number . ':' . $attributes['value'] . ":$content";
return $this->dummy_block_instance_number . ':' . $attributes['value'];
}

/**
Expand Down Expand Up @@ -69,41 +68,11 @@ function test_dynamic_block_rendering() {
$updated_post_content = do_blocks( $post_content );
$this->assertEquals( $updated_post_content,
'before' .
'1:b1:' .
'2:b1:' .
'1:b1' .
'2:b1' .
'between' .
'3:b2:' .
'4:b2:' .
'after'
);
}

/**
* Test dynamic blocks that contain content.
*
* @covers do_blocks
*/
function test_dynamic_block_rendering_with_content() {
$settings = array(
'render_callback' => array(
$this,
'render_dummy_block',
),
);
register_block_type( 'core/dummy', $settings );
$post_content =
'before' .
"<!-- wp:core/dummy {\"value\":\"b1\"} -->this\ncontent\n\nshould\nbe\npassed<!-- /wp:core/dummy -->" .
'between' .
'<!-- wp:core/dummy {"value":"b2"} -->content2<!-- /wp:core/dummy -->' .
'after';

$updated_post_content = do_blocks( $post_content );
$this->assertEquals( $updated_post_content,
'before' .
"1:b1:this\ncontent\n\nshould\nbe\npassed" .
'between' .
'2:b2:content2' .
'3:b2' .
'4:b2' .
'after'
);
}
Expand Down
22 changes: 19 additions & 3 deletions phpunit/class-registration-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@
*/

/**
* Test register_block_type() and unregister_block_type()
* Test register_block_type(), unregister_block_type(), get_dynamic_block_names()
*/
class Registration_Test extends WP_UnitTestCase {

function render_stub() {}

function tearDown() {
parent::tearDown();

$registry = WP_Block_Type_Registry::get_instance();

if ( $registry->is_registered( 'core/dummy' ) ) {
$registry->unregister( 'core/dummy' );
foreach ( array( 'dummy', 'dynamic' ) as $block_name ) {
$block_name = 'core/' . $block_name;

if ( $registry->is_registered( $block_name ) ) {
$registry->unregister( $block_name );
}
}
}

Expand Down Expand Up @@ -44,4 +50,14 @@ function test_unregister_affects_main_registry() {
$registry = WP_Block_Type_Registry::get_instance();
$this->assertFalse( $registry->is_registered( $name ) );
}

function test_get_dynamic_block_names() {
register_block_type( 'core/dummy', array() );
register_block_type( 'core/dynamic', array( 'render_callback' => array( $this, 'render_stub' ) ) );

$dynamic_block_names = get_dynamic_block_names();

$this->assertContains( 'core/dynamic', $dynamic_block_names );
$this->assertNotContains( 'core/dummy', $dynamic_block_names );
}
}
4 changes: 2 additions & 2 deletions phpunit/class-reusable-blocks-render-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function test_render() {
*/
public function test_ref_empty() {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/block' );
$output = $block_type->render( array(), 'foo' );
$output = $block_type->render( array() );
$this->assertSame( '', $output );
}

Expand All @@ -91,7 +91,7 @@ public function test_ref_empty() {
*/
public function test_ref_wrong_post_type() {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/block' );
$output = $block_type->render( array( 'ref' => self::$post_id ), 'foo' );
$output = $block_type->render( array( 'ref' => self::$post_id ) );
$this->assertSame( '', $output );
}
}

0 comments on commit 49f05e5

Please sign in to comment.