Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parsing in do_blocks() and rendering of blocks on frontend in the_content #1244

Merged
merged 6 commits into from
Jun 20, 2017
Merged
46 changes: 32 additions & 14 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,28 +102,46 @@ function do_blocks( $content ) {
global $wp_registered_blocks;

// Extract the blocks from the post content.
$matcher = '/<!--\s*wp:([a-z](?:[a-z0-9\/]+)*)\s+((?:(?!-->).)*)\s*\/?-->(?:.*?<!--\s*\/wp:\g1\s+-->)?/s';
$matcher = '#' . join( '', array(
'(?P<opener><!--\s*',
'wp:(?P<block_name>[a-z](?:[a-z0-9/]+)*)\s+',
'(?P<attributes>(?:(?!-->).)*)',
'\s*/?-->\n?)',
'(?:',
'(?P<content>.*?)',
'(?P<closer><!--\s*/wp:\g{block_name}\s+-->\n?)',
')?',
) ) . '#s';
preg_match_all( $matcher, $content, $matches, PREG_OFFSET_CAPTURE );

$new_content = $content;
$offset_differential = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if it would be simpler to implement as while ( preg_match( $matcher, $content, $match ) ) {

Maybe a performance hit by repeatedly matching the same string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aduth good thought, but the problem is that preg_match doesn't accept an offset, so it would have to keep running the regex and search from the beginning of the string. So yeah, it would be less efficient.

Regardless, all of this regex logic will be eliminated once a PEG is implemented in PHP (#1152).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preg_match does accept an offset:

http://php.net/manual/en/function.preg-match.php

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, TIL. In any case, re-using the one set of results from preg_match_all seems good.

foreach ( $matches[0] as $index => $block_match ) {
$block_name = $matches[1][ $index ][0];
// do nothing if the block is not registered.
if ( ! isset( $wp_registered_blocks[ $block_name ] ) ) {
continue;
}
$block_name = $matches['block_name'][ $index ][0];

$output = '';
if ( isset( $wp_registered_blocks[ $block_name ] ) ) {
$block_attributes_string = $matches['attributes'][ $index ][0];
$block_attributes = parse_block_attributes( $block_attributes_string );

$block_markup = $block_match[0];
$block_attributes_string = $matches[2][ $index ][0];
$block_attributes = parse_block_attributes( $block_attributes_string );
// Call the block's render function to generate the dynamic output.
$output = call_user_func( $wp_registered_blocks[ $block_name ]['render'], $block_attributes );
} elseif ( isset( $matches['content'][ $index ][0] ) ) {
$output = $matches['content'][ $index ][0];
}

// Call the block's render function to generate the dynamic output.
$output = call_user_func( $wp_registered_blocks[ $block_name ]['render'], $block_attributes );
// Replace the matched block with the static or dynamic output.
$new_content = substr_replace(
$new_content,
$output,
$block_match[1] - $offset_differential,
strlen( $block_match[0] )
);

// Replace the matched block with the dynamic output.
$new_content = str_replace( $block_markup, $output, $new_content );
// Update offset for the next replacement.
$offset_differential += strlen( $block_match[0] ) - strlen( $output );
}

return $new_content;
}
add_filter( 'the_content', 'do_blocks', 10 ); // BEFORE do_shortcode().
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode() and wpautop().
43 changes: 36 additions & 7 deletions phpunit/class-dynamic-blocks-render-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
* Test do_blocks
*/
class Dynamic_Blocks_Render_Test extends WP_UnitTestCase {

/**
* Dummy block instance number.
*
* @var int
*/
protected $dummy_block_instance_number = 0;

/**
* Dummy block rendering function.
*
Expand All @@ -17,13 +25,23 @@ class Dynamic_Blocks_Render_Test extends WP_UnitTestCase {
* @return string Block output.
*/
function render_dummy_block( $attributes ) {
return $attributes['value'];
$this->dummy_block_instance_number += 1;
return $this->dummy_block_instance_number . ':' . $attributes['value'];
}

/**
* Tear down.
*/
function tearDown() {
$this->dummy_block_instance_number = 0;
$GLOBALS['wp_registered_blocks'] = array();
}

/**
* Test dynamic blocks that lack content, including void blocks.
*
* @covers do_blocks
*/
function test_dynamic_block_rendering() {
$settings = array(
'render' => array(
Expand All @@ -32,23 +50,34 @@ function test_dynamic_block_rendering() {
),
);
register_block_type( 'core/dummy', $settings );

// The duplicated dynamic blocks below are there to ensure that do_blocks() replaces each one-by-one.
$post_content =
'before' .
'<!-- wp:core/dummy value="b1" --><!-- /wp:core/dummy -->' .
'<!-- wp:core/dummy value="b1" --><!-- /wp:core/dummy -->' .
'between' .
'<!-- wp:core/dummy value="b2" --><!-- /wp:core/dummy -->' .
'<!-- wp:core/dummy value="b2" /-->' .
'<!-- wp:core/dummy value="b2" /-->' .
'after';

$updated_post_content = do_blocks( $post_content );
$this->assertEquals( $updated_post_content,
'before' .
'b1' .
'1:b1' .
'2:b1' .
'between' .
'b2' .
'3:b2' .
'4:b2' .
'after'
);
}

/**
* Test dynamic blocks that contain content.
*
* @covers do_blocks
*/
function test_dynamic_block_rendering_with_content() {
$settings = array(
'render' => array(
Expand All @@ -59,17 +88,17 @@ function test_dynamic_block_rendering_with_content() {
register_block_type( 'core/dummy', $settings );
$post_content =
'before' .
'<!-- wp:core/dummy value="b1" -->this should be ignored<!-- /wp:core/dummy -->' .
"<!-- wp:core/dummy value=\"b1\" -->this\nshould\n\nbe\nignored<!-- /wp:core/dummy -->" .
'between' .
'<!-- wp:core/dummy value="b2" -->this should also be ignored<!-- /wp:core/dummy -->' .
'after';

$updated_post_content = do_blocks( $post_content );
$this->assertEquals( $updated_post_content,
'before' .
'b1' .
'1:b1' .
'between' .
'b2' .
'2:b2' .
'after'
);
}
Expand Down