diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 0c153c4c34d89..9289d5d27f880 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -3817,7 +3817,14 @@ public function set_modifiable_text( string $plaintext_content ): bool { return true; } - if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { + /* + * The rest of this function handles modifiable text for special "atomic" HTML elements. + * Only tags in the HTML namespace should be processed. + */ + if ( + self::STATE_MATCHED_TAG !== $this->parser_state || + 'html' !== $this->get_namespace() + ) { return false; } diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorModifiableText.php b/tests/phpunit/tests/html-api/wpHtmlProcessorModifiableText.php index 08a4514fa14fd..5d093ae05dd07 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessorModifiableText.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessorModifiableText.php @@ -24,7 +24,7 @@ class Tests_HtmlApi_WpHtmlProcessorModifiableText extends WP_UnitTestCase { * @param string $set_text Text to set. * @param string $expected_html Expected HTML output. */ - public function test_modifiable_text_special_textarea( string $set_text, string $expected_html ) { + public function test_modifiable_text_special_textarea( string $set_text, string $expected_html ): void { $processor = WP_HTML_Processor::create_fragment( '' ); $processor->next_token(); $processor->set_modifiable_text( $set_text ); @@ -50,9 +50,9 @@ public function test_modifiable_text_special_textarea( string $set_text, string /** * Data provider. * - * @return array[] + * @return array */ - public static function data_modifiable_text_special_textarea() { + public static function data_modifiable_text_special_textarea(): array { return array( 'Leading newline' => array( "\nAFTER NEWLINE", @@ -68,4 +68,59 @@ public static function data_modifiable_text_special_textarea() { ), ); } + + /** + * Ensures that `set_modifiable_text()` returns false for elements that are not special "atomic" elements. + * + * This includes atomic-like foreign elements (`
`). + * + * @ticket 64751 + * @dataProvider data_set_modifiable_fails_non_atomic_tags + */ + public function test_set_modifiable_fails_non_atomic_tags( + string $html, + string $target_tag + ): void { + $processor = WP_HTML_Processor::create_fragment( $html ); + $this->assertNotNull( $processor, 'Failed to create a processor.' ); + $this->assertTrue( $processor->next_tag( $target_tag ), 'Failed to find target tag.' ); + $this->assertFalse( + $processor->set_modifiable_text( 'test' ), + "set_modifiable_text() should return false on {$processor->get_namespace()}:{$processor->get_qualified_tag_name()}." + ); + $this->assertSame( + $html, + $processor->get_updated_html(), + 'HTML should be unchanged after rejected set_modifiable_text().' + ); + } + + /** + * Data provider. + * + * @return array + */ + public static function data_set_modifiable_fails_non_atomic_tags(): array { + return array( + // Plain HTML tags. + 'html DIV' => array( '
', 'DIV' ), + + // Foreign elements with non-atomic tags. + 'svg PATH' => array( '', 'PATH' ), + 'svg PATH (self-closing)' => array( '', 'PATH' ), + 'math MTEXT' => array( '', 'MTEXT' ), + 'math MSPACE (self-closing)' => array( '', 'MSPACE' ), + + // Foreign elements with atomic-like tags. + 'svg TEXTAREA' => array( '', 'TEXTAREA' ), + 'svg TITLE' => array( '', 'TITLE' ), + 'svg STYLE' => array( '', 'STYLE' ), + 'svg SCRIPT' => array( '', 'SCRIPT' ), + 'math TEXTAREA' => array( '', 'TEXTAREA' ), + 'math TITLE' => array( '', 'TITLE' ), + 'math STYLE' => array( '', 'STYLE' ), + 'math SCRIPT' => array( '', 'SCRIPT' ), + ); + } } diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php index 0a8ecc0976d66..f43d1fffaad0e 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php @@ -644,7 +644,7 @@ public function test_json_auto_escaping() { * * @ticket 64609 */ - public function test_modifiable_text_special_textarea() { + public function test_modifiable_text_special_textarea(): void { $processor = new WP_HTML_Tag_Processor( '' ); $processor->next_token(); $processor->set_modifiable_text( "\nAFTER NEWLINE" ); @@ -664,4 +664,60 @@ public function test_modifiable_text_special_textarea() { 'Should have preserved the leading newline in the content.' ); } + + /** + * Ensures that `set_modifiable_text()` returns false for elements that are + * not special "atomic" elements. + * + * This includes atomic-like foreign elements as well as non-atomic foreign elements. + * + * @ticket 64751 + * @dataProvider data_set_modifiable_fails_non_atomic_tags + */ + public function test_set_modifiable_fails_non_atomic_tags( + string $html, + string $parsing_namespace, + string $target_tag + ): void { + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->change_parsing_namespace( $parsing_namespace ); + $this->assertTrue( $processor->next_tag( $target_tag ), 'Failed to find target tag.' ); + $this->assertFalse( + $processor->set_modifiable_text( 'test' ), + "set_modifiable_text() should return false for {$parsing_namespace}:{$target_tag}." + ); + $this->assertSame( + $html, + $processor->get_updated_html(), + 'HTML should be unchanged after rejected set_modifiable_text().' + ); + } + + /** + * Data provider. + * + * @return array + */ + public static function data_set_modifiable_fails_non_atomic_tags(): array { + return array( + // Plain HTML tags. + 'html DIV' => array( '
', 'html', 'DIV' ), + + // Foreign elements with non-atomic tags. + 'svg PATH' => array( '', 'svg', 'PATH' ), + 'svg PATH (self-closing)' => array( '', 'svg', 'PATH' ), + 'math MTEXT' => array( '', 'math', 'MTEXT' ), + 'math MSPACE (self-closing)' => array( '', 'math', 'MSPACE' ), + + // Foreign elements with atomic-like tags. + 'svg TEXTAREA' => array( '', 'svg', 'TEXTAREA' ), + 'svg TITLE' => array( '', 'svg', 'TITLE' ), + 'svg STYLE' => array( '', 'svg', 'STYLE' ), + 'svg SCRIPT' => array( '', 'svg', 'SCRIPT' ), + 'math TEXTAREA' => array( '', 'math', 'TEXTAREA' ), + 'math TITLE' => array( '', 'math', 'TITLE' ), + 'math STYLE' => array( '', 'math', 'STYLE' ), + 'math SCRIPT' => array( '', 'math', 'SCRIPT' ), + ); + } }