Skip to content

Commit

Permalink
Server directive processing: Improve how block references are saved (#…
Browse files Browse the repository at this point in the history
…56107)

* Refactor first commit, still wip

* Fix wrong comparison

* Move static function to normal function

* Add conditional (if a root block has been added)

* Update tests to be more root blocks specific

* Function renaming and fix tests

* Fix typo

* Add new line between tests

---------

Co-authored-by: Luis Herranz <luisherranz@gmail.com>
  • Loading branch information
cbravobernal and luisherranz committed Nov 15, 2023
1 parent 1bf654b commit cbe7680
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 171 deletions.
Expand Up @@ -27,17 +27,26 @@ class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_4 {
*
* @var array
*/
public static $root_blocks = array();
public static $root_block = null;

/**
* Add a root block to the list.
* Add a root block to the variable.
*
* @param array $block The block to add.
*
* @return void
*/
public static function add_root_block( $block ) {
self::$root_blocks[] = md5( serialize( $block ) );
public static function mark_root_block( $block ) {
self::$root_block = md5( serialize( $block ) );
}

/**
* Remove a root block to the variable.
*
* @return void
*/
public static function unmark_root_block() {
self::$root_block = null;
}

/**
Expand All @@ -47,8 +56,17 @@ public static function add_root_block( $block ) {
*
* @return bool True if block is a root block, false otherwise.
*/
public static function is_root_block( $block ) {
return in_array( md5( serialize( $block ) ), self::$root_blocks, true );
public static function is_marked_as_root_block( $block ) {
return md5( serialize( $block ) ) === self::$root_block;
}

/**
* Check if a root block has already been defined.
*
* @return bool True if block is a root block, false otherwise.
*/
public static function has_root_block() {
return isset( self::$root_block );
}


Expand Down Expand Up @@ -92,6 +110,75 @@ public function next_balanced_closer() {
return false;
}

/**
* Traverses the HTML searching for Interactivity API directives and processing
* them.
*
* @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor.
* @param string $prefix Attribute prefix.
* @param string[] $directives Directives.
*
* @return WP_Directive_Processor The modified instance of the
* WP_Directive_Processor.
*/
public function process_rendered_html( $tags, $prefix, $directives ) {
$context = new WP_Directive_Context();
$tag_stack = array();

while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $tags->get_tag();

// Is this a tag that closes the latest opening tag?
if ( $tags->is_tag_closer() ) {
if ( 0 === count( $tag_stack ) ) {
continue;
}

list( $latest_opening_tag_name, $attributes ) = end( $tag_stack );
if ( $latest_opening_tag_name === $tag_name ) {
array_pop( $tag_stack );

// If the matching opening tag didn't have any directives, we move on.
if ( 0 === count( $attributes ) ) {
continue;
}
}
} else {
$attributes = array();
foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) {
/*
* Removes the part after the double hyphen before looking for
* the directive processor inside `$directives`, e.g., "wp-bind"
* from "wp-bind--src" and "wp-context" from "wp-context" etc...
*/
list( $type ) = WP_Directive_Processor::parse_attribute_name( $name );
if ( array_key_exists( $type, $directives ) ) {
$attributes[] = $type;
}
}

/*
* If this is an open tag, and if it either has directives, or if
* we're inside a tag that does, take note of this tag and its
* directives so we can call its directive processor once we
* encounter the matching closing tag.
*/
if (
! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) &&
( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) )
) {
$tag_stack[] = array( $tag_name, $attributes );
}
}

foreach ( $attributes as $attribute ) {
call_user_func( $directives[ $attribute ], $tags, $context );
}
}

return $tags;
}

/**
* Return the content between two balanced tags.
*
Expand Down
133 changes: 28 additions & 105 deletions lib/experimental/interactivity-api/directive-processing.php
Expand Up @@ -8,131 +8,54 @@
*/

