Skip to content

Commit

Permalink
Server directive processing: Stop processing non-interactive blocks (W…
Browse files Browse the repository at this point in the history
…ordPress#56302)

* Initial commit

* It works, but is stripping comments

* Added an extra return for debugging

* Refactor to use string instead of arrays to compare

* Use hidden textarea to save comments in production html

* Use divs as delimiters, comments not working for interactive innner components

* Back to array for references and comment delimiters

* Something is working :-)

* Refactor, process working

* experiment replacing inner blocks

* now working if there is only 1 interactive block

* now working with 2 interactive blocks

* Commented stopping at first custom element, after innerblocks is not being processed otherwise

* Try using bookmarks, fix autoclosing tag not working

* Not using bookmarks anymore, just processing inner inside processing function

* Seems to be working, still needs a good test battery

* Fix interactivity API directive processing

* Remove tests that will be updated in next commits

* Add markup tests

* Use correct div group structure

* Small refactor

* remove not needed block name in interactive markers

* Add more markup tests

* refactor tests

* Make inner blocks optional

* Move directives declaration to declare them only if needed

* Refactor more code, thanks to @DAreRodz

* Remove only the first ocurrence

* Improve comments and format

* Update tests to use HTML API, fix non interactive blocks not being parsed

* Unmark children of interactive blocks according to @DAreRodz comment

* Add a p tag check test

* Execute directives by priority

* Remove gutenberg name from tests and use camelCase for context property

* Fix empty style attribute error

* Add test for directive ordering

* Fix evaluate should only execute anonymous functions test

* Fix wp-style tests

* Improve tests

* Test that we don't process non-interactive blocks

* Stop unmarking children of interactive blocks

* Move directives inside gutenberg_process_interactive_html

---------

Co-authored-by: Luis Herranz <[email protected]>
Co-authored-by: David Arenas <[email protected]>
  • Loading branch information
3 people authored Dec 28, 2023
1 parent ec2c51d commit 1f9b753
Show file tree
Hide file tree
Showing 5 changed files with 506 additions and 217 deletions.
138 changes: 41 additions & 97 deletions lib/experimental/interactivity-api/class-wp-directive-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,79 @@
/**
* This processor is built on top of the HTML Tag Processor and augments its
* capabilities to process the Interactivity API directives.
*
* IMPORTANT DISCLAIMER: This code is highly experimental and its only purpose
* is to provide a way to test the server-side rendering of the Interactivity
* API. Most of this code will be discarded once the HTML Processor is
* available. Please restrain from investing unnecessary time and effort trying
* to improve this code.
*/
class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_5 {
/**
* String containing the current root block.
*
* @var string
*/
public static $root_block = null;

/**
* An array of root blocks.
* Array containing the direct children of interactive blocks.
*
* @var array
*/
public static $root_block = null;
public static $children_of_interactive_block = array();

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

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

/**
* Check if block is a root block.
* Checks if block is a root block.
*
* @param array $block The block to check.
*
* @return bool True if block is a root block, false otherwise.
*/
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.
* Checks if a root block has already been defined.
*
* @return bool True if block is a root block, false otherwise.
* @return bool True if there is a root block, false otherwise.
*/
public static function has_root_block() {
return isset( self::$root_block );
}

/**
* Stores a reference to a direct children of an interactive block to be able
* to identify it later.
*
* @param array $block The block to add.
*/
public static function mark_children_of_interactive_block( $block ) {
self::$children_of_interactive_block[] = md5( serialize( $block ) );
}

/**
* Find the matching closing tag for an opening tag.
* Checks if block is marked as children of an interactive block.
*
* @param array $block The block to check.
* @return bool True if block is a children of an interactive block, false otherwise.
*/
public static function is_marked_as_children_of_interactive_block( $block ) {
return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true );
}

/**
* Finds the matching closing tag for an opening tag.
*
* When called while on an open tag, traverse the HTML until we find the
* matching closing tag, respecting any in-between content, including nested
Expand Down Expand Up @@ -111,76 +125,7 @@ public function next_balanced_closer() {
}

/**
* 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.
* Returns the content between two balanced tags.
*
* When called on an opening tag, return the HTML content found between that
* opening tag and its matching closing tag.
Expand All @@ -206,14 +151,13 @@ public function get_inner_html() {
}

/**
* Set the content between two balanced tags.
* Sets the content between two balanced tags.
*
* When called on an opening tag, set the HTML content found between that
* opening tag and its matching closing tag.
*
* @param string $new_html The string to replace the content between the
* matching tags with.
*
* @return bool Whether the content was successfully replaced.
*/
public function set_inner_html( $new_html ) {
Expand All @@ -237,7 +181,7 @@ public function set_inner_html( $new_html ) {
}

/**
* Return a pair of bookmarks for the current opening tag and the matching
* Returns a pair of bookmarks for the current opening tag and the matching
* closing tag.
*
* @return array|false A pair of bookmarks, or false if there's no matching
Expand Down Expand Up @@ -267,12 +211,12 @@ public function get_balanced_tag_bookmarks() {
}

/**
* Whether a given HTML element is void (e.g. <br>).
* Checks whether a given HTML element is void (e.g. <br>).
*
* @see https://html.spec.whatwg.org/#elements-2
*
* @param string $tag_name The element in question.
* @return bool True if the element is void.
*
* @see https://html.spec.whatwg.org/#elements-2
*/
public static function is_html_void_element( $tag_name ) {
switch ( $tag_name ) {
Expand All @@ -297,7 +241,7 @@ public static function is_html_void_element( $tag_name ) {
}

/**
* Extract and return the directive type and the the part after the double
* Extracts and return the directive type and the the part after the double
* hyphen from an attribute name (if present), in an array format.
*
* Examples:
Expand All @@ -307,7 +251,7 @@ public static function is_html_void_element( $tag_name ) {
* 'wp-thing--and--thang' => array( 'wp-thing', 'and--thang' )
*
* @param string $name The attribute name.
* @return array The resulting array
* @return array The resulting array.
*/
public static function parse_attribute_name( $name ) {
return explode( '--', $name, 2 );
Expand Down
Loading

0 comments on commit 1f9b753

Please sign in to comment.