From 18397dc06ca6562df6ff50cf1366439fadcf4edd Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Fri, 17 Nov 2023 17:27:47 +0100 Subject: [PATCH 01/42] Initial commit --- .../class-wp-directive-processor.php | 55 ++++++++++++++++- .../directive-processing.php | 61 +++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index cf55a048bb9fa5..761f77a8fe5edd 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -23,9 +23,9 @@ class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_5 { /** - * An array of root blocks. + * A string containing the main root block. * - * @var array + * @var string */ public static $root_block = null; @@ -70,6 +70,57 @@ public static function has_root_block() { } + + /** + * A string containing the main children of interactive. + * + * @var string + */ + public static $children_of_interactive_block = null; + + + /** + * Add a root block to the variable. + * + * @param array $block The block to add. + * + * @return void + */ + public static function mark_children_of_interactive_block( $block ) { + self::$children_of_interactive_block = md5( serialize( $block ) ); + } + + /** + * Remove a root block to the variable. + * + * @return void + */ + public static function unmark_children_of_interactive_block() { + self::$children_of_interactive_block = null; + } + + /** + * Check 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_children_of_interactive_block( $block ) { + return md5( serialize( $block ) ) === self::$children_of_interactive_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_children_of_interactive_block() { + return isset( self::$children_of_interactive_block ); + } + + + /** * Find the matching closing tag for an opening tag. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 064fc8ea62cbb2..09c68a75ad3555 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -7,6 +7,8 @@ * @subpackage Interactivity API */ +global $children_of_interactive_block; +$children_of_interactive_block = array(); /** * 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 @@ -56,6 +58,65 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { } add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 ); +/** + * Creates a stack of interactive block children. + * + * @param array $parsed_block The parsed block. + * @param array $source_block The source block. + * @param WP_Block $parent_block The parent block. + */ +function gutenberg_mark_interactive_block_children( $parsed_block, $source_block, $parent_block ) { + if ( + isset( $parent_block ) && + isset( $parent_block->block_type->supports['interactivity'] ) && + $parent_block->block_type->supports['interactivity'] + ) { + WP_Directive_Processor::mark_children_of_interactive_block( $source_block ); + } + return $parsed_block; +} +add_filter( 'render_block_data', 'gutenberg_mark_interactive_block_children', 100, 3 ); + + +/** + * Add a marker indicating if the block is interactive or not. + * core/interactivity-wrapper if it is interactive. + * core/non-interactivity-wrapper if it is not interactive. + * + * @param string $block_content The block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. + */ +function gutenberg_mark_block_interactivity( $block_content, $block, $block_instance ) { + if ( + isset( $block_instance->block_type->supports['interactivity'] ) && + $block_instance->block_type->supports['interactivity'] + ) { + WP_Directive_Processor::unmark_children_of_interactive_block(); + // Mark interactive blocks so we can process them later. + return get_comment_delimited_block_content( + 'core/interactivity-wrapper', + array( + 'blockName' => $block['blockName'], + // We can put extra information about the block here. + ), + $block_content + ); + } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { + // Mark children of interactive blocks that are not interactive themselves + // to so we can skip them later. + WP_Directive_Processor::unmark_children_of_interactive_block(); + return get_comment_delimited_block_content( + 'core/non-interactivity-wrapper', + array(), + $block_content + ); + } + + return $block_content; +} + +add_filter( 'render_block', 'gutenberg_mark_block_interactivity', 10, 3 ); /** * Resolve the reference using the store and the context from the provided path. From 7f313a4c229c4dde97fa07f8f8e90d5e80753ef8 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 18 Nov 2023 13:33:09 +0100 Subject: [PATCH 02/42] It works, but is stripping comments --- .../class-wp-directive-processor.php | 16 +++++++++------- .../interactivity-api/directive-processing.php | 9 ++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 761f77a8fe5edd..f28989981ac7fa 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -72,11 +72,11 @@ public static function has_root_block() { /** - * A string containing the main children of interactive. + * An array containing the main children of interactive. * - * @var string + * @var array */ - public static $children_of_interactive_block = null; + public static $children_of_interactive_block = array(); /** @@ -87,16 +87,18 @@ public static function has_root_block() { * @return void */ public static function mark_children_of_interactive_block( $block ) { - self::$children_of_interactive_block = md5( serialize( $block ) ); + self::$children_of_interactive_block[] = md5( serialize( $block ) ); } /** * Remove a root block to the variable. * + * @param array $block The block to remove. + * * @return void */ - public static function unmark_children_of_interactive_block() { - self::$children_of_interactive_block = null; + public static function unmark_children_of_interactive_block( $block ) { + self::$children_of_interactive_block = array_diff( self::$children_of_interactive_block, array( md5( serialize( $block ) ) ) ); } /** @@ -107,7 +109,7 @@ public static function unmark_children_of_interactive_block() { * @return bool True if block is a root block, false otherwise. */ public static function is_marked_as_children_of_interactive_block( $block ) { - return md5( serialize( $block ) ) === self::$children_of_interactive_block; + return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); } /** diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 09c68a75ad3555..812c9c6f6afe0d 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -92,7 +92,6 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst isset( $block_instance->block_type->supports['interactivity'] ) && $block_instance->block_type->supports['interactivity'] ) { - WP_Directive_Processor::unmark_children_of_interactive_block(); // Mark interactive blocks so we can process them later. return get_comment_delimited_block_content( 'core/interactivity-wrapper', @@ -105,10 +104,10 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { // Mark children of interactive blocks that are not interactive themselves // to so we can skip them later. - WP_Directive_Processor::unmark_children_of_interactive_block(); - return get_comment_delimited_block_content( - 'core/non-interactivity-wrapper', - array(), + WP_Directive_Processor::unmark_children_of_interactive_block( $block ); + return sprintf( + '
%s
', + // '%s', $block_content ); } From d6582c41c6b0db23d4006da222322b775bb7d118 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 18 Nov 2023 13:36:39 +0100 Subject: [PATCH 03/42] Added an extra return for debugging --- .../directive-processing.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 812c9c6f6afe0d..b50b0dda3b0a5a 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -105,11 +105,24 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst // Mark children of interactive blocks that are not interactive themselves // to so we can skip them later. WP_Directive_Processor::unmark_children_of_interactive_block( $block ); - return sprintf( - '
%s
', - // '%s', + return get_comment_delimited_block_content( + 'core/interactivity-wrapper', + array( + 'blockName' => $block['blockName'], + // We can put extra information about the block here. + ), $block_content ); + /** + * Debugging purposes only. + */ + return sprintf( + '
%s
', + // '%s', + // $block['blockName'], + $block_content + ); + } return $block_content; From 1f8986d05900dcd0d9224ab41e57692a4eda9ca8 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 18 Nov 2023 14:10:55 +0100 Subject: [PATCH 04/42] Refactor to use string instead of arrays to compare --- .../class-wp-directive-processor.php | 30 +++++-------------- .../directive-processing.php | 21 ++++++------- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index f28989981ac7fa..c3822738ee41ff 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -69,15 +69,12 @@ public static function has_root_block() { return isset( self::$root_block ); } - - /** - * An array containing the main children of interactive. + * A string containing the main children of interactive. * - * @var array + * @var string */ - public static $children_of_interactive_block = array(); - + public static $children_of_interactive_block = null; /** * Add a root block to the variable. @@ -87,18 +84,16 @@ public static function has_root_block() { * @return void */ public static function mark_children_of_interactive_block( $block ) { - self::$children_of_interactive_block[] = md5( serialize( $block ) ); + self::$children_of_interactive_block = md5( serialize( $block ) ); } /** * Remove a root block to the variable. * - * @param array $block The block to remove. - * * @return void */ - public static function unmark_children_of_interactive_block( $block ) { - self::$children_of_interactive_block = array_diff( self::$children_of_interactive_block, array( md5( serialize( $block ) ) ) ); + public static function unmark_children_of_interactive_block() { + self::$children_of_interactive_block = null; } /** @@ -109,20 +104,9 @@ public static function unmark_children_of_interactive_block( $block ) { * @return bool True if block is a root 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 ); - } - - /** - * Check if a root block has already been defined. - * - * @return bool True if block is a root block, false otherwise. - */ - public static function has_children_of_interactive_block() { - return isset( self::$children_of_interactive_block ); + return md5( serialize( $block ) ) === self::$children_of_interactive_block; } - - /** * Find the matching closing tag for an opening tag. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index b50b0dda3b0a5a..2850840c4f9e5a 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -104,7 +104,17 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { // Mark children of interactive blocks that are not interactive themselves // to so we can skip them later. - WP_Directive_Processor::unmark_children_of_interactive_block( $block ); + WP_Directive_Processor::unmark_children_of_interactive_block(); + + /** + * Debugging purposes only. It seems that comments are being stripped. + */ + return sprintf( + '
%s
', + // '%s', + // $block['blockName'], + $block_content + ); return get_comment_delimited_block_content( 'core/interactivity-wrapper', array( @@ -113,15 +123,6 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst ), $block_content ); - /** - * Debugging purposes only. - */ - return sprintf( - '
%s
', - // '%s', - // $block['blockName'], - $block_content - ); } From abc0e731d5556ff11d470209344d78f92f74e31c Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 18 Nov 2023 15:53:11 +0100 Subject: [PATCH 05/42] Use hidden textarea to save comments in production html --- .../directive-processing.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 2850840c4f9e5a..28d42553f9ca2d 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -77,7 +77,6 @@ function gutenberg_mark_interactive_block_children( $parsed_block, $source_block } add_filter( 'render_block_data', 'gutenberg_mark_interactive_block_children', 100, 3 ); - /** * Add a marker indicating if the block is interactive or not. * core/interactivity-wrapper if it is interactive. @@ -107,23 +106,22 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst WP_Directive_Processor::unmark_children_of_interactive_block(); /** - * Debugging purposes only. It seems that comments are being stripped. + * Debugging purposes only. Nested comments are not allowed. + * We wrap a hidden textarea to save the block content delimited + * by comments so we can later process it. */ return sprintf( - '
%s
', - // '%s', - // $block['blockName'], - $block_content - ); - return get_comment_delimited_block_content( - 'core/interactivity-wrapper', - array( - 'blockName' => $block['blockName'], + ' %2s', + get_comment_delimited_block_content( + 'core/non-interactivity-wrapper', + array( + 'blockName' => $block['blockName'], // We can put extra information about the block here. + ), + $block_content ), $block_content ); - } return $block_content; From 08bc3bc210559eb3cf7969c92cfa61be727b9ca4 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 18 Nov 2023 17:20:55 +0100 Subject: [PATCH 06/42] Use divs as delimiters, comments not working for interactive innner components --- .../directive-processing.php | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 28d42553f9ca2d..c79420c4c5a202 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -92,12 +92,13 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst $block_instance->block_type->supports['interactivity'] ) { // Mark interactive blocks so we can process them later. - return get_comment_delimited_block_content( - 'core/interactivity-wrapper', - array( - 'blockName' => $block['blockName'], - // We can put extra information about the block here. - ), + /** + * Debugging purposes only. Nested comments are not allowed. + * We wrap a hidden textarea to save the block content delimited + * by comments so we can later process it. + */ + return sprintf( + '
%s
', $block_content ); } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { @@ -111,15 +112,7 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst * by comments so we can later process it. */ return sprintf( - ' %2s', - get_comment_delimited_block_content( - 'core/non-interactivity-wrapper', - array( - 'blockName' => $block['blockName'], - // We can put extra information about the block here. - ), - $block_content - ), + '
%s
', $block_content ); } From 009e5aabd54f8945987998b284aeabec2feb18bc Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 28 Nov 2023 19:02:37 +0100 Subject: [PATCH 07/42] Back to array for references and comment delimiters --- .../class-wp-directive-processor.php | 31 ++++++------ .../directive-processing.php | 47 ++++++++----------- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index c3822738ee41ff..5f14c030fef853 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -70,30 +70,31 @@ public static function has_root_block() { } /** - * A string containing the main children of interactive. + * An array containing the main children of interactive. * - * @var string + * @var array + */ + public static $children_of_interactive_block = array(); + + /** + * Add a root block to the variable. + * + * @param array $block The block to add. + * + * @return void */ - public static $children_of_interactive_block = null; - - /** - * Add a root block to the variable. - * - * @param array $block The block to add. - * - * @return void - */ public static function mark_children_of_interactive_block( $block ) { - self::$children_of_interactive_block = md5( serialize( $block ) ); + self::$children_of_interactive_block[] = md5( serialize( $block ) ); } /** * Remove a root block to the variable. * + * @param array $block The block to remove. * @return void */ - public static function unmark_children_of_interactive_block() { - self::$children_of_interactive_block = null; + public static function unmark_children_of_interactive_block( $block ) { + self::$children_of_interactive_block = array_diff( self::$children_of_interactive_block, array( md5( serialize( $block ) ) ) ); } /** @@ -104,7 +105,7 @@ public static function unmark_children_of_interactive_block() { * @return bool True if block is a root block, false otherwise. */ public static function is_marked_as_children_of_interactive_block( $block ) { - return md5( serialize( $block ) ) === self::$children_of_interactive_block; + return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); } /** diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index c79420c4c5a202..a85f03999a42d0 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -7,8 +7,6 @@ * @subpackage Interactivity API */ -global $children_of_interactive_block; -$children_of_interactive_block = array(); /** * 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 @@ -88,36 +86,32 @@ function gutenberg_mark_interactive_block_children( $parsed_block, $source_block */ function gutenberg_mark_block_interactivity( $block_content, $block, $block_instance ) { if ( - isset( $block_instance->block_type->supports['interactivity'] ) && - $block_instance->block_type->supports['interactivity'] - ) { + isset( $block_instance->block_type->supports['interactivity'] ) && + $block_instance->block_type->supports['interactivity'] + ) { // Mark interactive blocks so we can process them later. - /** - * Debugging purposes only. Nested comments are not allowed. - * We wrap a hidden textarea to save the block content delimited - * by comments so we can later process it. - */ - return sprintf( - '
%s
', + return get_comment_delimited_block_content( + 'core/interactivity-wrapper', + array( + 'blockName' => $block['blockName'], + // We can put extra information about the block here. + ), $block_content ); } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { + WP_Directive_Processor::unmark_children_of_interactive_block( $block ); // Mark children of interactive blocks that are not interactive themselves // to so we can skip them later. - WP_Directive_Processor::unmark_children_of_interactive_block(); - - /** - * Debugging purposes only. Nested comments are not allowed. - * We wrap a hidden textarea to save the block content delimited - * by comments so we can later process it. - */ - return sprintf( - '
%s
', + return get_comment_delimited_block_content( + 'core/non-interactivity-wrapper', + array( + 'blockName' => $block['blockName'], + // We can put extra information about the block here. + ), $block_content ); } - - return $block_content; + return $block_content; } add_filter( 'render_block', 'gutenberg_mark_block_interactivity', 10, 3 ); @@ -141,10 +135,9 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * passed context) using the subsequent path should be negated. */ $should_negate_value = '!' === $path[0]; - - $path = $should_negate_value ? substr( $path, 1 ) : $path; - $path_segments = explode( '.', $path ); - $current = $store; + $path = $should_negate_value ? substr( $path, 1 ) : $path; + $path_segments = explode( '.', $path ); + $current = $store; foreach ( $path_segments as $p ) { if ( isset( $current[ $p ] ) ) { $current = $current[ $p ]; From af929745cdbc64af85f826df0d0391e91eeecaad Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Thu, 30 Nov 2023 17:08:49 +0100 Subject: [PATCH 08/42] Something is working :-) --- .../directive-processing.php | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index a85f03999a42d0..1a20925fc5ef89 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -36,19 +36,24 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, * @return string Filtered block content. */ 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', - ); + $processed_content = ''; + $p = WP_HTML_Processor::create_fragment( '' ); + $parsed_blocks = parse_blocks( $block_content ); + foreach ( $parsed_blocks as $parsed_block ) { + if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { + $processed_content .= gutenberg_process_interactive_block( $parsed_block, $p ); + } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $p ); + } + } + if ( null === $p->get_last_error() ) { + $content = $processed_content; + } - $tags = new WP_Directive_Processor( $block_content ); - $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives ); - return $tags->get_updated_html(); + return $content; } @@ -161,3 +166,62 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr // Return the opposite if it has a negator operator (!). return $should_negate_value ? ! $current : $current; } + + +function gutenberg_process_interactive_block( $interactive_block, $p ) { + $block_index = 0; + $content = ''; + $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', + ); + + foreach ( $interactive_block['innerContent'] as $inner_content ) { + if ( is_string( $inner_content ) ) { + // This content belongs to an interactive block and therefore can contain + // directives. + $tags = new WP_Directive_Processor( $inner_content ); + $tags->process_rendered_html( $p, 'data-wp-', $directives ); + $content .= $tags->get_updated_html(); + } else { + // This is an inner block. It may be an interactive block or a + // non-interactive block. + $inner_block = $interactive_block['innerBlocks'][ $block_index ]; + $block_index += 1; + + if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_interactive_block( $inner_block, $p ); + } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_non_interactive_block( $inner_block, $p ); + } + } + } + return $content; +} + +function gutenberg_process_non_interactive_block( $non_interactive_block, $p ) { + $block_index = 0; + $content = ''; + foreach ( $non_interactive_block['innerContent'] as $inner_content ) { + if ( is_string( $inner_content ) ) { + // This content belongs to a non interactive block and therefore it cannot + // contain directives. We add the HTML directly to the final output. + $content .= $inner_content; + } else { + // This is an inner block. It may be an interactive block or a + // non-interactive block. + $inner_block = $non_interactive_block['innerBlocks'][ $block_index ]; + $block_index += 1; + + if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_interactive_block( $inner_block, $p ); + } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { + $content .= gutenberg_process_non_interactive_block( $inner_block, $p ); + } + } + } + return $content; +} From 8bc6184d08efd9e95cddfae6fee9909156d8382f Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Mon, 4 Dec 2023 17:34:08 +0100 Subject: [PATCH 09/42] Refactor, process working --- .../directive-processing.php | 49 +++++++++++------ .../directive-processing-test.php | 55 +++++++++++++++++++ 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 1a20925fc5ef89..4ff45ef0cdd994 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -40,26 +40,23 @@ 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(); $processed_content = ''; - $p = WP_HTML_Processor::create_fragment( '' ); $parsed_blocks = parse_blocks( $block_content ); foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_interactive_block( $parsed_block, $p ); + $processed_content .= gutenberg_process_interactive_block( $parsed_block ); } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $p ); + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block ); + } else { + $processed_content .= $parsed_block['innerHTML']; } } - if ( null === $p->get_last_error() ) { - $content = $processed_content; - } - - return $content; + return $processed_content; } return $block_content; } -add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 ); +add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 20, 2 ); /** * Creates a stack of interactive block children. @@ -167,8 +164,15 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr return $should_negate_value ? ! $current : $current; } - -function gutenberg_process_interactive_block( $interactive_block, $p ) { +/** + * Traverses the HTML of an interactive block, + * searching for Interactivity API directives and processing them. + * + * @param array $interactive_block The interactive block to process. + * + * @return string The processed HTML. + */ +function gutenberg_process_interactive_block( $interactive_block ) { $block_index = 0; $content = ''; $directives = array( @@ -184,25 +188,34 @@ function gutenberg_process_interactive_block( $interactive_block, $p ) { // This content belongs to an interactive block and therefore can contain // directives. $tags = new WP_Directive_Processor( $inner_content ); - $tags->process_rendered_html( $p, 'data-wp-', $directives ); + $tags->process_rendered_html( $tags, 'data-wp-', $directives ); $content .= $tags->get_updated_html(); } else { // This is an inner block. It may be an interactive block or a // non-interactive block. $inner_block = $interactive_block['innerBlocks'][ $block_index ]; $block_index += 1; - if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block, $p ); + $content .= gutenberg_process_interactive_block( $inner_block ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block, $p ); + $content .= gutenberg_process_non_interactive_block( $inner_block ); } } } return $content; } -function gutenberg_process_non_interactive_block( $non_interactive_block, $p ) { +/** + * Traverses the HTML of a non-interactive block, + * skipping the processing and returning the content. + * For the inner blocks, it calls the corresponding function + * depending on the wrapper type. + * + * @param array $non_interactive_block The non-interactive block to process. + * + * @return string The processed HTML. + */ +function gutenberg_process_non_interactive_block( $non_interactive_block ) { $block_index = 0; $content = ''; foreach ( $non_interactive_block['innerContent'] as $inner_content ) { @@ -217,9 +230,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $p ) { $block_index += 1; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block, $p ); + $content .= gutenberg_process_interactive_block( $inner_block ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block, $p ); + $content .= gutenberg_process_non_interactive_block( $inner_block ); } } } diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 46ef0284df15d9..8f2446d655a0ac 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -117,6 +117,61 @@ public function test_interactivity_process_directives_in_root_blocks() { gutenberg_process_directives_in_root_blocks( $rendered_content, $parsed_block ); $this->assertEmpty( WP_Directive_Processor::$root_block ); } + + public function test_directive_processing_of_interactive_block() { + register_block_type( + 'gutenberg/test-context', + array( + 'render_callback' => function () { + return '

