From c814a978d6847a7cdbebe3816edec36750480cd2 Mon Sep 17 00:00:00 2001 From: ArkaPrabhaChowdhury Date: Tue, 14 Jul 2026 12:37:30 +0530 Subject: [PATCH] Formatting: Prevent wpautop() from splitting anchors around block elements --- src/wp-includes/formatting.php | 39 +++++++++++++++++++++- tests/phpunit/tests/formatting/wpAutop.php | 15 +++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index acacb131a627b..a718b4d5f4389 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -444,7 +444,9 @@ function _wptexturize_pushpop_element( $text, &$stack, $disabled_elements ) { * @return string Text which has been converted into correct paragraph tags. */ function wpautop( $text, $br = true ) { - $pre_tags = array(); + $pre_tags = array(); + $has_block_anchor = false; + $block_anchor_placeholder = ''; if ( '' === trim( $text ) ) { return ''; @@ -486,6 +488,29 @@ function wpautop( $text, $br = true ) { $allblocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; + /* + * Anchors can contain block-level elements. Replace their tags with temporary + * block-level placeholders so the paragraph cleanup does not split the anchor. + */ + if ( str_contains( $text, ']*)>(?=\s*<' . $allblocks . '[\s/>])(.*?)()~s', + '<' . $block_anchor_placeholder . '$1>$2', + $text + ); + $has_block_anchor = str_contains( $text, '<' . $block_anchor_placeholder ); + + if ( $has_block_anchor ) { + $allblocks = substr( $allblocks, 0, -1 ) . '|' . $block_anchor_placeholder . ')'; + } + } + // Add a double line break above block-level opening tags. $text = preg_replace( '!(<' . $allblocks . '[\s/>])!', "\n\n$1", $text ); @@ -563,6 +588,13 @@ function wpautop( $text, $br = true ) { $text = preg_replace( '|

]*)>|i', '

', $text ); $text = str_replace( '

', '

', $text ); + // Move paragraphs inside anchors that contain block-level elements. + if ( $has_block_anchor ) { + $text = preg_replace( '|

(<' . $block_anchor_placeholder . '[^>]*>)|', '$1

', $text ); + $text = preg_replace( '|()

|', '

$1', $text ); + $text = preg_replace( '|

\s*

|', '', $text ); + } + // If an opening or closing block element tag is preceded by an opening

tag, remove it. $text = preg_replace( '!

\s*(]*>)!', '$1', $text ); @@ -601,6 +633,11 @@ function wpautop( $text, $br = true ) { $text = str_replace( array( ' ', '' ), "\n", $text ); } + // Restore anchors that contain block-level elements. + if ( $has_block_anchor ) { + $text = str_replace( array( '<' . $block_anchor_placeholder, '' ), array( '' ), $text ); + } + return $text; } diff --git a/tests/phpunit/tests/formatting/wpAutop.php b/tests/phpunit/tests/formatting/wpAutop.php index ee4d90645d09c..c7ebe1fbf9907 100644 --- a/tests/phpunit/tests/formatting/wpAutop.php +++ b/tests/phpunit/tests/formatting/wpAutop.php @@ -490,6 +490,21 @@ public function test_that_wpautop_treats_inline_elements_as_inline() { $this->assertSame( $expected, trim( wpautop( $content ) ) ); } + /** + * @ticket 40202 + */ + public function test_that_wpautop_does_not_split_anchors_containing_block_elements() { + $content = '

Text
'; + $expected = "\n
Text
\n
"; + + $this->assertSame( $expected, trim( wpautop( $content ) ) ); + + $content = '
First
Second
'; + $expected = "\n
First
\n
Second
\n
"; + + $this->assertSame( $expected, trim( wpautop( $content ) ) ); + } + /** * Do not allow newlines within HTML elements to become mangled. *