diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 55d3fc5fe68e5..8a6638d7d6e6b 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -1511,9 +1511,6 @@ public function serialize_token(): string { case 'IFRAME': case 'NOEMBED': case 'NOFRAMES': - $text = ''; - break; - case 'SCRIPT': case 'STYLE': case 'XMP': 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 8e125ba461890..ace3e14bea565 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 @@ -298,7 +298,7 @@ * closing the SCRIPT from inside a JavaScript string. E.g. `console.log( '' )`. * - `TITLE` and `TEXTAREA` whose contents are treated as plaintext and then any * character references are decoded. E.g. `1 < 2 < 3` becomes `1 < 2 < 3`. - * - `IFRAME`, `NOEMBED`, `NOFRAMES`, `STYLE` whose contents are treated as + * - `IFRAME`, `NOEMBED`, `NOFRAMES`, `STYLE`, `XMP` whose contents are treated as * raw plaintext and left as-is. E.g. `1 < 2 < 3` remains `1 < 2 < 3`. * * #### Other tokens with modifiable text. @@ -4075,6 +4075,32 @@ public function set_modifiable_text( string $plaintext_content ): bool { ); return true; + case 'IFRAME': + case 'NOEMBED': + case 'NOFRAMES': + case 'XMP': + $tag_name = $this->get_tag(); + if ( false !== stripos( $plaintext_content, "lexical_updates['modifiable text'] = new WP_HTML_Text_Replacement( + $this->text_starts_at, + $this->text_length, + $plaintext_content + ); + + return true; + case 'STYLE': $plaintext_content = preg_replace_callback( '~style)~i', @@ -4132,7 +4158,7 @@ static function ( $tag_match ) { _doing_it_wrong( __METHOD__, - __( 'Only the SCRIPT, STYLE, TEXTAREA, and TITLE tags support setting modifiable text.' ), + __( 'This tag does not support setting modifiable text.' ), '7.1.0' ); return false; diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index 81149dd5744ef..587d0a4d4bd8e 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -270,22 +270,6 @@ public function test_style_contents_are_not_escaped() { ); } - /** - * XMP contents are parsed using the generic raw text element parsing algorithm. - * Their contents should not be escaped with HTML character references on normalization. - * - * @ticket 65372 - */ - public function test_xmp_contents_are_not_escaped() { - $normalized = WP_HTML_Processor::normalize( " < > & \" ' \x00 " ); - - $this->assertSame( - " < > & \" ' \u{FFFD} ", - $normalized, - 'Should have preserved text inside an XMP element, except for replacing NULL bytes.' - ); - } - public function test_unexpected_closing_tags_are_removed() { $this->assertSame( WP_HTML_Processor::normalize( 'onetwothree' ), @@ -481,9 +465,9 @@ public function test_replaces_null_bytes_appropriately( string $html_with_nulls, /** * Data provider. * - * @return array[] + * @return array */ - public static function data_tokens_with_null_bytes() { + public static function data_tokens_with_null_bytes(): array { return array( 'Tag name' => array( "", "" ), 'Attribute name' => array( "", "" ), @@ -492,11 +476,45 @@ public static function data_tokens_with_null_bytes() { 'Foreign content text' => array( "one\x00two", "one\u{FFFD}two" ), 'SCRIPT content' => array( "", "" ), 'STYLE content' => array( "", "" ), + 'IFRAME content' => array( "", "" ), + 'NOEMBED content' => array( "a\x00b", "a\u{FFFD}b" ), + 'NOFRAMES content' => array( "a\x00b", "a\u{FFFD}b" ), 'XMP content' => array( "a\x00b", "a\u{FFFD}b" ), 'Comment text' => array( "", "" ), ); } + /** + * Ensures that contents of rawtext elements are preserved when serializing. + * + * @ticket 65372 + * + * @dataProvider data_rawtext_elements_with_html_syntax_character_contents + * + * @param string $html Normalized HTML containing a rawtext element with contents. + */ + public function test_rawtext_element_contents_are_preserved_when_normalizing( string $html ): void { + $this->assertSame( + $html, + WP_HTML_Processor::normalize( $html ), + 'Should have preserved the rawtext element contents.' + ); + } + + /** + * Data provider. + * + * @return array + */ + public static function data_rawtext_elements_with_html_syntax_character_contents(): array { + return array( + 'IFRAME' => array( 'beforeafter' ), + 'NOEMBED' => array( 'before < > &amp; " \' after' ), + 'NOFRAMES' => array( 'before < > &amp; " \' after' ), + 'XMP' => array( 'before < > &amp; " \' after' ), + ); + } + /** * @ticket 62396 * diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php index 35b5ed88baec0..589318daf3a70 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php @@ -434,6 +434,10 @@ public static function data_tokens_with_basic_modifiable_text_updates() { 'PI node (no separator)' => array( '', 1, '{"just": "kidding"}', '' ), 'Text node (end)' => array( 'of a dog', 2, 'of a cat', 'of a cat' ), 'Encoded text node' => array( '
birds and dogs
', 2, ' & ', '
<birds> & <dogs>
' ), + 'IFRAME tag' => array( 'beforeafter', 2, '

raw & text

', 'beforeafter' ), + 'NOEMBED tag' => array( 'beforeold contentafter', 2, '

raw & text

', 'before<p>raw & text</p>after' ), + 'NOFRAMES tag' => array( 'beforeold contentafter', 2, '

raw & text

', 'before<p>raw & text</p>after' ), + 'XMP tag' => array( 'beforeold contentafter', 2, '

raw & text

', 'before<p>raw & text</p>after' ), 'SCRIPT tag' => array( 'beforeafter', 2, 'const img = " &
";', 'beforeafter' ), 'STYLE tag' => array( '', 1, 'p::before { content: " & "; }', '' ), 'TEXTAREA tag' => array( 'ab', 2, "so it ", "ab" ), @@ -624,6 +628,10 @@ public static function data_unallowed_modifiable_text_updates() { return array( 'Comment with -->' => array( '', 'Comments end in -->' ), 'Comment with --!>' => array( '', 'Invalid but legitimate comments end in --!>' ), + 'IFRAME with ' => array( '', 'Just a ' ), + 'NOEMBED with ' => array( 'Replace me', 'Just a ' ), + 'NOFRAMES with ' => array( 'Replace me', 'beforeafter' ), + 'XMP with ' => array( 'Replace me', 'Just a ' ), 'PI with >' => array( '', 'Processing instructions end at the first >' ), 'PI with leading space' => array( '', ' leading whitespace is skipped after the target' ), 'PI with leading tab' => array( '', "\tleading whitespace is skipped after the target" ),