Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity API: Fix SSR context included in void tags shouldn't propagate to following elements #6267

Closed
96 changes: 56 additions & 40 deletions src/wp-includes/interactivity-api/class-wp-interactivity-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,34 +290,42 @@ private function process_directives_args( string $html, array &$context_stack, a
}
}
/*
* If the matching opener tag didn't have any directives, it can skip the
* processing.
*/
* If the matching opener tag didn't have any directives, it can skip the
* processing.
*/
if ( 0 === count( $directives_prefixes ) ) {
continue;
}

/*
* Sorts the attributes by the order of the `directives_processor` array
* and checks what directives are present in this element. The processing
* order is reversed for tag closers.
*/
$directives_prefixes = array_intersect(
gziolo marked this conversation as resolved.
Show resolved Hide resolved
$p->is_tag_closer()
? $directive_processor_prefixes_reversed
: $directive_processor_prefixes,
$directives_prefixes
// Directive processing might be different depending on if it is entering the tag or exiting it.
$modes = array(
'enter' => ! $p->is_tag_closer(),
'exit' => $p->is_tag_closer() || ! $p->has_and_visits_its_closer_tag(),
);

// Executes the directive processors present in this element.
foreach ( $directives_prefixes as $directive_prefix ) {
$func = is_array( self::$directive_processors[ $directive_prefix ] )
? self::$directive_processors[ $directive_prefix ]
: array( $this, self::$directive_processors[ $directive_prefix ] );
call_user_func_array(
$func,
array( $p, &$context_stack, &$namespace_stack, &$tag_stack )
foreach ( $modes as $mode => $should_run ) {
if ( ! $should_run ) {
continue;
}

/*
* Sorts the attributes by the order of the `directives_processor` array
* and checks what directives are present in this element.
*/
$existing_directives_prefixes = array_intersect(
'enter' === $mode ? $directive_processor_prefixes : $directive_processor_prefixes_reversed,
$directives_prefixes
);
foreach ( $existing_directives_prefixes as $directive_prefix ) {
$func = is_array( self::$directive_processors[ $directive_prefix ] )
? self::$directive_processors[ $directive_prefix ]
: array( $this, self::$directive_processors[ $directive_prefix ] );

call_user_func_array(
$func,
array( $p, $mode, &$context_stack, &$namespace_stack, &$tag_stack )
);
}
}
}

Expand Down Expand Up @@ -470,12 +478,13 @@ function ( $matches ) {
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
// In closing tags, it removes the last namespace from the stack.
if ( $p->is_tag_closer() ) {
private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
// When exiting tags, it removes the last namespace from the stack.
if ( 'exit' === $mode ) {
array_pop( $namespace_stack );
return;
}
Expand Down Expand Up @@ -514,12 +523,13 @@ private function data_wp_interactive_processor( WP_Interactivity_API_Directives_
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
// In closing tags, it removes the last context from the stack.
if ( $p->is_tag_closer() ) {
private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
// When exiting tags, it removes the last context from the stack.
if ( 'exit' === $mode ) {
array_pop( $context_stack );
return;
}
Expand Down Expand Up @@ -560,11 +570,12 @@ private function data_wp_context_processor( WP_Interactivity_API_Directives_Proc
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
if ( ! $p->is_tag_closer() ) {
private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
if ( 'enter' === $mode ) {
$all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' );

foreach ( $all_bind_directives as $attribute_name ) {
Expand Down Expand Up @@ -604,11 +615,12 @@ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Process
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
if ( ! $p->is_tag_closer() ) {
private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
if ( 'enter' === $mode ) {
$all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' );

foreach ( $all_class_directives as $attribute_name ) {
Expand Down Expand Up @@ -638,11 +650,12 @@ private function data_wp_class_processor( WP_Interactivity_API_Directives_Proces
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
if ( ! $p->is_tag_closer() ) {
private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
if ( 'enter' === $mode ) {
$all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' );

foreach ( $all_style_attributes as $attribute_name ) {
Expand Down Expand Up @@ -730,11 +743,12 @@ private function merge_style_property( string $style_attribute_value, string $st
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
*/
private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
if ( ! $p->is_tag_closer() ) {
private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
if ( 'enter' === $mode ) {
$attribute_value = $p->get_attribute( 'data-wp-text' );
$result = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) );

Expand Down Expand Up @@ -827,10 +841,11 @@ class="screen-reader-text"
*
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
*/
private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p ) {
if ( ! $p->is_tag_closer() && ! $this->has_processed_router_region ) {
private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
if ( 'enter' === $mode && ! $this->has_processed_router_region ) {
$this->has_processed_router_region = true;

// Initialize the `core/router` store.
Expand Down Expand Up @@ -866,12 +881,13 @@ private function data_wp_router_region_processor( WP_Interactivity_API_Directive
* @since 6.5.0
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
* @param array $context_stack The reference to the context stack.
* @param array $namespace_stack The reference to the store namespace stack.
* @param array $tag_stack The reference to the tag stack.
*/
private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack, array &$tag_stack ) {
if ( ! $p->is_tag_closer() && 'TEMPLATE' === $p->get_tag() ) {
private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack, array &$tag_stack ) {
if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) {
$attribute_name = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0];
$extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name );
$item_name = isset( $extracted_suffix[1] ) ? $this->kebab_to_camel_case( $extracted_suffix[1] ) : 'item';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,79 @@ public function test_process_directives_in_tags_that_dont_visit_closer_tag() {
unregister_block_type( 'test/custom-directive-block' );
$this->assertEquals( '1', $processor->get_attribute( 'src' ) );
}

/**
* Tests that context from void tags is not propagated to next tags.
*
* @ticket 60768
*
* @covers wp_interactivity_process_directives_of_interactive_blocks
*/
public function test_process_context_directive_in_void_tags() {
register_block_type(
'test/custom-directive-block',
array(
'render_callback' => function () {
return '<div data-wp-interactive="nameSpace" data-wp-context=\'{"text": "outer"}\'><input id="first-input" data-wp-context=\'{"text": "inner"}\' data-wp-bind--value="context.text" /><input id="second-input" data-wp-bind--value="context.text" /></div>';
gziolo marked this conversation as resolved.
Show resolved Hide resolved
},
'supports' => array(
'interactivity' => true,
),
)
);
$post_content = '<!-- wp:test/custom-directive-block /-->';
$processed_content = do_blocks( $post_content );
$processor = new WP_HTML_Tag_Processor( $processed_content );
$processor->next_tag(
array(
'tag_name' => 'input',
'id' => 'first-input',
)
);
$first_input_value = $processor->get_attribute( 'value' );
$processor->next_tag(
array(
'tag_name' => 'input',
'id' => 'second-input',
)
);
$second_input_value = $processor->get_attribute( 'value' );
unregister_block_type( 'test/custom-directive-block' );
$this->assertEquals( 'inner', $first_input_value );
$this->assertEquals( 'outer', $second_input_value );
}

/**
* Tests that namespace from void tags is not propagated to next tags.
*
* @ticket 60768
*
* @covers wp_interactivity_process_directives_of_interactive_blocks
*/
public function test_process_interactive_directive_in_void_tags() {
wp_interactivity_state(
'void',
array(
'text' => 'void',
)
);
register_block_type(
'test/custom-directive-block',
array(
'render_callback' => function () {
return '<div data-wp-interactive="parent"><img data-wp-interactive="void" /><input data-wp-bind--value="state.text" /></div>';
},
'supports' => array(
'interactivity' => true,
),
)
);
$post_content = '<!-- wp:test/custom-directive-block /-->';
$processed_content = do_blocks( $post_content );
$processor = new WP_HTML_Tag_Processor( $processed_content );
$processor->next_tag( array( 'tag_name' => 'input' ) );
$input_value = $processor->get_attribute( 'value' );
unregister_block_type( 'test/custom-directive-block' );
$this->assertNull( $input_value );
}
}
Loading