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

Introduce strip_dynamic_blocks() for excerpts #8984

Merged
merged 4 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 69 additions & 16 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ function get_dynamic_block_names() {
return $dynamic_block_names;
}

/**
* Retrieve the dynamic blocks regular expression for searching.
*
* @return string
*/
function get_dynamic_blocks_regex() {
$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+(\/)?-->/'
);

return $dynamic_block_pattern;
}

/**
* Renders a single block into a HTML string.
*
Expand Down Expand Up @@ -124,22 +148,8 @@ function gutenberg_render_block( $block ) {
* @return string Updated post content.
*/
function do_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+(\/)?-->/'
);
$rendered_content = '';
$dynamic_block_pattern = get_dynamic_blocks_regex();

while ( preg_match( $dynamic_block_pattern, $content, $block_match, PREG_OFFSET_CAPTURE ) ) {
$opening_tag = $block_match[0][0];
Expand Down Expand Up @@ -208,3 +218,46 @@ function do_blocks( $content ) {
return $rendered_content;
}
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode().

/**
* Remove all dynamic blocks from the given content.
*
* @param string $content Content of the current post.
* @return string
*/
function strip_dynamic_blocks( $content ) {
return preg_replace( get_dynamic_blocks_regex(), '', $content );
}

/**
* Adds the content filter to strip dynamic blocks from excerpts.
*
* It's a bit hacky for now, but once this gets merged into core the function
* can just be called in `wp_trim_excerpt()`.
*
* @param string $text Excerpt.
*
* @return string
*/
function strip_dynamic_blocks_add_filter( $text ) {
add_filter( 'the_content', 'strip_dynamic_blocks', 8 ); // Before do_blocks().

return $text;
}
add_filter( 'get_the_excerpt', 'strip_dynamic_blocks_add_filter', 9 ); // Before wp_trim_excerpt().

/**
* Adds the content filter to strip dynamic blocks from excerpts.
*
* It's a bit hacky for now, but once this gets merged into core the function
* can just be called in `wp_trim_excerpt()`.
*
* @param string $text Excerpt.
* @return string
*/
function strip_dynamic_blocks_remove_filter( $text ) {
remove_filter( 'the_content', 'strip_dynamic_blocks', 8 );

return $text;
}
add_filter( 'wp_trim_excerpt', 'strip_dynamic_blocks_add_filter', 0 ); // Before all other.
111 changes: 111 additions & 0 deletions phpunit/class-blocks-api-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
* Blocks API Tests
*
* @package Gutenberg
*/

/**
* Test functions in blocks.php
*/
class Blocks_API extends WP_UnitTestCase {

public static $post_id;

public $content = '
<!-- wp:paragraph -->
<p>paragraph</p>
<!-- /wp:paragraph -->

<!-- wp:latest-posts {"postsToShow":3,"displayPostDate":true,"order":"asc","orderBy":"title"} /-->

<!-- wp:spacer -->
<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->';

public $filtered_content = '
<!-- wp:paragraph -->
<p>paragraph</p>
<!-- /wp:paragraph -->



<!-- wp:spacer -->
<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->';

/**
* Dummy block rendering function.
*
* @return string Block output.
*/
function render_dummy_block() {
return get_the_excerpt( self::$post_id );
}

/**
* Set up.
*/
function setUp() {
parent::setUp();

self::$post_id = $this->factory()->post->create( array(
'post_excerpt' => '', // Empty excerpt, so it has to be generated.
'post_content' => '<!-- wp:core/dummy /-->',
) );

register_block_type( 'core/dummy', array(
'render_callback' => array( $this, 'render_dummy_block' ),
) );
}

/**
* Tear down.
*/
function tearDown() {
parent::tearDown();

$registry = WP_Block_Type_Registry::get_instance();
$registry->unregister( 'core/dummy' );

wp_delete_post( self::$post_id, true );
}

/**
* Tests strip_dynamic_blocks().
*
* @covers ::strip_dynamic_blocks
*/
function test_strip_dynamic_blocks() {
// Simple dynamic block..
$content = '<!-- wp:core/block /-->';
$this->assertEmpty( strip_dynamic_blocks( $content ) );

// Dynamic block with options, embedded in other content.
$this->assertEquals( $this->filtered_content, strip_dynamic_blocks( $this->content ) );
}

/**
* Tests that dynamic blocks don't cause an out-of-memory error.
*
* When dynamic blocks happen to generate an excerpt, they can cause an
* infinite loop if that block is part of the post's content.
*
* `wp_trim_excerpt()` applies the `the_content` filter, which has
* `do_blocks` attached to it, trying to render the block which again will
* attempt to return an excerpt of that post.
*
* This infinite loop can be avoided by stripping dynamic blocks before
* `the_content` gets applied, just like shortcodes.
*
* @covers ::strip_dynamic_blocks_add_filter, ::strip_dynamic_blocks_remove_filter
*/
function test_excerpt_infinite_loop() {
$query = new WP_Query( array(
'post__in' => array( self::$post_id ),
) );
$query->the_post();

$this->assertEmpty( do_blocks( '<!-- wp:core/dummy /-->' ) );
}
}