/**
* Process the Interactivity API directives using the root blocks of the
* outermost rendering, ignoring the root blocks of inner blocks like Patterns,
* Template Parts or Content.
* Mark if the block is a root block. Checks that there is already a root block
* in order not to mark template-parts or synced patterns as root blocks, where
* the parent is null.
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
* @param array $parent_block The parent block.
*
* @return array The parsed block.
*/
function gutenberg_interactivity_process_directives( $parsed_block, $source_block, $parent_block ) {
static $is_inside_root_block = false;
static $process_directives_in_root_blocks = null;

if ( ! isset( $process_directives_in_root_blocks ) ) {
/**
* Process directives in each root block.
*
* @param string $block_content The block content.
* @param array $block The full block.
*
* @return string Filtered block content.
*/
$process_directives_in_root_blocks = static function ( $block_content, $block ) use ( &$is_inside_root_block ) {

if ( WP_Directive_Processor::is_root_block( $block ) ) {

$directives = array(
'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
);

$tags = new WP_Directive_Processor( $block_content );
$tags = gutenberg_interactivity_process_rendered_html( $tags, 'data-wp-', $directives );
$is_inside_root_block = false;
return $tags->get_updated_html();

}

return $block_content;
};
add_filter( 'render_block', $process_directives_in_root_blocks, 10, 2 );
}

if ( ! isset( $parent_block ) && ! $is_inside_root_block ) {
WP_Directive_Processor::add_root_block( $parsed_block );
$is_inside_root_block = true;
function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, $parent_block ) {
if ( ! isset( $parent_block ) && ! WP_Directive_Processor::has_root_block() ) {
WP_Directive_Processor::mark_root_block( $parsed_block );
}

return $parsed_block;
}
add_filter( 'render_block_data', 'gutenberg_interactivity_process_directives', 10, 3 );

add_filter( 'render_block_data', 'gutenberg_interactivity_mark_root_blocks', 10, 3 );

/**
* Traverses the HTML searching for Interactivity API directives and processing
* them.
* Process directives in each root block.
*
* @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor.
* @param string $prefix Attribute prefix.
* @param string[] $directives Directives.
* @param string $block_content The block content.
* @param array $block The full block.
*
* @return WP_Directive_Processor The modified instance of the
* WP_Directive_Processor.
* @return string Filtered block content.
*/
function gutenberg_interactivity_process_rendered_html( $tags, $prefix, $directives ) {
$context = new WP_Directive_Context();
$tag_stack = array();
function gutenberg_process_directives_in_root_blocks( $block_content, $block ) {
if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) {
WP_Directive_Processor::unmark_root_block();
$directives = array(
'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind',
'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
'data-wp-class' => 'gutenberg_interactivity_process_wp_class',
'data-wp-style' => 'gutenberg_interactivity_process_wp_style',
'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
);

$tags = new WP_Directive_Processor( $block_content );
$tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives );
return $tags->get_updated_html();

while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $tags->get_tag();

// Is this a tag that closes the latest opening tag?
if ( $tags->is_tag_closer() ) {
if ( 0 === count( $tag_stack ) ) {
continue;
}

list( $latest_opening_tag_name, $attributes ) = end( $tag_stack );
if ( $latest_opening_tag_name === $tag_name ) {
array_pop( $tag_stack );

// If the matching opening tag didn't have any directives, we move on.
if ( 0 === count( $attributes ) ) {
continue;
}
}
} else {
$attributes = array();
foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) {
/*
* Removes the part after the double hyphen before looking for
* the directive processor inside `$directives`, e.g., "wp-bind"
* from "wp-bind--src" and "wp-context" from "wp-context" etc...
*/
list( $type ) = WP_Directive_Processor::parse_attribute_name( $name );
if ( array_key_exists( $type, $directives ) ) {
$attributes[] = $type;
}
}

/*
* If this is an open tag, and if it either has directives, or if
* we're inside a tag that does, take note of this tag and its
* directives so we can call its directive processor once we
* encounter the matching closing tag.
*/
if (
! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) &&
( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) )
) {
$tag_stack[] = array( $tag_name, $attributes );
}
}

foreach ( $attributes as $attribute ) {
call_user_func( $directives[ $attribute ], $tags, $context );
}
}

return $tags;
return $block_content;
}
add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 );


/**
* Resolve the reference using the store and the context from the provided path.
Expand Down
2 changes: 1 addition & 1 deletion packages/e2e-tests/plugins/interactive-blocks.php
Expand Up @@ -40,7 +40,7 @@ function () {
if ( 'true' === $_GET['disable_directives_ssr'] ) {
remove_filter(
'render_block_data',
'gutenberg_interactivity_process_directives'
'gutenberg_interactivity_mark_root_blocks'
);
}
}
Expand Down

0 comments on commit cbe7680

Please sign in to comment.