diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
index 24201e82fd..3b2dcb1237 100644
--- a/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
+++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
@@ -180,6 +180,39 @@ final class WP_Interactivity_API_Directives_Processor extends WP_HTML_Tag_Proces
return array( $opener_tag, $closer_tag );
}
+ /**
+ * Skips processing the content between tags.
+ *
+ * It positions the cursor in the closer tag of the foreign element, if it
+ * exists.
+ *
+ * This function is intended to skip processing SVG and MathML inner content
+ * instead of bailing out the whole processing.
+ *
+ * @since 6.5.0
+ *
+ * @access private
+ *
+ * @return bool Whether the foreign content was successfully skipped.
+ */
+ public function skip_to_tag_closer(): bool {
+ $depth = 1;
+ $tag_name = $this->get_tag();
+ while ( $depth > 0 && $this->next_tag(
+ array(
+ 'tag_name' => $tag_name,
+ 'tag_closers' => 'visit',
+ )
+ ) ) {
+ if ( $this->has_self_closing_flag() ) {
+ continue;
+ }
+ $depth += $this->is_tag_closer() ? -1 : 1;
+ }
+
+ return 0 === $depth;
+ }
+
/**
* Finds the matching closing tag for an opening tag.
*
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 536b6623e2..9e5b1be1fa 100644
--- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php
+++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php
@@ -235,9 +235,14 @@ final class WP_Interactivity_API {
while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $p->get_tag();
+ /*
+ * Directives inside SVG and MATH tags are not processed,
+ * as they are not compatible with the Tag Processor yet.
+ * We still process the rest of the HTML.
+ */
if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
- $unbalanced = true;
- break;
+ $p->skip_to_tag_closer();
+ continue;
}
if ( $p->is_tag_closer() ) {
diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php
index 5e8298a77e..25b8379acd 100644
--- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php
+++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php
@@ -11,7 +11,7 @@
*
* @coversDefaultClass WP_Interactivity_API
*/
-class Tests_WP_Interactivity_API extends WP_UnitTestCase {
+class Tests_Interactivity_API_WpInteractivityAPI extends WP_UnitTestCase {
/**
* Instance of WP_Interactivity_API.
*
@@ -508,50 +508,131 @@ class Tests_WP_Interactivity_API extends WP_UnitTestCase {
}
/**
- * Tests that the `process_directives` returns the same HTML if it finds an
- * SVG tag.
+ * Tests that the `process_directives` process the HTML outside a SVG tag.
*
- * @ticket 60356
+ * @ticket 60517
*
* @covers ::process_directives
*/
- public function test_process_directives_doesnt_change_html_if_contains_svgs() {
- $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
+ public function test_process_directives_changes_html_if_contains_svgs() {
+ $this->interactivity->state(
+ 'myPlugin',
+ array(
+ 'id' => 'some-id',
+ 'width' => '100',
+ )
+ );
$html = '
-
-
+
+
+
+
';
$processed_html = $this->interactivity->process_directives( $html );
$p = new WP_HTML_Tag_Processor( $processed_html );
- $p->next_tag();
+ $p->next_tag( 'svg' );
+ $this->assertNull( $p->get_attribute( 'width' ) );
+ $p->next_tag( 'div' );
+ $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
+ $p->next_tag( 'div' );
+ $this->assertEquals( '100', $p->get_attribute( 'id' ) );
+ }
+
+ /**
+ * Tests that the `process_directives` does not process the HTML
+ * inside SVG tags.
+ *
+ * @ticket 60517
+ *
+ * @covers ::process_directives
+ */
+ public function test_process_directives_does_not_change_inner_html_in_svgs() {
+ $this->interactivity->state(
+ 'myPlugin',
+ array(
+ 'id' => 'some-id',
+ )
+ );
+ $html = '
+
+
+
+
+
+
+ ';
+ $processed_html = $this->interactivity->process_directives( $html );
+ $p = new WP_HTML_Tag_Processor( $processed_html );
+ $p->next_tag( 'div' );
$this->assertNull( $p->get_attribute( 'id' ) );
}
/**
- * Tests that the `process_directives` returns the same HTML if it finds an
+ * Tests that the `process_directives` process the HTML outside the
* MathML tag.
*
- * @ticket 60356
+ * @ticket 60517
*
* @covers ::process_directives
*/
- public function test_process_directives_doesnt_change_html_if_contains_math() {
- $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
+ public function test_process_directives_change_html_if_contains_math() {
+ $this->interactivity->state(
+ 'myPlugin',
+ array(
+ 'id' => 'some-id',
+ 'math' => 'ml-id',
+ )
+ );
$html = '
-
-
+
+
';
$processed_html = $this->interactivity->process_directives( $html );
$p = new WP_HTML_Tag_Processor( $processed_html );
- $p->next_tag();
+ $p->next_tag( 'math' );
+ $this->assertNull( $p->get_attribute( 'id' ) );
+ $p->next_tag( 'div' );
+ $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
+ }
+
+ /**
+ * Tests that the `process_directives` does not process the HTML
+ * inside MathML tags.
+ *
+ * @ticket 60517
+ *
+ * @covers ::process_directives
+ */
+ public function test_process_directives_does_not_change_inner_html_in_math() {
+ $this->interactivity->state(
+ 'myPlugin',
+ array(
+ 'id' => 'some-id',
+ )
+ );
+ $html = '
+
+
+
+ x
+ =
+ 1
+
+
+ ';
+ $processed_html = $this->interactivity->process_directives( $html );
+ $p = new WP_HTML_Tag_Processor( $processed_html );
+ $p->next_tag( 'div' );
$this->assertNull( $p->get_attribute( 'id' ) );
}
diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php
index bf2225597c..e7395b5f38 100644
--- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php
+++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php
@@ -11,7 +11,7 @@
*
* @coversDefaultClass WP_Interactivity_API_Directives_Processor
*/
-class Tests_WP_Interactivity_API_Directives_Processor extends WP_UnitTestCase {
+class Tests_Interactivity_API_WpInteractivityAPIDirectivesProcessor extends WP_UnitTestCase {
/**
* Tests the `get_content_between_balanced_template_tags` method on template
* tags.
@@ -778,4 +778,51 @@ class Tests_WP_Interactivity_API_Directives_Processor extends WP_UnitTestCase {
$p->next_tag( array( 'tag_closers' => 'visit' ) );
$this->assertFalse( $p->next_balanced_tag_closer_tag() );
}
+
+ /**
+ * Tests that skip_to_tag_closer skips to the next tag,
+ * independant of the content.
+ *
+ * @ticket 60517
+ *
+ * @covers ::skip_to_tag_closer
+ */
+ public function test_skip_to_tag_closer() {
+ $content = '
Not closed
';
+ $p = new WP_Interactivity_API_Directives_Processor( $content );
+ $p->next_tag();
+ $this->assertTrue( $p->skip_to_tag_closer() );
+ $this->assertTrue( $p->is_tag_closer() );
+ $this->assertEquals( 'DIV', $p->get_tag() );
+ }
+
+ /**
+ * Tests that skip_to_tag_closer does not skip to the
+ * next tag if there is no closing tag.
+ *
+ * @ticket 60517
+ *
+ * @covers ::skip_to_tag_closer
+ */
+ public function test_skip_to_tag_closer_bails_not_closed() {
+ $content = '
Not closed parent';
+ $p = new WP_Interactivity_API_Directives_Processor( $content );
+ $p->next_tag();
+ $this->assertFalse( $p->skip_to_tag_closer() );
+ }
+
+ /**
+ * Tests that skip_to_tag_closer does not skip to the next
+ * tag if the closing tag is different from the current tag.
+ *
+ * @ticket 60517
+ *
+ * @covers ::skip_to_tag_closer
+ */
+ public function test_skip_to_tag_closer_bails_different_tags() {
+ $content = '