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 967d616129647..1ce38432598ed 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -3256,38 +3256,55 @@ private function step_in_body(): bool { /* * > Any other end tag */ + return $this->in_body_any_other_end_tag(); + } - /* - * Find the corresponding tag opener in the stack of open elements, if - * it exists before reaching a special element, which provides a kind - * of boundary in the stack. For example, a `` should not - * close anything beyond its containing `P` or `DIV` element. - */ - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { - if ( 'html' === $node->namespace && $token_name === $node->node_name ) { - break; - } + $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); + // This unnecessary return prevents tools from inaccurately reporting type errors. + return false; + } - if ( self::is_special( $node ) ) { - // This is a parse error, ignore the token. - return $this->step(); - } + /** + * In body insertion mode's "any other end tag" logic can be invoked from different places + * and may require additional processing. + * + * @param $should_step bool Set to `false` to prevent advancing the parser in case the token + * is ignored. + * @return bool Whether an element was found. + */ + private function in_body_any_other_end_tag( bool $should_step = true ): bool { + $token_name = $this->get_token_name(); + + /* + * Find the corresponding tag opener in the stack of open elements, if + * it exists before reaching a special element, which provides a kind + * of boundary in the stack. For example, a `` should not + * close anything beyond its containing `P` or `DIV` element. + */ + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + if ( 'html' === $node->namespace && $token_name === $node->node_name ) { + break; } - $this->generate_implied_end_tags( $token_name ); - if ( $node !== $this->state->stack_of_open_elements->current_node() ) { - // @todo Record parse error: this error doesn't impact parsing. + if ( self::is_special( $node ) ) { + // This is a parse error, ignore the token. + return $should_step ? $this->step() : false; } + } - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); - if ( $node === $item ) { - return true; - } + $this->generate_implied_end_tags( $token_name ); + if ( $node !== $this->state->stack_of_open_elements->current_node() ) { + // @todo Record parse error: this error doesn't impact parsing. + } + + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + if ( $node === $item ) { + return true; } } - $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); + $this->bail( 'Should not have been able to reach end of IN BODY "any other end tag" processing. Check HTML API code.' ); // This unnecessary return prevents tools from inaccurately reporting type errors. return false; } @@ -6265,7 +6282,14 @@ private function run_adoption_agency_algorithm(): void { // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. if ( null === $formatting_element ) { - $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); + /* + * The Adoption Agency Algorithm is not responsible for advancing the parser state, + * so `in_body_any_other_end_tag` is invoked with `$should_step = false` argument. + * This algorithm will return to the context that called it, which is responsible + * for advancing the parser. + */ + $this->in_body_any_other_end_tag( false ); + return; } // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index 5afe37a010a41..c95d1345750c6 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -265,6 +265,47 @@ public function test_unexpected_closing_tags_are_removed() { ); } + /** + * Ensures that unexpected closing formatting tags are ignored. + * + * @ticket 65372 + * + * @dataProvider data_unexpected_closing_formatting_tags + * + * @param string $formatting_tag_name Formatting tag name with no active formatting element. + */ + public function test_unexpected_closing_formatting_tags_are_ignored( string $formatting_tag_name ) { + $this->assertSame( + 'onetwo', + WP_HTML_Processor::normalize( "one{$formatting_tag_name}>two" ), + "Should have ignored unexpected {$formatting_tag_name} closer." + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_unexpected_closing_formatting_tags() { + return array( + 'Unexpected A end tag' => array( 'a' ), + 'Unexpected B end tag' => array( 'b' ), + 'Unexpected BIG end tag' => array( 'big' ), + 'Unexpected CODE end tag' => array( 'code' ), + 'Unexpected EM end tag' => array( 'em' ), + 'Unexpected FONT end tag' => array( 'font' ), + 'Unexpected I end tag' => array( 'i' ), + 'Unexpected NOBR end tag' => array( 'nobr' ), + 'Unexpected S end tag' => array( 's' ), + 'Unexpected SMALL end tag' => array( 'small' ), + 'Unexpected STRIKE end tag' => array( 'strike' ), + 'Unexpected STRONG end tag' => array( 'strong' ), + 'Unexpected TT end tag' => array( 'tt' ), + 'Unexpected U end tag' => array( 'u' ), + ); + } + /** * Ensures that self-closing elements in foreign content retain their self-closing flag. * diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php index b54fc047ab040..085c409e46d0c 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessorBreadcrumbs.php @@ -577,6 +577,7 @@ public static function data_virtual_nodes_breadcrumbs() { 'Implied P tag opener on unmatched closer' => array( '
', 1, 'P', 'open', array( 'HTML', 'BODY', 'P' ) ), 'Implied heading tag closer on heading child' => array( '