From 4d0212d132649dcda6a490535570a2afe6ece615 Mon Sep 17 00:00:00 2001 From: Hugo Solar Date: Wed, 27 Aug 2025 12:44:39 -0400 Subject: [PATCH 1/4] fix potential fatal error on Interactivity API processing directives when wrong HTML tags --- .../interactivity-api/class-wp-interactivity-api.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index fdde5d429aa6b..e266bbce4514d 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -439,7 +439,12 @@ private function _process_directives( string $html ) { array_pop( $tag_stack ); } } else { - if ( 0 !== count( $p->get_attribute_names_with_prefix( 'data-wp-each-child' ) ) ) { + $each_child_attrs = $p->get_attribute_names_with_prefix( 'data-wp-each-child' ); + if ( ! is_countable( $each_child_attrs ) ) { + continue; + } + + if ( 0 !== count( $each_child_attrs ) ) { /* * If the tag has a `data-wp-each-child` directive, jump to its closer * tag because those tags have already been processed. From 19f2afbb2535d9c28443c7d6e3376c8a28702acc Mon Sep 17 00:00:00 2001 From: Hugo Solar Date: Fri, 7 Nov 2025 07:28:34 -0300 Subject: [PATCH 2/4] fix null checking and add test --- .../class-wp-interactivity-api.php | 5 +--- .../interactivity-api/wpInteractivityAPI.php | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index e266bbce4514d..10e6009ee60b9 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -440,11 +440,8 @@ private function _process_directives( string $html ) { } } else { $each_child_attrs = $p->get_attribute_names_with_prefix( 'data-wp-each-child' ); - if ( ! is_countable( $each_child_attrs ) ) { - continue; - } - if ( 0 !== count( $each_child_attrs ) ) { + if ( null !== $each_child_attrs && 0 !== count( $each_child_attrs ) ) { /* * If the tag has a `data-wp-each-child` directive, jump to its closer * tag because those tags have already been processed. diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index 1aa9a7238e104..dd05b043c0dac 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -830,9 +830,37 @@ public static function data_html_with_unbalanced_tags() { 'SPAN opener inside' => array( '
Inner content
' ), 'SPAN closer after' => array( '
Inner content
' ), 'SPAN overlapping' => array( '
Inner content
' ), + 'BR self-closing' => array( '
Content

' ), ); } + /** + * Tests that the `process_directives` handles self-closing tags with invalid + * closing tags without causing fatal errors. + * + * @covers ::process_directives + * + * @expectedIncorrectUsage WP_Interactivity_API::_process_directives + */ + public function test_process_directives_handles_self_closing_tags_with_invalid_closers() { + $this->interactivity->state( + 'myPlugin', + array( + 'id' => 'some-id', + ), + ); + + $html = '
Content

'; + + $processed_html = $this->interactivity->process_directives( $html ); + + $this->assertSame( $html, $processed_html ); + + $p = new WP_HTML_Tag_Processor( $processed_html ); + $p->next_tag( 'div' ); + $this->assertNull( $p->get_attribute( 'id' ) ); + } + /** * Tests that the `process_directives` process the HTML outside a SVG tag. * From c4bc65d0d7de6e72ddd88a5a852e24dbcc8ccd4b Mon Sep 17 00:00:00 2001 From: Hugo Solar Date: Fri, 7 Nov 2025 12:22:30 -0300 Subject: [PATCH 3/4] add unit test and fix process directives --- .../class-wp-interactivity-api.php | 5 ++++- .../interactivity-api/wpInteractivityAPI.php | 17 +++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index 10e6009ee60b9..f771912f326e7 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -440,8 +440,11 @@ private function _process_directives( string $html ) { } } else { $each_child_attrs = $p->get_attribute_names_with_prefix( 'data-wp-each-child' ); + if ( null === $each_child_attrs ) { + continue; + } - if ( null !== $each_child_attrs && 0 !== count( $each_child_attrs ) ) { + if ( 0 !== count( $each_child_attrs ) ) { /* * If the tag has a `data-wp-each-child` directive, jump to its closer * tag because those tags have already been processed. diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index dd05b043c0dac..11d7d9ef1f91b 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -830,19 +830,17 @@ public static function data_html_with_unbalanced_tags() { 'SPAN opener inside' => array( '
Inner content
' ), 'SPAN closer after' => array( '
Inner content
' ), 'SPAN overlapping' => array( '
Inner content
' ), - 'BR self-closing' => array( '
Content

' ), ); } /** - * Tests that the `process_directives` handles self-closing tags with invalid - * closing tags without causing fatal errors. + * Tests that the `process_directives` handles self-closing BR tags without + * causing fatal errors and processes directives correctly. * + * @ticket 63891 * @covers ::process_directives - * - * @expectedIncorrectUsage WP_Interactivity_API::_process_directives */ - public function test_process_directives_handles_self_closing_tags_with_invalid_closers() { + public function test_process_directives_handles_br_self_closing_tags_with_invalid_closers() { $this->interactivity->state( 'myPlugin', array( @@ -850,15 +848,14 @@ public function test_process_directives_handles_self_closing_tags_with_invalid_c ), ); - $html = '
Content

'; + $html = '
Content
'; $processed_html = $this->interactivity->process_directives( $html ); - $this->assertSame( $html, $processed_html ); - $p = new WP_HTML_Tag_Processor( $processed_html ); $p->next_tag( 'div' ); - $this->assertNull( $p->get_attribute( 'id' ) ); + + $this->assertSame( 'some-id', $p->get_attribute( 'id' ) ); } /** From 20157c940da4b0628f37a4fa8fbe6ccc315d8e5b Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 7 Nov 2025 21:42:40 +0100 Subject: [PATCH 4/4] Remove trailing comma in function call --- tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index 11d7d9ef1f91b..f9e52f6c8cea4 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -845,7 +845,7 @@ public function test_process_directives_handles_br_self_closing_tags_with_invali 'myPlugin', array( 'id' => 'some-id', - ), + ) ); $html = '
Content
';