From 0164e5a30f842f3c67e1db8ba9872ce2b2878328 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 21 Sep 2018 23:03:55 -0400 Subject: [PATCH] WIP: Parser: Add filter to process blocks after parsing There are numerous needs to process posts and block content from its structured form without demanding that plugin authors implement their own parsing systems. Since the new default parser was implemented in #8083 the server-side parse is now fast enough to consider doing full parses of our documents and with that brings the idea that we can filter block content from the parser itself. In this patch I'm exploring an API to allow extending the parser's behavior by post-processing blocks as they enter the parser's output array. This new filter gives the ability to transform all of the block's properties as they finish parsing. In the case of inner blocks the filter runs as the inner blocks have finished their own nesting. In the case of top-level blocks the filter runs after all inner content has finished parsing. One use case is in #8760 where we want to replace the HTML parts of blocks while preserving other structure. Another use case could be removing specific inner blocks or content based on the current user requesting a post. This filter exposes a kind of visitor pattern for the nested parse. > **THIS IS AN INCOMPLETE PATCH DO NOT MERGE** --- .../parser.php | 33 ++++++++++---- .../src/index.js | 28 ++++++++---- .../grammar.pegjs | 43 ++++++++++++++++--- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/packages/block-serialization-default-parser/parser.php b/packages/block-serialization-default-parser/parser.php index 9ea5b6ab789190..89072d6424b3e9 100644 --- a/packages/block-serialization-default-parser/parser.php +++ b/packages/block-serialization-default-parser/parser.php @@ -244,17 +244,19 @@ function proceed() { */ if ( 0 === $stack_depth ) { if ( isset( $leading_html_start ) ) { - $this->output[] = array( - 'attrs' => array(), - 'innerHTML' => substr( - $this->document, - $leading_html_start, - $start_offset - $leading_html_start - ), - ); + $block = array( + 'attrs' => array(), + 'innerHTML' => substr( + $this->document, + $leading_html_start, + $start_offset - $leading_html_start + ), + ); + + $this->output[] = self::filter_block( $block ); } - $this->output[] = new WP_Block_Parser_Block( $block_name, $attrs, array(), '' ); + $this->output[] = self::filter_block( new WP_Block_Parser_Block( $block_name, $attrs, array(), '' ) ); $this->offset = $start_offset + $token_length; return true; } @@ -458,4 +460,17 @@ function add_block_from_stack( $end_offset = null ) { $this->output[] = $stack_top->block; } + + static function filter_block( &$block ) { + if ( ! function_exists( 'apply_filters' ) ) { + return $block; + } + + /** + * Filter to allow plugins to process blocks after parsing + * + * @since 4.0.0 + */ + return apply_filters( 'block_post_parse', $block ); + } } diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index a4cec7aecd8ef0..370944a9b28e1d 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -23,6 +23,18 @@ function Frame( block, tokenStart, tokenLength, prevOffset, leadingHtmlStart ) { }; } +function filterBlock( block ) { + if ( + 'undefined' === typeof wp || + 'undefined' === typeof wp.hooks || + 'function' !== typeof wp.hooks.applyFilters + ) { + return block; + } + + return wp.hooks.applyFilters( 'blocks.postParse', block ); +} + export const parse = ( doc ) => { document = doc; offset = 0; @@ -78,12 +90,12 @@ function proceed() { // in the top-level of the document if ( 0 === stackDepth ) { if ( null !== leadingHtmlStart ) { - output.push( { + output.push( filterBlock( { attrs: {}, innerHTML: document.substr( leadingHtmlStart, startOffset - leadingHtmlStart ), - } ); + } ) ); } - output.push( Block( blockName, attrs, [], '' ) ); + output.push( filterBlock( Block( blockName, attrs, [], '' ) ) ); offset = startOffset + tokenLength; return true; } @@ -227,10 +239,10 @@ function addFreeform( rawLength ) { // specifies an object that's different. we can update the // specification and change here if we want to but for now we // want this parser to be spec-compliant - output.push( { + output.push( filterBlock( { attrs: {}, innerHTML: document.substr( offset, length ), - } ); + } ) ); } function addInnerBlock( block, tokenStart, tokenLength, lastOffset ) { @@ -253,11 +265,11 @@ function addBlockFromStack( endOffset ) { } if ( null !== leadingHtmlStart ) { - output.push( { + output.push( filterBlock( { attrs: {}, innerHTML: document.substr( leadingHtmlStart, tokenStart - leadingHtmlStart ), - } ); + } ) ); } - output.push( block ); + output.push( filterBlock( block ) ); } diff --git a/packages/block-serialization-spec-parser/grammar.pegjs b/packages/block-serialization-spec-parser/grammar.pegjs index aa1dc1b70a29ed..a15a5524193f6c 100644 --- a/packages/block-serialization-spec-parser/grammar.pegjs +++ b/packages/block-serialization-spec-parser/grammar.pegjs @@ -92,6 +92,21 @@ if ( ! function_exists( 'peg_join_blocks' ) ) { } } +if ( ! function_exists( 'peg_filter_block' ) ) { + function peg_filter_block( &$block ) { + if ( ! function_exists( 'apply_filters' ) ) { + return $block; + } + + /** + * Filter to allow plugins to process blocks after parsing + * + * @since 4.0.0 + */ + return apply_filters( 'block_post_parse', $block ); + } +} + ?> **/ function freeform( s ) { @@ -152,6 +167,18 @@ function partition( predicate, list ) { return [ truthy, falsey ]; } +function filterBlock( block ) { + if ( + 'undefined' === typeof wp || + 'undefined' === typeof wp.hooks || + 'function' !== typeof wp.hooks.applyFilters + ) { + return block; + } + + return wp.hooks.applyFilters( 'blocks.postParse', block ); +} + } ////////////////////////////////////////////////////// @@ -179,20 +206,22 @@ Block_Void })? "/-->" { /** $blockName, 'attrs' => $attrs, 'innerBlocks' => array(), 'innerHTML' => '', ); + + return peg_filter_block( $block ); ?> **/ - return { + return filterBlock( { blockName: blockName, attrs: attrs, innerBlocks: [], innerHTML: '' - }; + } ); } Block_Balanced @@ -201,24 +230,26 @@ Block_Balanced /** $s['blockName'], 'attrs' => $s['attrs'], 'innerBlocks' => $innerBlocks, 'innerHTML' => implode( '', $innerHTML ), ); + + return peg_filter_block( $block ); ?> **/ var innerContent = partition( function( a ) { return 'string' === typeof a }, children ); var innerHTML = innerContent[ 0 ]; var innerBlocks = innerContent[ 1 ]; - return { + return filterBlock( { blockName: s.blockName, attrs: s.attrs, innerBlocks: innerBlocks, innerHTML: innerHTML.join( '' ) - }; + } ); } Block_Start