'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + $args = array( + 'post_content' => '', + 'post_excerpt' => '', + ); + $post = $this->factory()->post->create_and_get( $args ); + setup_postdata( $post ); + + $content = get_the_content( null, false, $post ); + $rendered_blocks = do_blocks( $content ); + $expected = '

hello

'; + $this->assertSame( $expected, $rendered_blocks ); + + unregister_block_type( 'gutenberg/test-context' ); + wp_delete_post( $post->ID, true ); + } + public function test_directive_processing_child_blocks() { + register_block_type( + 'gutenberg/test-context', + array( + 'render_callback' => function () { + return '

'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + $args = array( + 'post_content' => '
', + 'post_excerpt' => '', + ); + $post = $this->factory()->post->create_and_get( $args ); + setup_postdata( $post ); + + $content = get_the_content( null, false, $post ); + $rendered_blocks = do_blocks( $content ); + $expected = '

hello

'; + $this->assertSame( $expected, $rendered_blocks ); + + unregister_block_type( 'gutenberg/test-context' ); + wp_delete_post( $post->ID, true ); + } } From 82c2b16168766adf713730013b78423c2c0ee6ee Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 5 Dec 2023 19:26:40 +0100 Subject: [PATCH 10/42] experiment replacing inner blocks --- .../directive-processing.php | 31 ++++++++++--------- .../directive-processing-test.php | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 4ff45ef0cdd994..8bed9a46409b38 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -173,35 +173,36 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * @return string The processed HTML. */ function gutenberg_process_interactive_block( $interactive_block ) { - $block_index = 0; - $content = ''; - $directives = array( + $block_index = 0; + $content = ''; + $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', ); + $inner_blocks = array(); foreach ( $interactive_block['innerContent'] as $inner_content ) { if ( is_string( $inner_content ) ) { - // This content belongs to an interactive block and therefore can contain - // directives. - $tags = new WP_Directive_Processor( $inner_content ); - $tags->process_rendered_html( $tags, 'data-wp-', $directives ); - $content .= $tags->get_updated_html(); + $content .= $inner_content; } else { // This is an inner block. It may be an interactive block or a // non-interactive block. - $inner_block = $interactive_block['innerBlocks'][ $block_index ]; - $block_index += 1; - if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block ); - } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block ); - } + $content .= ''; + $inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; + $block_index += 1; } } + $tags = new WP_Directive_Processor( $content ); + $tags->process_rendered_html( $tags, 'data-wp-', $directives ); + $content = $tags->get_updated_html(); + if ( ! empty( $inner_blocks ) ) { + $content = str_replace( '', gutenberg_process_interactive_block( $inner_blocks[0] ), $content ); + array_shift( $inner_blocks ); + } + return $content; } diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 8f2446d655a0ac..f01298c84403df 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -166,7 +166,7 @@ public function test_directive_processing_child_blocks() { $content = get_the_content( null, false, $post ); $rendered_blocks = do_blocks( $content ); - $expected = '

hello

'; + $expected = '

hello

'; $this->assertSame( $expected, $rendered_blocks ); unregister_block_type( 'gutenberg/test-context' ); From 65487d10e48339387bca3bb28ba6469a61cbd0d2 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Wed, 13 Dec 2023 15:38:18 +0100 Subject: [PATCH 11/42] now working if there is only 1 interactive block --- .../class-wp-directive-processor.php | 5 ++ .../directive-processing.php | 50 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 5f14c030fef853..4f5a3e50c76e99 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -166,6 +166,11 @@ public function process_rendered_html( $tags, $prefix, $directives ) { while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $tags->get_tag(); + if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) ) { + // Should we process the inner blocks here or just shut. + return $tags; + } + // Is this a tag that closes the latest opening tag? if ( $tags->is_tag_closer() ) { if ( 0 === count( $tag_stack ) ) { diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 8bed9a46409b38..51046b0f5ab28c 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -172,38 +172,50 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * * @return string The processed HTML. */ -function gutenberg_process_interactive_block( $interactive_block ) { - $block_index = 0; - $content = ''; - $directives = array( +function gutenberg_process_interactive_block( $interactive_block, $previous_html = null ) { + $block_index = 0; + $content = ''; + $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', ); - $inner_blocks = array(); + $interactive_inner_blocks = array(); foreach ( $interactive_block['innerContent'] as $inner_content ) { if ( is_string( $inner_content ) ) { $content .= $inner_content; } else { // This is an inner block. It may be an interactive block or a - // non-interactive block. - $content .= ''; - $inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; - $block_index += 1; + // non-interactive block. Not sure if autoclosed custom tags are supported in HTML_Tag_Processor. + $content .= ''; + $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; + $block_index += 1; } } - $tags = new WP_Directive_Processor( $content ); - $tags->process_rendered_html( $tags, 'data-wp-', $directives ); - $content = $tags->get_updated_html(); - if ( ! empty( $inner_blocks ) ) { - $content = str_replace( '', gutenberg_process_interactive_block( $inner_blocks[0] ), $content ); - array_shift( $inner_blocks ); + // If we are processing an interactive inner block, we need to update the instance with the new HTML. + // If we create a new instance, we lose the previous context. So the next lines are wrong. + if ( isset( $previous_html ) ) { + $complete_content = str_replace( '', $content, $previous_html ); + $inner_tags = new WP_Directive_Processor( $complete_content ); + $inner_tags = $inner_tags->process_rendered_html( $inner_tags, 'data-wp-', $directives ); + return $inner_tags->get_updated_html(); + } else { + $tags = new WP_Directive_Processor( $content ); } - return $content; + // Process inner blocks. + if ( ! empty( $interactive_inner_blocks ) ) { + foreach ( $interactive_inner_blocks as $inner_block ) { + $inner_block_content = gutenberg_process_interactive_block( $inner_block, $tags->get_updated_html() ); + } + array_shift( $interactive_inner_blocks ); + return $inner_block_content; + } + $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives ); + return $tags->get_updated_html(); } /** @@ -216,7 +228,7 @@ function gutenberg_process_interactive_block( $interactive_block ) { * * @return string The processed HTML. */ -function gutenberg_process_non_interactive_block( $non_interactive_block ) { +function gutenberg_process_non_interactive_block( $non_interactive_block, $tags = null ) { $block_index = 0; $content = ''; foreach ( $non_interactive_block['innerContent'] as $inner_content ) { @@ -231,9 +243,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block ) { $block_index += 1; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block ); + $content .= gutenberg_process_interactive_block( $inner_block, $tags ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block ); + $content .= gutenberg_process_non_interactive_block( $inner_block, $tags ); } } } From f43d4989c228ee899863034206b23bf646219a1d Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Wed, 13 Dec 2023 20:02:03 +0100 Subject: [PATCH 12/42] now working with 2 interactive blocks --- .../class-wp-directive-processor.php | 3 +- .../directive-processing.php | 61 +++++++++---------- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 4f5a3e50c76e99..c7ed2396cc0070 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -159,8 +159,7 @@ public function next_balanced_closer() { * @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(); + public function process_rendered_html( $tags, $prefix, $directives, $context ) { $tag_stack = array(); while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 51046b0f5ab28c..e4eb83d1bcf229 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -41,11 +41,12 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { WP_Directive_Processor::unmark_root_block(); $processed_content = ''; $parsed_blocks = parse_blocks( $block_content ); + $context = new WP_Directive_Context(); foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_interactive_block( $parsed_block ); + $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context ); } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_non_interactive_block( $parsed_block ); + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context ); } else { $processed_content .= $parsed_block['innerHTML']; } @@ -172,18 +173,10 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * * @return string The processed HTML. */ -function gutenberg_process_interactive_block( $interactive_block, $previous_html = null ) { +function gutenberg_process_interactive_block( $interactive_block, $context, $interactive_inner_blocks_processed = array() ) { $block_index = 0; $content = ''; - $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', - ); $interactive_inner_blocks = array(); - foreach ( $interactive_block['innerContent'] as $inner_content ) { if ( is_string( $inner_content ) ) { $content .= $inner_content; @@ -195,27 +188,33 @@ function gutenberg_process_interactive_block( $interactive_block, $previous_html $block_index += 1; } } - // If we are processing an interactive inner block, we need to update the instance with the new HTML. - // If we create a new instance, we lose the previous context. So the next lines are wrong. - if ( isset( $previous_html ) ) { - $complete_content = str_replace( '', $content, $previous_html ); - $inner_tags = new WP_Directive_Processor( $complete_content ); - $inner_tags = $inner_tags->process_rendered_html( $inner_tags, 'data-wp-', $directives ); - return $inner_tags->get_updated_html(); - } else { - $tags = new WP_Directive_Processor( $content ); - } - - // Process inner blocks. + $tags = new WP_Directive_Processor( $content ); + $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 = $tags->process_rendered_html( $tags, 'data-wp-', $directives, $context ); if ( ! empty( $interactive_inner_blocks ) ) { + $inner_index = 0; foreach ( $interactive_inner_blocks as $inner_block ) { - $inner_block_content = gutenberg_process_interactive_block( $inner_block, $tags->get_updated_html() ); + $inner_block_content = gutenberg_process_interactive_block( $inner_block, $context, $interactive_inner_blocks_processed ); + $interactive_inner_blocks_processed[ '' ] = $inner_block_content; + $inner_index += 1; + } + } + $previous_content = $tags->get_updated_html(); + if ( ! empty( $interactive_inner_blocks_processed ) ) { + foreach ( $interactive_inner_blocks_processed as $inner_block_tag => $inner_blockcontent ) { + if ( str_contains( $previous_content, $inner_block_tag ) ) { + $previous_content = str_replace( $inner_block_tag, $inner_blockcontent, $previous_content ); + unset( $inner_block_tag, $interactive_inner_blocks_processed ); + } } - array_shift( $interactive_inner_blocks ); - return $inner_block_content; } - $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives ); - return $tags->get_updated_html(); + return $previous_content; } /** @@ -228,7 +227,7 @@ function gutenberg_process_interactive_block( $interactive_block, $previous_html * * @return string The processed HTML. */ -function gutenberg_process_non_interactive_block( $non_interactive_block, $tags = null ) { +function gutenberg_process_non_interactive_block( $non_interactive_block, $context ) { $block_index = 0; $content = ''; foreach ( $non_interactive_block['innerContent'] as $inner_content ) { @@ -243,9 +242,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $tags $block_index += 1; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block, $tags ); + $content .= gutenberg_process_interactive_block( $inner_block, $context ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block, $tags ); + $content .= gutenberg_process_non_interactive_block( $inner_block, $context ); } } } From 3288fbe9f5c543a6bc3fcbc8a8301b3c05de76ec Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Thu, 14 Dec 2023 00:27:33 +0100 Subject: [PATCH 13/42] Commented stopping at first custom element, after innerblocks is not being processed otherwise --- .../interactivity-api/class-wp-directive-processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index c7ed2396cc0070..c75708343d1bd5 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -167,7 +167,7 @@ public function process_rendered_html( $tags, $prefix, $directives, $context ) { if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) ) { // Should we process the inner blocks here or just shut. - return $tags; + // return $tags; } // Is this a tag that closes the latest opening tag? From cb26cbef23a78c34b7c62a8b2153d22fd3c478f8 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 16 Dec 2023 12:12:39 +0100 Subject: [PATCH 14/42] Try using bookmarks, fix autoclosing tag not working --- .../class-wp-directive-processor.php | 15 ++++++++++----- .../interactivity-api/directive-processing.php | 7 +++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index c75708343d1bd5..f78bf4f0a6192d 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -159,15 +159,20 @@ public function next_balanced_closer() { * @return WP_Directive_Processor The modified instance of the * WP_Directive_Processor. */ - public function process_rendered_html( $tags, $prefix, $directives, $context ) { + public function process_rendered_html( $tags, $prefix, $directives, $context, $bookmark = null ) { $tag_stack = array(); + if ( $bookmark ) { + $tags->seek( $bookmark ); + $tags->release_bookmark( $bookmark ); + } + while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $tags->get_tag(); - - if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) ) { - // Should we process the inner blocks here or just shut. - // return $tags; + if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && is_null( $bookmark ) ) { + // Process the inner blocks. + $tags->set_bookmark( 'inner-blocks' ); + return $tags; } // Is this a tag that closes the latest opening tag? diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index e4eb83d1bcf229..9da049fe0acde3 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -183,7 +183,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $int } else { // This is an inner block. It may be an interactive block or a // non-interactive block. Not sure if autoclosed custom tags are supported in HTML_Tag_Processor. - $content .= ''; + $content .= ''; $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; $block_index += 1; } @@ -201,10 +201,13 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $int $inner_index = 0; foreach ( $interactive_inner_blocks as $inner_block ) { $inner_block_content = gutenberg_process_interactive_block( $inner_block, $context, $interactive_inner_blocks_processed ); - $interactive_inner_blocks_processed[ '' ] = $inner_block_content; + $interactive_inner_blocks_processed[ '' ] = $inner_block_content; $inner_index += 1; } + // Return to process after inner blocks. + $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives, $context, 'inner-blocks' ); } + $previous_content = $tags->get_updated_html(); if ( ! empty( $interactive_inner_blocks_processed ) ) { foreach ( $interactive_inner_blocks_processed as $inner_block_tag => $inner_blockcontent ) { From 7e056585b381b476be63000aef3412559774e4b3 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Sat, 16 Dec 2023 20:49:53 +0100 Subject: [PATCH 15/42] Not using bookmarks anymore, just processing inner inside processing function --- .../class-wp-directive-processor.php | 25 ++-- .../directive-processing.php | 117 +++++++++++++----- 2 files changed, 99 insertions(+), 43 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index f78bf4f0a6192d..efba98a69f9395 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -159,20 +159,22 @@ public function next_balanced_closer() { * @return WP_Directive_Processor The modified instance of the * WP_Directive_Processor. */ - public function process_rendered_html( $tags, $prefix, $directives, $context, $bookmark = null ) { - $tag_stack = array(); - - if ( $bookmark ) { - $tags->seek( $bookmark ); - $tags->release_bookmark( $bookmark ); - } - + public function process_rendered_html( $tags, $prefix, $directives, $context, $interactive_inner_blocks = array() ) { + $tag_stack = array(); + $inner_blocks_processed_content = ''; while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $tags->get_tag(); - if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && is_null( $bookmark ) ) { + if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) ) { // Process the inner blocks. - $tags->set_bookmark( 'inner-blocks' ); - return $tags; + if ( ! empty( $interactive_inner_blocks ) ) { + foreach ( $interactive_inner_blocks as $inner_block ) { + foreach ( $inner_block['innerContent'] as $inner_content ) { + $inner_tags = new WP_Directive_Processor( $inner_content ); + $inner_tags = $inner_tags->process_rendered_html( $inner_tags, 'data-wp-', $directives, $context ); + $inner_blocks_processed_content .= $inner_tags->get_updated_html(); + } + } + } } // Is this a tag that closes the latest opening tag? @@ -184,7 +186,6 @@ public function process_rendered_html( $tags, $prefix, $directives, $context, $b 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; diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 9da049fe0acde3..f322c1c9f7a5b4 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -173,7 +173,7 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * * @return string The processed HTML. */ -function gutenberg_process_interactive_block( $interactive_block, $context, $interactive_inner_blocks_processed = array() ) { +function gutenberg_process_interactive_block( $interactive_block, $context ) { $block_index = 0; $content = ''; $interactive_inner_blocks = array(); @@ -188,36 +188,8 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $int $block_index += 1; } } - $tags = new WP_Directive_Processor( $content ); - $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 = $tags->process_rendered_html( $tags, 'data-wp-', $directives, $context ); - if ( ! empty( $interactive_inner_blocks ) ) { - $inner_index = 0; - foreach ( $interactive_inner_blocks as $inner_block ) { - $inner_block_content = gutenberg_process_interactive_block( $inner_block, $context, $interactive_inner_blocks_processed ); - $interactive_inner_blocks_processed[ '' ] = $inner_block_content; - $inner_index += 1; - } - // Return to process after inner blocks. - $tags = $tags->process_rendered_html( $tags, 'data-wp-', $directives, $context, 'inner-blocks' ); - } - - $previous_content = $tags->get_updated_html(); - if ( ! empty( $interactive_inner_blocks_processed ) ) { - foreach ( $interactive_inner_blocks_processed as $inner_block_tag => $inner_blockcontent ) { - if ( str_contains( $previous_content, $inner_block_tag ) ) { - $previous_content = str_replace( $inner_block_tag, $inner_blockcontent, $previous_content ); - unset( $inner_block_tag, $interactive_inner_blocks_processed ); - } - } - } - return $previous_content; + $content = gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context ); + return $content; } /** @@ -253,3 +225,86 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte } return $content; } + + +function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { + $tags = new WP_Directive_Processor( $html ); + $prefix = 'data-wp-'; + $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', + ); + $tag_stack = array(); + $inner_processed_blocks = array(); + + while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { + $tag_name = $tags->get_tag(); + if ( ! isset( $inner_blocks_index ) ) { + $inner_blocks_index = 0; + } + if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { + // Process the inner blocks. + $inner_block_content = ''; + foreach ( $inner_blocks[ $inner_blocks_index ]['innerContent'] as $inner_content ) { + $inner_block_content = gutenberg_process_interactive_html( $inner_content, $inner_blocks[ $inner_blocks_index ]['innerBlocks'], $context ); + } + $inner_processed_blocks[ strtolower( $tag_name ) ] = $inner_block_content; + $inner_blocks_index += 1; + } + 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 ) = $tags::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 ( + ! $tags::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 ); + } + } + $processed_html = $tags->get_updated_html(); + if ( ! empty( $inner_processed_blocks ) ) { + foreach ( $inner_processed_blocks as $inner_block_tag => $inner_block_content ) { + if ( str_contains( $processed_html, $inner_block_tag ) ) { + $processed_html = str_replace( '<' . $inner_block_tag . '>', $inner_block_content, $processed_html ); + } + } + } + return $processed_html; +} From f23d2e047a594c999848e99f7cfac90597e02a58 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Mon, 18 Dec 2023 13:52:39 +0100 Subject: [PATCH 16/42] Seems to be working, still needs a good test battery --- .../directive-processing.php | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index f322c1c9f7a5b4..62e57264080dbe 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -182,7 +182,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context ) { $content .= $inner_content; } else { // This is an inner block. It may be an interactive block or a - // non-interactive block. Not sure if autoclosed custom tags are supported in HTML_Tag_Processor. + // non-interactive block. $content .= ''; $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; $block_index += 1; @@ -226,7 +226,22 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte return $content; } - +/** + * Processes interactive HTML by applying directives to the HTML tags. + * + * @param string $html The HTML to process. + * @param array $inner_blocks The inner blocks to process. + * @param mixed $context The context to use when processing. + * + * This function processes an HTML string by applying directives to the HTML tags. + * It uses the WP_Directive_Processor class to parse the HTML and apply the directives. + * The directives are specified in the $directives array and are applied to the tags that + * have the corresponding data attribute. + * If a tag contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, the function + * processes these inner blocks and replaces the 'WP-INNER-BLOCKS' tag in the HTML with those blocks. + * + * @return string The processed HTML. + */ function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { $tags = new WP_Directive_Processor( $html ); $prefix = 'data-wp-'; @@ -245,12 +260,11 @@ function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { if ( ! isset( $inner_blocks_index ) ) { $inner_blocks_index = 0; } + // Process the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { - // Process the inner blocks. $inner_block_content = ''; - foreach ( $inner_blocks[ $inner_blocks_index ]['innerContent'] as $inner_content ) { - $inner_block_content = gutenberg_process_interactive_html( $inner_content, $inner_blocks[ $inner_blocks_index ]['innerBlocks'], $context ); - } + $inner_block = $inner_blocks[$inner_blocks_index]; + $inner_block_content .= gutenberg_process_interactive_block( $inner_block, $context ); $inner_processed_blocks[ strtolower( $tag_name ) ] = $inner_block_content; $inner_blocks_index += 1; } @@ -299,6 +313,7 @@ function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { } } $processed_html = $tags->get_updated_html(); + // Replace the inner block tags with the content of each inner block processed. if ( ! empty( $inner_processed_blocks ) ) { foreach ( $inner_processed_blocks as $inner_block_tag => $inner_block_content ) { if ( str_contains( $processed_html, $inner_block_tag ) ) { From 1fc73ae723ea61de0341476c20865b46f76c8fea Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Mon, 18 Dec 2023 18:14:52 +0100 Subject: [PATCH 17/42] Fix interactivity API directive processing --- .../class-wp-directive-processor.php | 79 ------------------- .../directive-processing.php | 63 ++++++++------- .../directive-processing-test.php | 10 +-- 3 files changed, 40 insertions(+), 112 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index efba98a69f9395..c8cfe82b175352 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -148,85 +148,6 @@ 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, $interactive_inner_blocks = array() ) { - $tag_stack = array(); - $inner_blocks_processed_content = ''; - while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { - $tag_name = $tags->get_tag(); - if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) ) { - // Process the inner blocks. - if ( ! empty( $interactive_inner_blocks ) ) { - foreach ( $interactive_inner_blocks as $inner_block ) { - foreach ( $inner_block['innerContent'] as $inner_content ) { - $inner_tags = new WP_Directive_Processor( $inner_content ); - $inner_tags = $inner_tags->process_rendered_html( $inner_tags, 'data-wp-', $directives, $context ); - $inner_blocks_processed_content .= $inner_tags->get_updated_html(); - } - } - } - } - - // 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. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 62e57264080dbe..a2342a35df9818 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -37,6 +37,15 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, */ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { + // Posible candidate to add a filter to allow plugins to add directives. + $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', + ); + if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) { WP_Directive_Processor::unmark_root_block(); $processed_content = ''; @@ -44,9 +53,9 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { $context = new WP_Directive_Context(); foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context ); + $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $directives ); } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context ); + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context, $directives ); } else { $processed_content .= $parsed_block['innerHTML']; } @@ -169,11 +178,13 @@ function gutenberg_interactivity_evaluate_reference( $path, array $context = arr * Traverses the HTML of an interactive block, * searching for Interactivity API directives and processing them. * - * @param array $interactive_block The interactive block to process. + * @param array $interactive_block The interactive block to process. + * @param WP_Directive_Context $context The context to use when processing. + * @param array $directives The directives to apply. * * @return string The processed HTML. */ -function gutenberg_process_interactive_block( $interactive_block, $context ) { +function gutenberg_process_interactive_block( $interactive_block, $context, $directives ) { $block_index = 0; $content = ''; $interactive_inner_blocks = array(); @@ -188,7 +199,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context ) { $block_index += 1; } } - $content = gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context ); + $content = gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context, $directives ); return $content; } @@ -198,11 +209,13 @@ function gutenberg_process_interactive_block( $interactive_block, $context ) { * For the inner blocks, it calls the corresponding function * depending on the wrapper type. * - * @param array $non_interactive_block The non-interactive block to process. + * @param array $non_interactive_block The non-interactive block to process. + * @param WP_Directive_Context $context The context to use when processing. + * @param array $directives The directives to apply. * * @return string The processed HTML. */ -function gutenberg_process_non_interactive_block( $non_interactive_block, $context ) { +function gutenberg_process_non_interactive_block( $non_interactive_block, $context, $directives ) { $block_index = 0; $content = ''; foreach ( $non_interactive_block['innerContent'] as $inner_content ) { @@ -217,9 +230,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte $block_index += 1; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block, $context ); + $content .= gutenberg_process_interactive_block( $inner_block, $context, $directives ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block, $context ); + $content .= gutenberg_process_non_interactive_block( $inner_block, $context, $directives ); } } } @@ -230,28 +243,22 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte * Processes interactive HTML by applying directives to the HTML tags. * * @param string $html The HTML to process. - * @param array $inner_blocks The inner blocks to process. - * @param mixed $context The context to use when processing. + * @param array $inner_blocks The inner blocks to process. + * @param mixed $context The context to use when processing. + * @param array $directives The directives to apply. * - * This function processes an HTML string by applying directives to the HTML tags. - * It uses the WP_Directive_Processor class to parse the HTML and apply the directives. - * The directives are specified in the $directives array and are applied to the tags that - * have the corresponding data attribute. - * If a tag contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, the function - * processes these inner blocks and replaces the 'WP-INNER-BLOCKS' tag in the HTML with those blocks. + * This function processes an HTML string by applying directives to the HTML tags. + * It uses the WP_Directive_Processor class to parse the HTML and apply the directives. + * The directives are specified in the $directives array and are applied to the tags that + * have the corresponding data attribute. + * If a tag contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, the function + * processes these inner blocks and replaces the 'WP-INNER-BLOCKS' tag in the HTML with those blocks. * * @return string The processed HTML. */ -function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { +function gutenberg_process_interactive_html( $html, $inner_blocks, $context, $directives ) { $tags = new WP_Directive_Processor( $html ); $prefix = 'data-wp-'; - $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', - ); $tag_stack = array(); $inner_processed_blocks = array(); @@ -262,9 +269,9 @@ function gutenberg_process_interactive_html( $html, $inner_blocks, $context ) { } // Process the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { - $inner_block_content = ''; - $inner_block = $inner_blocks[$inner_blocks_index]; - $inner_block_content .= gutenberg_process_interactive_block( $inner_block, $context ); + $inner_block_content = ''; + $inner_block = $inner_blocks[ $inner_blocks_index ]; + $inner_block_content .= gutenberg_process_interactive_block( $inner_block, $context, $directives ); $inner_processed_blocks[ strtolower( $tag_name ) ] = $inner_block_content; $inner_blocks_index += 1; } diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index f01298c84403df..9647c75cecca1a 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -35,7 +35,7 @@ function gutenberg_test_process_directives_helper_increment( $store ) { */ class Tests_Process_Directives extends WP_UnitTestCase { public function test_correctly_call_attribute_directive_processor_on_closing_tag() { - + $context = new WP_Directive_Context(); // PHPUnit cannot stub functions, only classes. $test_helper = $this->createMock( Helper_Class::class ); @@ -59,11 +59,12 @@ function ( $p ) { ); $markup = '
Example:
This is a test>
Here is a nested div
'; - $tags = new WP_Directive_Processor( $markup ); - $tags->process_rendered_html( $tags, 'foo-', $directives ); + + gutenberg_process_interactive_html( $markup, array(), $context, $directives ); } public function test_directives_with_double_hyphen_processed_correctly() { + $context = new WP_Directive_Context(); $test_helper = $this->createMock( Helper_Class::class ); $test_helper->expects( $this->atLeastOnce() ) ->method( 'process_foo_test' ); @@ -73,8 +74,7 @@ public function test_directives_with_double_hyphen_processed_correctly() { ); $markup = '
'; - $tags = new WP_Directive_Processor( $markup ); - $tags->process_rendered_html( $tags, 'foo-', $directives ); + gutenberg_process_interactive_html( $markup, array(), $context, $directives ); } public function test_interactivity_process_directives_in_root_blocks() { From 07f819a35cd386a14189f7cfc3dfb546f6b1b6ab Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Mon, 18 Dec 2023 19:03:49 +0100 Subject: [PATCH 18/42] Remove tests that will be updated in next commits --- .../directive-processing-test.php | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 9647c75cecca1a..6edb0456a96bc4 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -11,7 +11,7 @@ class Helper_Class { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - public function process_foo_test( $tags, $context ) { + public function process_foo_test( $markup, $inner_blocks, $context, $directives ) { } public function increment( $store ) { @@ -34,48 +34,6 @@ function gutenberg_test_process_directives_helper_increment( $store ) { * @covers gutenberg_interactivity_process_rendered_html */ class Tests_Process_Directives extends WP_UnitTestCase { - public function test_correctly_call_attribute_directive_processor_on_closing_tag() { - $context = new WP_Directive_Context(); - // PHPUnit cannot stub functions, only classes. - $test_helper = $this->createMock( Helper_Class::class ); - - $test_helper->expects( $this->exactly( 2 ) ) - ->method( 'process_foo_test' ) - ->with( - $this->callback( - function ( $p ) { - return 'DIV' === $p->get_tag() && ( - // Either this is a closing tag... - $p->is_tag_closer() || - // ...or it is an open tag, and has the directive attribute set. - ( ! $p->is_tag_closer() && 'abc' === $p->get_attribute( 'foo-test' ) ) - ); - } - ) - ); - - $directives = array( - 'foo-test' => array( $test_helper, 'process_foo_test' ), - ); - - $markup = '
Example:
This is a test>
Here is a nested div
'; - - gutenberg_process_interactive_html( $markup, array(), $context, $directives ); - } - - public function test_directives_with_double_hyphen_processed_correctly() { - $context = new WP_Directive_Context(); - $test_helper = $this->createMock( Helper_Class::class ); - $test_helper->expects( $this->atLeastOnce() ) - ->method( 'process_foo_test' ); - - $directives = array( - 'foo-test' => array( $test_helper, 'process_foo_test' ), - ); - - $markup = '
'; - gutenberg_process_interactive_html( $markup, array(), $context, $directives ); - } public function test_interactivity_process_directives_in_root_blocks() { From 30e366e6e2285473d80492448c25020567e409d6 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Mon, 18 Dec 2023 23:08:25 +0100 Subject: [PATCH 19/42] Add markup tests --- .../directive-processing-test.php | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 6edb0456a96bc4..097191013c4cb5 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -10,9 +10,6 @@ class Helper_Class { - // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - public function process_foo_test( $markup, $inner_blocks, $context, $directives ) { - } public function increment( $store ) { return $store['state']['count'] + $store['context']['count']; @@ -35,6 +32,30 @@ function gutenberg_test_process_directives_helper_increment( $store ) { */ class Tests_Process_Directives extends WP_UnitTestCase { + public function set_up() { + parent::set_up(); + register_block_type( + 'gutenberg/test-context-level-1', + array( + 'render_callback' => function ( $a, $b, $block ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + return '

' . $inner_blocks_html . '

'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + } + + public function tear_down() { + unregister_block_type( 'gutenberg/test-context-level-1' ); + parent::tear_down(); + } + public function test_interactivity_process_directives_in_root_blocks() { $block_content = @@ -77,57 +98,41 @@ public function test_interactivity_process_directives_in_root_blocks() { } public function test_directive_processing_of_interactive_block() { - register_block_type( - 'gutenberg/test-context', - array( - 'render_callback' => function () { - return '

'; - }, - 'supports' => array( - 'interactivity' => true, - ), - ) - ); $args = array( - 'post_content' => '', - 'post_excerpt' => '', + 'post_content' => '', ); $post = $this->factory()->post->create_and_get( $args ); setup_postdata( $post ); $content = get_the_content( null, false, $post ); $rendered_blocks = do_blocks( $content ); - $expected = '

hello

'; + $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); - - unregister_block_type( 'gutenberg/test-context' ); wp_delete_post( $post->ID, true ); } public function test_directive_processing_child_blocks() { - register_block_type( - 'gutenberg/test-context', - array( - 'render_callback' => function () { - return '

'; - }, - 'supports' => array( - 'interactivity' => true, - ), - ) - ); $args = array( - 'post_content' => '
', - 'post_excerpt' => '', + 'post_content' => '
', ); $post = $this->factory()->post->create_and_get( $args ); setup_postdata( $post ); $content = get_the_content( null, false, $post ); $rendered_blocks = do_blocks( $content ); - $expected = '

hello

'; + $expected = '

level-1

level-1

'; + $this->assertSame( $expected, $rendered_blocks ); + wp_delete_post( $post->ID, true ); + } + public function test_directive_processing_inner_non_interactive_blocks() { + $args = array( + 'post_content' => '

inner non interactive

', + ); + $post = $this->factory()->post->create_and_get( $args ); + setup_postdata( $post ); + $content = get_the_content( null, false, $post ); + $rendered_blocks = do_blocks( $content ); + $expected = '

level-1

inner non interactive

level-1

'; $this->assertSame( $expected, $rendered_blocks ); - - unregister_block_type( 'gutenberg/test-context' ); wp_delete_post( $post->ID, true ); } } From b3063a7c6c1355563a6bc99189fbd9cf28ab0fed Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 19 Dec 2023 10:28:49 +0100 Subject: [PATCH 20/42] Use correct div group structure --- .../interactivity-api/directive-processing-test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 097191013c4cb5..5e6206af48768a 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -119,7 +119,7 @@ public function test_directive_processing_child_blocks() { $content = get_the_content( null, false, $post ); $rendered_blocks = do_blocks( $content ); - $expected = '

level-1

level-1

'; + $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); wp_delete_post( $post->ID, true ); } From b9ac02cd809b828095ec9ee761f6f6c166ead852 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 19 Dec 2023 11:11:29 +0100 Subject: [PATCH 21/42] Small refactor --- .../directive-processing.php | 13 ++++----- .../directive-processing-test.php | 28 ++++++------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index a2342a35df9818..9f08a5b131ed76 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -49,8 +49,9 @@ 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(); $processed_content = ''; - $parsed_blocks = parse_blocks( $block_content ); - $context = new WP_Directive_Context(); + // We parse our own block delimiters for interactive and non-interactive blocks. + $parsed_blocks = parse_blocks( $block_content ); + $context = new WP_Directive_Context(); foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $directives ); @@ -199,8 +200,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir $block_index += 1; } } - $content = gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context, $directives ); - return $content; + return gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context, $directives ); } /** @@ -269,10 +269,7 @@ function gutenberg_process_interactive_html( $html, $inner_blocks, $context, $di } // Process the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { - $inner_block_content = ''; - $inner_block = $inner_blocks[ $inner_blocks_index ]; - $inner_block_content .= gutenberg_process_interactive_block( $inner_block, $context, $directives ); - $inner_processed_blocks[ strtolower( $tag_name ) ] = $inner_block_content; + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index ], $context, $directives ); $inner_blocks_index += 1; } if ( $tags->is_tag_closer() ) { diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 5e6206af48768a..788b577a6cb50b 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -98,42 +98,30 @@ public function test_interactivity_process_directives_in_root_blocks() { } public function test_directive_processing_of_interactive_block() { - $args = array( + $args = array( 'post_content' => '', ); - $post = $this->factory()->post->create_and_get( $args ); - setup_postdata( $post ); - - $content = get_the_content( null, false, $post ); - $rendered_blocks = do_blocks( $content ); + $rendered_blocks = do_blocks( $args['post_content'] ); $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); - wp_delete_post( $post->ID, true ); } + public function test_directive_processing_child_blocks() { - $args = array( + $args = array( 'post_content' => '
', ); - $post = $this->factory()->post->create_and_get( $args ); - setup_postdata( $post ); - - $content = get_the_content( null, false, $post ); - $rendered_blocks = do_blocks( $content ); + $rendered_blocks = do_blocks( $args['post_content'] ); $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); - wp_delete_post( $post->ID, true ); } + public function test_directive_processing_inner_non_interactive_blocks() { - $args = array( + $args = array( 'post_content' => '

inner non interactive

', ); - $post = $this->factory()->post->create_and_get( $args ); - setup_postdata( $post ); - $content = get_the_content( null, false, $post ); - $rendered_blocks = do_blocks( $content ); + $rendered_blocks = do_blocks( $args['post_content'] ); $expected = '

level-1

inner non interactive

level-1

'; $this->assertSame( $expected, $rendered_blocks ); - wp_delete_post( $post->ID, true ); } } From 35e4d9f73b8a448c547cfb2f227417f2e7a52198 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Tue, 19 Dec 2023 11:31:34 +0100 Subject: [PATCH 22/42] remove not needed block name in interactive markers --- .../interactivity-api/directive-processing.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 9f08a5b131ed76..4f4e24d6cf15b9 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -105,10 +105,7 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst // Mark interactive blocks so we can process them later. return get_comment_delimited_block_content( 'core/interactivity-wrapper', - array( - 'blockName' => $block['blockName'], - // We can put extra information about the block here. - ), + array(), $block_content ); } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { @@ -117,10 +114,7 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst // to so we can skip them later. return get_comment_delimited_block_content( 'core/non-interactivity-wrapper', - array( - 'blockName' => $block['blockName'], - // We can put extra information about the block here. - ), + array(), $block_content ); } From 27615189ae280152941914b877074098eac4192b Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Wed, 20 Dec 2023 12:57:06 +0100 Subject: [PATCH 23/42] Add more markup tests --- .../directive-processing-test.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 788b577a6cb50b..04d68f7597b7eb 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -49,10 +49,38 @@ public function set_up() { ), ) ); + register_block_type( + 'gutenberg/test-context-level-2', + array( + 'render_callback' => function ( $a, $b, $block ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + return '

' . $inner_blocks_html . '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + register_block_type( + 'gutenberg/test-context-read-only', + array( + 'render_callback' => function () { + return '

'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); } public function tear_down() { unregister_block_type( 'gutenberg/test-context-level-1' ); + unregister_block_type( 'gutenberg/test-context-level-2' ); + unregister_block_type( 'gutenberg/test-context-read-only' ); parent::tear_down(); } @@ -123,6 +151,32 @@ public function test_directive_processing_inner_non_interactive_blocks() { $expected = '

level-1

inner non interactive

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } + public function test_directive_processing_two_interactive_blocks_at_same_level() { + $args = array( + 'post_content' => '
', + ); + $rendered_blocks = do_blocks( $args['post_content'] ); + $expected = '

level-1

level-1

level-1

level-1

'; + $this->assertSame( $expected, $rendered_blocks ); + } + + public function test_directive_processing_alternating_interactive_and_not_interactive() { + $args = array( + 'post_content' => '

inner non interactive

', + ); + $rendered_blocks = do_blocks( $args['post_content'] ); + $expected = '

level-1

level-1

inner non interactive

level-1

level-1

'; + $this->assertSame( $expected, $rendered_blocks ); + } + + public function test_directive_directives_are_processed_at_tag_end() { + $args = array( + 'post_content' => '', + ); + $rendered_blocks = do_blocks( $args['post_content'] ); + $expected = '

level-1

level-2

level-1

level-1

'; + $this->assertSame( $expected, $rendered_blocks ); + } } From b66c522a91c40102fc3d9ba68f38bcc4ffe93fd3 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Wed, 20 Dec 2023 15:09:41 +0100 Subject: [PATCH 24/42] refactor tests --- .../directive-processing-test.php | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 04d68f7597b7eb..a6d373b03cdbcb 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -126,57 +126,52 @@ public function test_interactivity_process_directives_in_root_blocks() { } public function test_directive_processing_of_interactive_block() { - $args = array( - 'post_content' => '', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } public function test_directive_processing_child_blocks() { - $args = array( - 'post_content' => '
', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + $post_content = '
'; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } public function test_directive_processing_inner_non_interactive_blocks() { - $args = array( - 'post_content' => '

inner non interactive

', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + $post_content = '

inner non interactive

'; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

inner non interactive

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } public function test_directive_processing_two_interactive_blocks_at_same_level() { - $args = array( - 'post_content' => '
', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + $post_content = '
'; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

level-1

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } public function test_directive_processing_alternating_interactive_and_not_interactive() { - $args = array( - 'post_content' => '

inner non interactive

', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + $post_content = '

inner non interactive

'; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

level-1

inner non interactive

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } - public function test_directive_directives_are_processed_at_tag_end() { - $args = array( - 'post_content' => '', - ); - $rendered_blocks = do_blocks( $args['post_content'] ); + public function test_directives_are_processed_at_tag_end() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); $expected = '

level-1

level-2

level-1

level-1

'; $this->assertSame( $expected, $rendered_blocks ); } + + public function test_directive_processing_with_patterns() { + $post_content = '

inner non interactive

inner non interactive

inner non interactive

'; + $rendered_blocks = do_blocks( $post_content ); + $expected = '

level-1

inner non interactive

level-2

level-2

inner non interactive

level-2

inner non interactive

level-1

'; + $this->assertSame( $expected, $rendered_blocks ); + } } From 6e5c57906c4319b91af21fc8a347e2a450877d58 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Wed, 20 Dec 2023 20:53:34 +0100 Subject: [PATCH 25/42] Make inner blocks optional --- lib/experimental/interactivity-api/directive-processing.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 4f4e24d6cf15b9..20038a08814f2a 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -194,7 +194,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir $block_index += 1; } } - return gutenberg_process_interactive_html( $content, $interactive_inner_blocks, $context, $directives ); + return gutenberg_process_interactive_html( $content, $context, $directives, $interactive_inner_blocks ); } /** @@ -237,9 +237,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte * Processes interactive HTML by applying directives to the HTML tags. * * @param string $html The HTML to process. - * @param array $inner_blocks The inner blocks to process. * @param mixed $context The context to use when processing. * @param array $directives The directives to apply. + * @param array $inner_blocks The inner blocks to process. * * This function processes an HTML string by applying directives to the HTML tags. * It uses the WP_Directive_Processor class to parse the HTML and apply the directives. @@ -250,7 +250,7 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte * * @return string The processed HTML. */ -function gutenberg_process_interactive_html( $html, $inner_blocks, $context, $directives ) { +function gutenberg_process_interactive_html( $html, $context, $directives, $inner_blocks = array() ) { $tags = new WP_Directive_Processor( $html ); $prefix = 'data-wp-'; $tag_stack = array(); From 6a9ba8fc323ad1336b7c27c3adc4070461f7ff04 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Thu, 21 Dec 2023 16:55:13 +0100 Subject: [PATCH 26/42] Move directives declaration to declare them only if needed --- .../interactivity-api/directive-processing.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 20038a08814f2a..4d04b11e12b6d5 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -37,21 +37,20 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, */ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { - // Posible candidate to add a filter to allow plugins to add directives. - $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', - ); - if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) { WP_Directive_Processor::unmark_root_block(); $processed_content = ''; // We parse our own block delimiters for interactive and non-interactive blocks. $parsed_blocks = parse_blocks( $block_content ); $context = new WP_Directive_Context(); + // Posible candidate to add a filter to allow plugins to add directives. + $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', + ); foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $directives ); From 90f1c6af88d57a00f7074fbb28b8843c8fe64222 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Thu, 21 Dec 2023 17:33:47 +0100 Subject: [PATCH 27/42] Refactor more code, thanks to @darerodz --- .../interactivity-api/directive-processing.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 4d04b11e12b6d5..89b8f6cfd1ff02 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -189,8 +189,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir // This is an inner block. It may be an interactive block or a // non-interactive block. $content .= ''; - $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index ]; - $block_index += 1; + $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index++ ]; } } return gutenberg_process_interactive_html( $content, $context, $directives, $interactive_inner_blocks ); @@ -219,8 +218,7 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte } else { // This is an inner block. It may be an interactive block or a // non-interactive block. - $inner_block = $non_interactive_block['innerBlocks'][ $block_index ]; - $block_index += 1; + $inner_block = $non_interactive_block['innerBlocks'][ $block_index++ ]; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { $content .= gutenberg_process_interactive_block( $inner_block, $context, $directives ); @@ -254,16 +252,12 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne $prefix = 'data-wp-'; $tag_stack = array(); $inner_processed_blocks = array(); - + $inner_blocks_index = 0; while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $tags->get_tag(); - if ( ! isset( $inner_blocks_index ) ) { - $inner_blocks_index = 0; - } // Process the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { - $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index ], $context, $directives ); - $inner_blocks_index += 1; + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); } if ( $tags->is_tag_closer() ) { if ( 0 === count( $tag_stack ) ) { From 51caefd809fa888dc6797e458e1a42b7e69be329 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Thu, 21 Dec 2023 19:00:30 +0100 Subject: [PATCH 28/42] Remove only the first ocurrence --- .../interactivity-api/class-wp-directive-processor.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index c8cfe82b175352..92b1a0045ddcfd 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -94,7 +94,10 @@ public static function mark_children_of_interactive_block( $block ) { * @return void */ public static function unmark_children_of_interactive_block( $block ) { - self::$children_of_interactive_block = array_diff( self::$children_of_interactive_block, array( md5( serialize( $block ) ) ) ); + $key = array_search( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); + if ( false !== $key ) { + unset( self::$children_of_interactive_block[ $key ] ); + } } /** From bfc0ecd54762576f58b7c6cf029244ec8f419d7a Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Thu, 21 Dec 2023 19:01:17 +0100 Subject: [PATCH 29/42] Improve comments and format --- .../class-wp-directive-processor.php | 70 +++---- .../directive-processing.php | 198 +++++++++--------- .../directive-processing-test.php | 22 +- 3 files changed, 139 insertions(+), 151 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 92b1a0045ddcfd..9e4faa4e8967f2 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -13,47 +13,42 @@ /** * 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 { - /** - * A string containing the main root block. + * String containing the current root block. * * @var string */ public static $root_block = null; /** - * Add a root block to the variable. + * Array containing the direct children of interactive blocks. * - * @param array $block The block to add. + * @var array + */ + public static $children_of_interactive_block = array(); + + /** + * Sets the current root block. * - * @return void + * @param array $block The block to add. */ 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 ) { @@ -61,37 +56,28 @@ public static function is_marked_as_root_block( $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 ); } /** - * An array containing the main children of interactive. - * - * @var array - */ - public static $children_of_interactive_block = array(); - - /** - * Add a root block to the variable. + * 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. - * - * @return void */ public static function mark_children_of_interactive_block( $block ) { self::$children_of_interactive_block[] = md5( serialize( $block ) ); } /** - * Remove a root block to the variable. + * Removes a reference to a direct children of an interactive block. * * @param array $block The block to remove. - * @return void */ public static function unmark_children_of_interactive_block( $block ) { $key = array_search( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); @@ -101,18 +87,17 @@ public static function unmark_children_of_interactive_block( $block ) { } /** - * Check if block is a root block. + * 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 root block, false otherwise. + * @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 ); } /** - * Find the matching closing tag for an opening tag. + * 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 @@ -152,7 +137,7 @@ public function next_balanced_closer() { } /** - * 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. @@ -178,14 +163,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 ) { @@ -209,7 +193,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 @@ -239,12 +223,12 @@ public function get_balanced_tag_bookmarks() { } /** - * Whether a given HTML element is void (e.g.
). + * Checks whether a given HTML element is void (e.g.
). + * + * @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 ) { @@ -269,7 +253,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: @@ -279,7 +263,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 ); diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 89b8f6cfd1ff02..8975b9221d9e33 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -1,16 +1,16 @@ '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', + ); if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) { WP_Directive_Processor::unmark_root_block(); + + // Parse our own block delimiters for interactive and non-interactive blocks. + $parsed_blocks = parse_blocks( $block_content ); + $context = new WP_Directive_Context(); $processed_content = ''; - // We parse our own block delimiters for interactive and non-interactive blocks. - $parsed_blocks = parse_blocks( $block_content ); - $context = new WP_Directive_Context(); - // Posible candidate to add a filter to allow plugins to add directives. - $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', - ); + foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $directives ); @@ -61,7 +62,6 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { } } return $processed_content; - } return $block_content; @@ -69,28 +69,26 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 20, 2 ); /** - * Creates a stack of interactive block children. + * Marks the block as a children of an interactive block. * * @param array $parsed_block The parsed block. * @param array $source_block The source block. * @param WP_Block $parent_block The parent block. */ -function gutenberg_mark_interactive_block_children( $parsed_block, $source_block, $parent_block ) { +function gutenberg_mark_chidren_of_interactive_block( $parsed_block, $source_block, $parent_block ) { if ( - isset( $parent_block ) && - isset( $parent_block->block_type->supports['interactivity'] ) && - $parent_block->block_type->supports['interactivity'] - ) { + isset( $parent_block ) && + isset( $parent_block->block_type->supports['interactivity'] ) && + $parent_block->block_type->supports['interactivity'] + ) { WP_Directive_Processor::mark_children_of_interactive_block( $source_block ); } return $parsed_block; } -add_filter( 'render_block_data', 'gutenberg_mark_interactive_block_children', 100, 3 ); +add_filter( 'render_block_data', 'gutenberg_mark_chidren_of_interactive_block', 100, 3 ); /** - * Add a marker indicating if the block is interactive or not. - * core/interactivity-wrapper if it is interactive. - * core/non-interactivity-wrapper if it is not interactive. + * Adds a comment delimiter to mark if the block is interactive or not. * * @param string $block_content The block content. * @param array $block The full block, including name and attributes. @@ -101,7 +99,8 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst isset( $block_instance->block_type->supports['interactivity'] ) && $block_instance->block_type->supports['interactivity'] ) { - // Mark interactive blocks so we can process them later. + // Wraps the interactive block with a comment delimiter to be able to + // process it later. return get_comment_delimited_block_content( 'core/interactivity-wrapper', array(), @@ -109,8 +108,9 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst ); } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { WP_Directive_Processor::unmark_children_of_interactive_block( $block ); - // Mark children of interactive blocks that are not interactive themselves - // to so we can skip them later. + + // Wraps the non-interactive block with a comment delimiter to be able to + // skip it later. return get_comment_delimited_block_content( 'core/non-interactivity-wrapper', array(), @@ -119,58 +119,12 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst } return $block_content; } - add_filter( 'render_block', 'gutenberg_mark_block_interactivity', 10, 3 ); /** - * Resolve the reference using the store and the context from the provided path. - * - * @param string $path Path. - * @param array $context Context data. - * @return mixed - */ -function gutenberg_interactivity_evaluate_reference( $path, array $context = array() ) { - $store = array_merge( - WP_Interactivity_Store::get_data(), - array( 'context' => $context ) - ); - - /* - * Check first if the directive path is preceded by a negator operator (!), - * indicating that the value obtained from the Interactivity Store (or the - * passed context) using the subsequent path should be negated. - */ - $should_negate_value = '!' === $path[0]; - $path = $should_negate_value ? substr( $path, 1 ) : $path; - $path_segments = explode( '.', $path ); - $current = $store; - foreach ( $path_segments as $p ) { - if ( isset( $current[ $p ] ) ) { - $current = $current[ $p ]; - } else { - return null; - } - } - - /* - * Check if $current is an anonymous function or an arrow function, and if - * so, call it passing the store. Other types of callables are ignored on - * purpose, as arbitrary strings or arrays could be wrongly evaluated as - * "callables". - * - * E.g., "file" is an string and a "callable" (the "file" function exists). - */ - if ( $current instanceof Closure ) { - $current = call_user_func( $current, $store ); - } - - // Return the opposite if it has a negator operator (!). - return $should_negate_value ? ! $current : $current; -} - -/** - * Traverses the HTML of an interactive block, - * searching for Interactivity API directives and processing them. + * Traverses the HTML of an interactive block, searching for Interactivity API + * directives and processing them. For the inner blocks, it calls the + * corresponding function depending on the wrapper type. * * @param array $interactive_block The interactive block to process. * @param WP_Directive_Context $context The context to use when processing. @@ -182,6 +136,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir $block_index = 0; $content = ''; $interactive_inner_blocks = array(); + foreach ( $interactive_block['innerContent'] as $inner_content ) { if ( is_string( $inner_content ) ) { $content .= $inner_content; @@ -192,13 +147,13 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir $interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index++ ]; } } + return gutenberg_process_interactive_html( $content, $context, $directives, $interactive_inner_blocks ); } /** - * Traverses the HTML of a non-interactive block, - * skipping the processing and returning the content. - * For the inner blocks, it calls the corresponding function + * Returns the HTML of a non-interactive block without processing the + * directives. For the inner blocks, it calls the corresponding function * depending on the wrapper type. * * @param array $non_interactive_block The non-interactive block to process. @@ -233,18 +188,18 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte /** * Processes interactive HTML by applying directives to the HTML tags. * + * It uses the WP_Directive_Processor class to parse the HTML and apply the + * directives. The directives are specified in the $directives array and are + * applied to the tags that have the corresponding data attribute. If a tag + * contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, + * the function processes these inner blocks and replaces the 'WP-INNER-BLOCKS' + * tag in the HTML with those blocks. + * * @param string $html The HTML to process. * @param mixed $context The context to use when processing. * @param array $directives The directives to apply. * @param array $inner_blocks The inner blocks to process. * - * This function processes an HTML string by applying directives to the HTML tags. - * It uses the WP_Directive_Processor class to parse the HTML and apply the directives. - * The directives are specified in the $directives array and are applied to the tags that - * have the corresponding data attribute. - * If a tag contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, the function - * processes these inner blocks and replaces the 'WP-INNER-BLOCKS' tag in the HTML with those blocks. - * * @return string The processed HTML. */ function gutenberg_process_interactive_html( $html, $context, $directives, $inner_blocks = array() ) { @@ -255,11 +210,11 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne $inner_blocks_index = 0; while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) { $tag_name = $tags->get_tag(); - // Process the inner blocks. + + // Processes the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); - } - if ( $tags->is_tag_closer() ) { + } elseif ( $tags->is_tag_closer() ) { if ( 0 === count( $tag_stack ) ) { continue; } @@ -292,8 +247,8 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne * encounter the matching closing tag. */ if ( - ! $tags::is_html_void_element( $tags->get_tag() ) && - ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) + ! $tags::is_html_void_element( $tag_name ) && + ( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) ) ) { $tag_stack[] = array( $tag_name, $attributes ); } @@ -303,8 +258,11 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne call_user_func( $directives[ $attribute ], $tags, $context ); } } + $processed_html = $tags->get_updated_html(); - // Replace the inner block tags with the content of each inner block processed. + + // Replaces the inner block tags with the content of each inner block + // processed. if ( ! empty( $inner_processed_blocks ) ) { foreach ( $inner_processed_blocks as $inner_block_tag => $inner_block_content ) { if ( str_contains( $processed_html, $inner_block_tag ) ) { @@ -312,5 +270,53 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne } } } + return $processed_html; } + +/** + * Resolves the reference using the store and the context from the provided + * path. + * + * @param string $path Path. + * @param array $context Context data. + * @return mixed + */ +function gutenberg_interactivity_evaluate_reference( $path, array $context = array() ) { + $store = array_merge( + WP_Interactivity_Store::get_data(), + array( 'context' => $context ) + ); + + /* + * Checks first if the directive path is preceded by a negator operator (!), + * indicating that the value obtained from the Interactivity Store (or the + * passed context) using the subsequent path should be negated. + */ + $should_negate_value = '!' === $path[0]; + $path = $should_negate_value ? substr( $path, 1 ) : $path; + $path_segments = explode( '.', $path ); + $current = $store; + foreach ( $path_segments as $p ) { + if ( isset( $current[ $p ] ) ) { + $current = $current[ $p ]; + } else { + return null; + } + } + + /* + * Checks if $current is an anonymous function or an arrow function, and if + * so, call it passing the store. Other types of callables are ignored on + * purpose, as arbitrary strings or arrays could be wrongly evaluated as + * "callables". + * + * E.g., "file" is an string and a "callable" (the "file" function exists). + */ + if ( $current instanceof Closure ) { + $current = call_user_func( $current, $store ); + } + + // Returns the opposite if it has a negator operator (!). + return $should_negate_value ? ! $current : $current; +} diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index a6d373b03cdbcb..2576fb9ab84302 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -8,9 +8,7 @@ * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound */ - class Helper_Class { - public function increment( $store ) { return $store['state']['count'] + $store['context']['count']; } @@ -31,9 +29,9 @@ function gutenberg_test_process_directives_helper_increment( $store ) { * @covers gutenberg_interactivity_process_rendered_html */ class Tests_Process_Directives extends WP_UnitTestCase { - public function set_up() { parent::set_up(); + register_block_type( 'gutenberg/test-context-level-1', array( @@ -49,6 +47,7 @@ public function set_up() { ), ) ); + register_block_type( 'gutenberg/test-context-level-2', array( @@ -64,6 +63,7 @@ public function set_up() { ), ) ); + register_block_type( 'gutenberg/test-context-read-only', array( @@ -85,7 +85,6 @@ public function tear_down() { } public function test_interactivity_process_directives_in_root_blocks() { - $block_content = '' . '

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

' . @@ -94,15 +93,11 @@ public function test_interactivity_process_directives_in_root_blocks() { '

Welcome to WordPress.

' . ''; - $parsed_block = parse_blocks( $block_content )[0]; - - $source_block = $parsed_block; - - $rendered_content = render_block( $parsed_block ); - + $parsed_block = parse_blocks( $block_content )[0]; + $source_block = $parsed_block; + $rendered_content = render_block( $parsed_block ); $parsed_block_second = parse_blocks( $block_content )[1]; - - $fake_parent_block = array(); + $fake_parent_block = array(); // Test that root block is intially emtpy. $this->assertEmpty( WP_Directive_Processor::$root_block ); @@ -197,6 +192,7 @@ public function test_evaluate_function_should_access_state() { ), ) ); + $this->assertSame( 1, gutenberg_interactivity_evaluate_reference( 'state.core.number' ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( 'state.core.bool' ) ); $this->assertSame( 'hi', gutenberg_interactivity_evaluate_reference( 'state.core.nested.string' ) ); @@ -213,10 +209,12 @@ public function test_evaluate_function_should_access_passed_context() { ), ), ); + $this->assertSame( 2, gutenberg_interactivity_evaluate_reference( 'context.local.number', $context ) ); $this->assertFalse( gutenberg_interactivity_evaluate_reference( 'context.local.bool', $context ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( '!context.local.bool', $context ) ); $this->assertSame( 'bye', gutenberg_interactivity_evaluate_reference( 'context.local.nested.string', $context ) ); + // Previously defined state is also accessible. $this->assertSame( 1, gutenberg_interactivity_evaluate_reference( 'state.core.number' ) ); $this->assertTrue( gutenberg_interactivity_evaluate_reference( 'state.core.bool' ) ); From 5a01ef221b717540792913e67bf9960c658881e3 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Fri, 22 Dec 2023 00:21:29 +0100 Subject: [PATCH 30/42] Update tests to use HTML API, fix non interactive blocks not being parsed --- .../class-wp-directive-processor.php | 12 -- .../directive-processing.php | 11 +- .../directive-processing-test.php | 140 +++++++----------- 3 files changed, 59 insertions(+), 104 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 9e4faa4e8967f2..bb70068aa9482b 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -74,18 +74,6 @@ public static function mark_children_of_interactive_block( $block ) { self::$children_of_interactive_block[] = md5( serialize( $block ) ); } - /** - * Removes a reference to a direct children of an interactive block. - * - * @param array $block The block to remove. - */ - public static function unmark_children_of_interactive_block( $block ) { - $key = array_search( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); - if ( false !== $key ) { - unset( self::$children_of_interactive_block[ $key ] ); - } - } - /** * Checks if block is marked as children of an interactive block. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 8975b9221d9e33..7944bfb84d7294 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -107,8 +107,6 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst $block_content ); } elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) { - WP_Directive_Processor::unmark_children_of_interactive_block( $block ); - // Wraps the non-interactive block with a comment delimiter to be able to // skip it later. return get_comment_delimited_block_content( @@ -213,8 +211,13 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne // Processes the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { - $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); - } elseif ( $tags->is_tag_closer() ) { + if ( 'core/interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); + } elseif ( 'core/non-interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_non_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); + } + } + if ( $tags->is_tag_closer() ) { if ( 0 === count( $tag_stack ) ) { continue; } diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 2576fb9ab84302..655dc1ab03022f 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -4,30 +4,8 @@ * * @package Gutenberg * @subpackage Interactivity API - * - * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound */ -class Helper_Class { - public function increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } - - public static function static_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } -} - -function gutenberg_test_process_directives_helper_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; -} - -/** - * Tests for the gutenberg_interactivity_process_rendered_html function. - * - * @group interactivity-api - * @covers gutenberg_interactivity_process_rendered_html - */ class Tests_Process_Directives extends WP_UnitTestCase { public function set_up() { parent::set_up(); @@ -35,12 +13,8 @@ public function set_up() { register_block_type( 'gutenberg/test-context-level-1', array( - 'render_callback' => function ( $a, $b, $block ) { - $inner_blocks_html = ''; - foreach ( $block->inner_blocks as $inner_block ) { - $inner_blocks_html .= $inner_block->render(); - } - return '

' . $inner_blocks_html . '

'; + 'render_callback' => function ( $a, $content ) { + return '
' . $content . '
'; }, 'supports' => array( 'interactivity' => true, @@ -51,12 +25,8 @@ public function set_up() { register_block_type( 'gutenberg/test-context-level-2', array( - 'render_callback' => function ( $a, $b, $block ) { - $inner_blocks_html = ''; - foreach ( $block->inner_blocks as $inner_block ) { - $inner_blocks_html .= $inner_block->render(); - } - return '

' . $inner_blocks_html . '
'; + 'render_callback' => function ( $a, $content ) { + return '
' . $content . '
'; }, 'supports' => array( 'interactivity' => true, @@ -68,7 +38,7 @@ public function set_up() { 'gutenberg/test-context-read-only', array( 'render_callback' => function () { - return '

'; + return '
'; }, 'supports' => array( 'interactivity' => true, @@ -123,60 +93,60 @@ public function test_interactivity_process_directives_in_root_blocks() { public function test_directive_processing_of_interactive_block() { $post_content = ''; $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); } - public function test_directive_processing_child_blocks() { - $post_content = '
'; - $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); - } - - public function test_directive_processing_inner_non_interactive_blocks() { - $post_content = '

inner non interactive

'; - $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

inner non interactive

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); - } public function test_directive_processing_two_interactive_blocks_at_same_level() { - $post_content = '
'; + $post_content = '
'; $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

level-1

level-1

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); - } - - public function test_directive_processing_alternating_interactive_and_not_interactive() { - $post_content = '

inner non interactive

'; - $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

level-1

inner non interactive

level-1

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-2-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-2', $value ); } public function test_directives_are_processed_at_tag_end() { $post_content = ''; $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

level-2

level-1

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-2-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-2', $value ); + $p->next_tag( array( 'class_name' => 'read-only-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); } - public function test_directive_processing_with_patterns() { - $post_content = '

inner non interactive

inner non interactive

inner non interactive

'; - $rendered_blocks = do_blocks( $post_content ); - $expected = '

level-1

inner non interactive

level-2

level-2

inner non interactive

level-2

inner non interactive

level-1

'; - $this->assertSame( $expected, $rendered_blocks ); + public function increment( $store ) { + return $store['state']['count'] + $store['context']['count']; } -} + public static function static_increment( $store ) { + return $store['state']['count'] + $store['context']['count']; + } + + protected function gutenberg_test_process_directives_helper_increment( $store ) { + return $store['state']['count'] + $store['context']['count']; + } -/** - * Tests for the gutenberg_interactivity_evaluate_reference function. - * - * @group interactivity-api - * @covers gutenberg_interactivity_evaluate_reference - */ -class Tests_Utils_Evaluate extends WP_UnitTestCase { public function test_evaluate_function_should_access_state() { // Init a simple store. wp_store( @@ -227,7 +197,6 @@ public function test_evaluate_function_should_return_null_for_unresolved_paths() public function test_evaluate_function_should_execute_anonymous_functions() { $context = new WP_Directive_Context( array( 'count' => 2 ) ); - $helper = new Helper_Class(); wp_store( array( @@ -235,34 +204,29 @@ public function test_evaluate_function_should_execute_anonymous_functions() { 'count' => 3, ), 'selectors' => array( - 'anonymous_function' => function ( $store ) { + 'anonymous_function' => function ( $store ) { return $store['state']['count'] + $store['context']['count']; }, // Other types of callables should not be executed. - 'function_name' => 'gutenberg_test_process_directives_helper_increment', - 'class_method' => array( $helper, 'increment' ), - 'class_static_method' => 'Helper_Class::static_increment', - 'class_static_method_as_array' => array( 'Helper_Class', 'static_increment' ), + 'function_name' => array( $this, 'gutenberg_test_process_directives_helper_increment' ), + 'class_method' => array( $this, 'increment' ), + 'class_static_method' => array( $this, 'static_increment' ), ), ) ); $this->assertSame( 5, gutenberg_interactivity_evaluate_reference( 'selectors.anonymous_function', $context->get_context() ) ); $this->assertSame( - 'gutenberg_test_process_directives_helper_increment', + array( $this, 'gutenberg_test_process_directives_helper_increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.function_name', $context->get_context() ) ); $this->assertSame( - array( $helper, 'increment' ), + array( $this, 'increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.class_method', $context->get_context() ) ); $this->assertSame( - 'Helper_Class::static_increment', + array( $this, 'static_increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.class_static_method', $context->get_context() ) ); - $this->assertSame( - array( 'Helper_Class', 'static_increment' ), - gutenberg_interactivity_evaluate_reference( 'selectors.class_static_method_as_array', $context->get_context() ) - ); } } From f5ac832ebd8f1b2666f7eb1999be44c9d9172d63 Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Fri, 22 Dec 2023 10:52:22 +0100 Subject: [PATCH 31/42] Unmark children of interactive blocks according to @DAreRodz comment --- .../class-wp-directive-processor.php | 12 ++++++++++++ .../interactivity-api/directive-processing.php | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index bb70068aa9482b..9e4faa4e8967f2 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -74,6 +74,18 @@ public static function mark_children_of_interactive_block( $block ) { self::$children_of_interactive_block[] = md5( serialize( $block ) ); } + /** + * Removes a reference to a direct children of an interactive block. + * + * @param array $block The block to remove. + */ + public static function unmark_children_of_interactive_block( $block ) { + $key = array_search( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); + if ( false !== $key ) { + unset( self::$children_of_interactive_block[ $key ] ); + } + } + /** * Checks if block is marked as children of an interactive block. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 7944bfb84d7294..3bcfd36bd7ef0b 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -99,6 +99,12 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst isset( $block_instance->block_type->supports['interactivity'] ) && $block_instance->block_type->supports['interactivity'] ) { + if ( isset( $block['innerBlocks'] ) ) { + foreach ( $block['innerBlocks'] as $inner_block ) { + WP_Directive_Processor::unmark_children_of_interactive_block( $inner_block ); + } + } + // Wraps the interactive block with a comment delimiter to be able to // process it later. return get_comment_delimited_block_content( From 4843cb7ff8a0f4f9ec073e3a3163d85d6b8c007b Mon Sep 17 00:00:00 2001 From: Carlos Bravo Date: Fri, 22 Dec 2023 13:18:48 +0100 Subject: [PATCH 32/42] Add a p tag check test --- .../directive-processing-test.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 655dc1ab03022f..0dafc3c8a58970 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -135,6 +135,23 @@ public function test_directives_are_processed_at_tag_end() { $this->assertSame( 'level-1', $value ); } + public function test_non_interactive_children_of_interactive_is_rendered() { + $post_content = '

Welcome

'; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag( array( 'class_name' => 'read-only-input-1' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + $p->next_tag(); + $this->assertSame( 'P', $p->get_tag() ); + $p->next_tag( array( 'class_name' => 'level-1-input-2' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'level-1', $value ); + } + public function increment( $store ) { return $store['state']['count'] + $store['context']['count']; } From 091193804f651d02db241798f0655fc7eee94ac5 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Thu, 30 Nov 2023 14:20:28 +0100 Subject: [PATCH 33/42] Execute directives by priority --- .../directive-processing.php | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 3bcfd36bd7ef0b..b9c646503882aa 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -37,8 +37,8 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, */ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { static $directives = array( - 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', + 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', @@ -263,7 +263,24 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne } } - foreach ( $attributes as $attribute ) { + // Extract all directive names. They'll be used later on. + $directive_names = array_keys( $directives ); + $directive_names_rev = array_reverse( $directive_names ); + + /* + * Sort attributes by the order they appear in the `$directives` + * argument, considering it as the priority order in which + * directives should be processed. Note that the order is reversed + * for tag closers. + */ + $sorted_attrs = array_intersect( + $tags->is_tag_closer() + ? $directive_names_rev + : $directive_names, + $attributes + ); + + foreach ( $sorted_attrs as $attribute ) { call_user_func( $directives[ $attribute ], $tags, $context ); } } From aea5f74d7c173a5b55f352eddd191eecfdc3b170 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:21:22 +0100 Subject: [PATCH 34/42] Remove gutenberg name from tests and use camelCase for context property --- .../directive-processing-test.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 0dafc3c8a58970..212c0b39eac953 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -11,10 +11,10 @@ public function set_up() { parent::set_up(); register_block_type( - 'gutenberg/test-context-level-1', + 'test/context-level-1', array( 'render_callback' => function ( $a, $content ) { - return '
' . $content . '
'; + return '
' . $content . '
'; }, 'supports' => array( 'interactivity' => true, @@ -23,10 +23,10 @@ public function set_up() { ); register_block_type( - 'gutenberg/test-context-level-2', + 'test/context-level-2', array( 'render_callback' => function ( $a, $content ) { - return '
' . $content . '
'; + return '
' . $content . '
'; }, 'supports' => array( 'interactivity' => true, @@ -35,10 +35,10 @@ public function set_up() { ); register_block_type( - 'gutenberg/test-context-read-only', + 'test/context-read-only', array( 'render_callback' => function () { - return '
'; + return '
'; }, 'supports' => array( 'interactivity' => true, @@ -48,9 +48,9 @@ public function set_up() { } public function tear_down() { - unregister_block_type( 'gutenberg/test-context-level-1' ); - unregister_block_type( 'gutenberg/test-context-level-2' ); - unregister_block_type( 'gutenberg/test-context-read-only' ); + unregister_block_type( 'test/context-level-1' ); + unregister_block_type( 'test/context-level-2' ); + unregister_block_type( 'test/context-read-only' ); parent::tear_down(); } @@ -91,7 +91,7 @@ public function test_interactivity_process_directives_in_root_blocks() { } public function test_directive_processing_of_interactive_block() { - $post_content = ''; + $post_content = ''; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); @@ -103,7 +103,7 @@ public function test_directive_processing_of_interactive_block() { } public function test_directive_processing_two_interactive_blocks_at_same_level() { - $post_content = '
'; + $post_content = '
'; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); @@ -118,7 +118,7 @@ public function test_directive_processing_two_interactive_blocks_at_same_level() } public function test_directives_are_processed_at_tag_end() { - $post_content = ''; + $post_content = ''; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); @@ -136,7 +136,7 @@ public function test_directives_are_processed_at_tag_end() { } public function test_non_interactive_children_of_interactive_is_rendered() { - $post_content = '

Welcome

'; + $post_content = '

Welcome

'; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); From 93be7567c5ccae6a9f6e451c08f4b9cf9ccff625 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:23:40 +0100 Subject: [PATCH 35/42] Fix empty style attribute error --- lib/experimental/interactivity-api/directives/wp-style.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/interactivity-api/directives/wp-style.php b/lib/experimental/interactivity-api/directives/wp-style.php index 9c37f9082c2c0b..431ee696c1e736 100644 --- a/lib/experimental/interactivity-api/directives/wp-style.php +++ b/lib/experimental/interactivity-api/directives/wp-style.php @@ -28,7 +28,7 @@ function gutenberg_interactivity_process_wp_style( $tags, $context ) { $expr = $tags->get_attribute( $attr ); $style_value = gutenberg_interactivity_evaluate_reference( $expr, $context->get_context() ); if ( $style_value ) { - $style_attr = $tags->get_attribute( 'style' ); + $style_attr = $tags->get_attribute( 'style' ) || ''; $style_attr = gutenberg_interactivity_set_style( $style_attr, $style_name, $style_value ); $tags->set_attribute( 'style', $style_attr ); } else { From 2c0b72259bb230d53f05d711ffb6d0c81e823eb0 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:23:57 +0100 Subject: [PATCH 36/42] Add test for directive ordering --- .../directive-processing-test.php | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 212c0b39eac953..1e784077cc0c1c 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -103,7 +103,7 @@ public function test_directive_processing_of_interactive_block() { } public function test_directive_processing_two_interactive_blocks_at_same_level() { - $post_content = '
'; + $post_content = '
'; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); $p->next_tag( array( 'class_name' => 'level-1-input-1' ) ); @@ -152,6 +152,36 @@ public function test_non_interactive_children_of_interactive_is_rendered() { $this->assertSame( 'level-1', $value ); } + public function test_directives_ordering() { + register_block_type( + 'test/directives-ordering', + array( + 'render_callback' => function () { + return ''; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag(); + + $value = $p->get_attribute( 'class' ); + $this->assertSame( 'other-class some-class', $value ); + + $value = $p->get_attribute( 'value' ); + $this->assertSame( 'some-value', $value ); + + $value = $p->get_attribute( 'style' ); + $this->assertSame( 'display: none;', $value ); + + unregister_block_type( 'test/directives-ordering' ); + } + public function increment( $store ) { return $store['state']['count'] + $store['context']['count']; } From d500b1910b3fc3bd61bd2a99db8ce1504a8c3e50 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:37:47 +0100 Subject: [PATCH 37/42] Fix evaluate should only execute anonymous functions test --- .../directive-processing-test.php | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 1e784077cc0c1c..3ea17d30c485d1 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -182,18 +182,6 @@ public function test_directives_ordering() { unregister_block_type( 'test/directives-ordering' ); } - public function increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } - - public static function static_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } - - protected function gutenberg_test_process_directives_helper_increment( $store ) { - return $store['state']['count'] + $store['context']['count']; - } - public function test_evaluate_function_should_access_state() { // Init a simple store. wp_store( @@ -255,16 +243,16 @@ public function test_evaluate_function_should_execute_anonymous_functions() { return $store['state']['count'] + $store['context']['count']; }, // Other types of callables should not be executed. - 'function_name' => array( $this, 'gutenberg_test_process_directives_helper_increment' ), + 'function_name' => 'gutenberg_test_process_directives_helper_increment', 'class_method' => array( $this, 'increment' ), - 'class_static_method' => array( $this, 'static_increment' ), + 'class_static_method' => array( 'Tests_Process_Directives', 'static_increment' ), ), ) ); $this->assertSame( 5, gutenberg_interactivity_evaluate_reference( 'selectors.anonymous_function', $context->get_context() ) ); $this->assertSame( - array( $this, 'gutenberg_test_process_directives_helper_increment' ), + 'gutenberg_test_process_directives_helper_increment', gutenberg_interactivity_evaluate_reference( 'selectors.function_name', $context->get_context() ) ); $this->assertSame( @@ -272,7 +260,7 @@ public function test_evaluate_function_should_execute_anonymous_functions() { gutenberg_interactivity_evaluate_reference( 'selectors.class_method', $context->get_context() ) ); $this->assertSame( - array( $this, 'static_increment' ), + array( 'Tests_Process_Directives', 'static_increment' ), gutenberg_interactivity_evaluate_reference( 'selectors.class_static_method', $context->get_context() ) ); } From ec51bda7727dd252f2252c4727d58f4f06be8576 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:46:22 +0100 Subject: [PATCH 38/42] Fix wp-style tests --- .../interactivity-api/directives/wp-style.php | 2 +- .../directives/wp-style-test.php | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/experimental/interactivity-api/directives/wp-style.php b/lib/experimental/interactivity-api/directives/wp-style.php index 431ee696c1e736..e5d7b269ace7cf 100644 --- a/lib/experimental/interactivity-api/directives/wp-style.php +++ b/lib/experimental/interactivity-api/directives/wp-style.php @@ -28,7 +28,7 @@ function gutenberg_interactivity_process_wp_style( $tags, $context ) { $expr = $tags->get_attribute( $attr ); $style_value = gutenberg_interactivity_evaluate_reference( $expr, $context->get_context() ); if ( $style_value ) { - $style_attr = $tags->get_attribute( 'style' ) || ''; + $style_attr = $tags->get_attribute( 'style' ) ?? ''; $style_attr = gutenberg_interactivity_set_style( $style_attr, $style_name, $style_value ); $tags->set_attribute( 'style', $style_attr ); } else { diff --git a/phpunit/experimental/interactivity-api/directives/wp-style-test.php b/phpunit/experimental/interactivity-api/directives/wp-style-test.php index 8942559b2fe89f..447ea10201db73 100644 --- a/phpunit/experimental/interactivity-api/directives/wp-style-test.php +++ b/phpunit/experimental/interactivity-api/directives/wp-style-test.php @@ -31,7 +31,7 @@ public function test_directive_adds_style() { } public function test_directive_ignores_empty_style() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); @@ -43,4 +43,17 @@ public function test_directive_ignores_empty_style() { $this->assertStringNotContainsString( 'color: green;', $tags->get_attribute( 'style' ) ); $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-style directive changed context' ); } + + public function test_directive_works_without_style_attribute() { + $markup = '
Test
'; + $tags = new WP_HTML_Tag_Processor( $markup ); + $tags->next_tag(); + + $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context = $context_before; + gutenberg_interactivity_process_wp_style( $tags, $context ); + + $this->assertSame( 'color: green;', $tags->get_attribute( 'style' ) ); + $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-style directive changed context' ); + } } From 9c0290d66cfc1d2540148b73051e56968f7a7022 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Sun, 24 Dec 2023 16:49:26 +0100 Subject: [PATCH 39/42] Improve tests --- .../directives/wp-style-test.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directives/wp-style-test.php b/phpunit/experimental/interactivity-api/directives/wp-style-test.php index 447ea10201db73..51468bd8a28141 100644 --- a/phpunit/experimental/interactivity-api/directives/wp-style-test.php +++ b/phpunit/experimental/interactivity-api/directives/wp-style-test.php @@ -14,16 +14,16 @@ */ class Tests_Directives_WpStyle extends WP_UnitTestCase { public function test_directive_adds_style() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); - $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); $context = $context_before; gutenberg_interactivity_process_wp_style( $tags, $context ); $this->assertSame( - '
Test
', + '
Test
', $tags->get_updated_html() ); $this->assertStringContainsString( 'color: green;', $tags->get_attribute( 'style' ) ); @@ -31,11 +31,11 @@ public function test_directive_adds_style() { } public function test_directive_ignores_empty_style() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); - $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); $context = $context_before; gutenberg_interactivity_process_wp_style( $tags, $context ); @@ -45,14 +45,18 @@ public function test_directive_ignores_empty_style() { } public function test_directive_works_without_style_attribute() { - $markup = '
Test
'; + $markup = '
Test
'; $tags = new WP_HTML_Tag_Processor( $markup ); $tags->next_tag(); - $context_before = new WP_Directive_Context( array( 'myblock' => array( 'color' => 'green' ) ) ); + $context_before = new WP_Directive_Context( array( 'color' => 'green' ) ); $context = $context_before; gutenberg_interactivity_process_wp_style( $tags, $context ); + $this->assertSame( + '
Test
', + $tags->get_updated_html() + ); $this->assertSame( 'color: green;', $tags->get_attribute( 'style' ) ); $this->assertSame( $context_before->get_context(), $context->get_context(), 'data-wp-style directive changed context' ); } From 3c00d97eb4765dc0cfb0c36bf85b9d2d0d4b89a1 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Mon, 25 Dec 2023 13:21:51 +0100 Subject: [PATCH 40/42] Test that we don't process non-interactive blocks --- .../directive-processing-test.php | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/phpunit/experimental/interactivity-api/directive-processing-test.php b/phpunit/experimental/interactivity-api/directive-processing-test.php index 3ea17d30c485d1..99218f69a8fb79 100644 --- a/phpunit/experimental/interactivity-api/directive-processing-test.php +++ b/phpunit/experimental/interactivity-api/directive-processing-test.php @@ -13,7 +13,7 @@ public function set_up() { register_block_type( 'test/context-level-1', array( - 'render_callback' => function ( $a, $content ) { + 'render_callback' => function ( $attributes, $content ) { return '
' . $content . '
'; }, 'supports' => array( @@ -25,7 +25,7 @@ public function set_up() { register_block_type( 'test/context-level-2', array( - 'render_callback' => function ( $a, $content ) { + 'render_callback' => function ( $attributes, $content ) { return '
' . $content . '
'; }, 'supports' => array( @@ -45,12 +45,52 @@ public function set_up() { ), ) ); + + register_block_type( + 'test/non-interactive-with-directive', + array( + 'render_callback' => function () { + return ''; + }, + ) + ); + + register_block_type( + 'test/context-level-with-manual-inner-block-rendering', + array( + 'render_callback' => function ( $attributes, $content, $block ) { + $inner_blocks_html = ''; + foreach ( $block->inner_blocks as $inner_block ) { + $inner_blocks_html .= $inner_block->render(); + } + return '
' . $inner_blocks_html . '
'; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); + + register_block_type( + 'test/directives-ordering', + array( + 'render_callback' => function () { + return ''; + }, + 'supports' => array( + 'interactivity' => true, + ), + ) + ); } public function tear_down() { unregister_block_type( 'test/context-level-1' ); unregister_block_type( 'test/context-level-2' ); unregister_block_type( 'test/context-read-only' ); + unregister_block_type( 'test/non-interactive-with-directive' ); + unregister_block_type( 'test/context-level-with-manual-inner-block-rendering' ); + unregister_block_type( 'test/directives-ordering' ); parent::tear_down(); } @@ -152,19 +192,25 @@ public function test_non_interactive_children_of_interactive_is_rendered() { $this->assertSame( 'level-1', $value ); } - public function test_directives_ordering() { - register_block_type( - 'test/directives-ordering', - array( - 'render_callback' => function () { - return ''; - }, - 'supports' => array( - 'interactivity' => true, - ), - ) - ); + public function test_non_interactive_blocks_are_not_processed() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'non-interactive-with-directive' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( null, $value ); + } + public function test_non_interactive_blocks_with_manual_inner_block_rendering_are_not_processed() { + $post_content = ''; + $rendered_blocks = do_blocks( $post_content ); + $p = new WP_HTML_Tag_Processor( $rendered_blocks ); + $p->next_tag( array( 'class_name' => 'non-interactive-with-directive' ) ); + $value = $p->get_attribute( 'value' ); + $this->assertSame( null, $value ); + } + + public function test_directives_ordering() { $post_content = ''; $rendered_blocks = do_blocks( $post_content ); $p = new WP_HTML_Tag_Processor( $rendered_blocks ); @@ -178,8 +224,6 @@ public function test_directives_ordering() { $value = $p->get_attribute( 'style' ); $this->assertSame( 'display: none;', $value ); - - unregister_block_type( 'test/directives-ordering' ); } public function test_evaluate_function_should_access_state() { From 22f61313a5dcc76d67a8d2b4e9062cd235fddbca Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Mon, 25 Dec 2023 13:25:13 +0100 Subject: [PATCH 41/42] Stop unmarking children of interactive blocks --- .../class-wp-directive-processor.php | 12 ------------ .../interactivity-api/directive-processing.php | 6 ------ 2 files changed, 18 deletions(-) diff --git a/lib/experimental/interactivity-api/class-wp-directive-processor.php b/lib/experimental/interactivity-api/class-wp-directive-processor.php index 9e4faa4e8967f2..bb70068aa9482b 100644 --- a/lib/experimental/interactivity-api/class-wp-directive-processor.php +++ b/lib/experimental/interactivity-api/class-wp-directive-processor.php @@ -74,18 +74,6 @@ public static function mark_children_of_interactive_block( $block ) { self::$children_of_interactive_block[] = md5( serialize( $block ) ); } - /** - * Removes a reference to a direct children of an interactive block. - * - * @param array $block The block to remove. - */ - public static function unmark_children_of_interactive_block( $block ) { - $key = array_search( md5( serialize( $block ) ), self::$children_of_interactive_block, true ); - if ( false !== $key ) { - unset( self::$children_of_interactive_block[ $key ] ); - } - } - /** * Checks if block is marked as children of an interactive block. * diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index b9c646503882aa..8bfe0cee9d45d0 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -99,12 +99,6 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst isset( $block_instance->block_type->supports['interactivity'] ) && $block_instance->block_type->supports['interactivity'] ) { - if ( isset( $block['innerBlocks'] ) ) { - foreach ( $block['innerBlocks'] as $inner_block ) { - WP_Directive_Processor::unmark_children_of_interactive_block( $inner_block ); - } - } - // Wraps the interactive block with a comment delimiter to be able to // process it later. return get_comment_delimited_block_content( From b10672050ee73bd29e32d7f675ab01f26a917fdd Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Thu, 28 Dec 2023 11:31:34 +0100 Subject: [PATCH 42/42] Move directives inside gutenberg_process_interactive_html --- .../directive-processing.php | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/experimental/interactivity-api/directive-processing.php b/lib/experimental/interactivity-api/directive-processing.php index 8bfe0cee9d45d0..075d31d577634c 100644 --- a/lib/experimental/interactivity-api/directive-processing.php +++ b/lib/experimental/interactivity-api/directive-processing.php @@ -36,14 +36,6 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, * @return string Filtered block content. */ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { - static $directives = array( - 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', - 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', - 'data-wp-class' => 'gutenberg_interactivity_process_wp_class', - 'data-wp-style' => 'gutenberg_interactivity_process_wp_style', - 'data-wp-text' => 'gutenberg_interactivity_process_wp_text', - ); - if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) { WP_Directive_Processor::unmark_root_block(); @@ -54,9 +46,9 @@ function gutenberg_process_directives_in_root_blocks( $block_content, $block ) { foreach ( $parsed_blocks as $parsed_block ) { if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $directives ); + $processed_content .= gutenberg_process_interactive_block( $parsed_block, $context ); } elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) { - $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context, $directives ); + $processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context ); } else { $processed_content .= $parsed_block['innerHTML']; } @@ -126,11 +118,10 @@ function gutenberg_mark_block_interactivity( $block_content, $block, $block_inst * * @param array $interactive_block The interactive block to process. * @param WP_Directive_Context $context The context to use when processing. - * @param array $directives The directives to apply. * * @return string The processed HTML. */ -function gutenberg_process_interactive_block( $interactive_block, $context, $directives ) { +function gutenberg_process_interactive_block( $interactive_block, $context ) { $block_index = 0; $content = ''; $interactive_inner_blocks = array(); @@ -146,7 +137,7 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir } } - return gutenberg_process_interactive_html( $content, $context, $directives, $interactive_inner_blocks ); + return gutenberg_process_interactive_html( $content, $context, $interactive_inner_blocks ); } /** @@ -156,11 +147,10 @@ function gutenberg_process_interactive_block( $interactive_block, $context, $dir * * @param array $non_interactive_block The non-interactive block to process. * @param WP_Directive_Context $context The context to use when processing. - * @param array $directives The directives to apply. * * @return string The processed HTML. */ -function gutenberg_process_non_interactive_block( $non_interactive_block, $context, $directives ) { +function gutenberg_process_non_interactive_block( $non_interactive_block, $context ) { $block_index = 0; $content = ''; foreach ( $non_interactive_block['innerContent'] as $inner_content ) { @@ -174,9 +164,9 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte $inner_block = $non_interactive_block['innerBlocks'][ $block_index++ ]; if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_interactive_block( $inner_block, $context, $directives ); + $content .= gutenberg_process_interactive_block( $inner_block, $context ); } elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) { - $content .= gutenberg_process_non_interactive_block( $inner_block, $context, $directives ); + $content .= gutenberg_process_non_interactive_block( $inner_block, $context ); } } } @@ -187,20 +177,25 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte * Processes interactive HTML by applying directives to the HTML tags. * * It uses the WP_Directive_Processor class to parse the HTML and apply the - * directives. The directives are specified in the $directives array and are - * applied to the tags that have the corresponding data attribute. If a tag - * contains a 'WP-INNER-BLOCKS' string and there are inner blocks to process, - * the function processes these inner blocks and replaces the 'WP-INNER-BLOCKS' - * tag in the HTML with those blocks. + * directives. If a tag contains a 'WP-INNER-BLOCKS' string and there are inner + * blocks to process, the function processes these inner blocks and replaces the + * 'WP-INNER-BLOCKS' tag in the HTML with those blocks. * * @param string $html The HTML to process. * @param mixed $context The context to use when processing. - * @param array $directives The directives to apply. * @param array $inner_blocks The inner blocks to process. * * @return string The processed HTML. */ -function gutenberg_process_interactive_html( $html, $context, $directives, $inner_blocks = array() ) { +function gutenberg_process_interactive_html( $html, $context, $inner_blocks = array() ) { + static $directives = array( + 'data-wp-context' => 'gutenberg_interactivity_process_wp_context', + 'data-wp-bind' => 'gutenberg_interactivity_process_wp_bind', + '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( $html ); $prefix = 'data-wp-'; $tag_stack = array(); @@ -212,9 +207,9 @@ function gutenberg_process_interactive_html( $html, $context, $directives, $inne // Processes the inner blocks. if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) { if ( 'core/interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { - $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context ); } elseif ( 'core/non-interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) { - $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_non_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $directives ); + $inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_non_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context ); } } if ( $tags->is_tag_closer() ) {