diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 498d676f5c20f..c0646ac5d5d80 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -868,6 +868,60 @@ function shortcode_unautop( $text ) { return preg_replace( $pattern, '$1', $text ); } +/** + * Strips unnecessary newlines from HTML content. + * + * @since 7.1.0 + * + * @param string $text The HTML content to strip newlines from. + * @return string The HTML content with unnecessary newlines removed. + */ +function strip_html_newlines( $text ) { + if ( ! str_contains( $text, "\n" ) && ! str_contains( $text, "\r" ) ) { + return $text; + } + + $preserve_newlines_elements = array( 'pre', 'code', 'kbd', 'script', 'style' ); + + $textarr = wp_html_split( $text ); + $changed = false; + $skip = false; + + foreach ( $textarr as $i => $chunk ) { + if ( '' === $chunk ) { + continue; + } + + if ( 0 !== $i % 2 ) { + foreach ( $preserve_newlines_elements as $element ) { + if ( stripos( $chunk, '<' . $element ) === 0 ) { + $skip = true; + break; + } + if ( stripos( $chunk, '' . $element . '>' ) === 0 ) { + $skip = false; + break; + } + } + continue; + } + + if ( ! $skip ) { + $stripped = preg_replace( '/[\n\r]+/', ' ', $chunk ); + if ( $stripped !== $chunk ) { + $textarr[ $i ] = $stripped; + $changed = true; + } + } + } + + if ( $changed ) { + return implode( '', $textarr ); + } + + return $text; +} + /** * Checks to see if a string is utf8 encoded. * diff --git a/tests/phpunit/tests/formatting/stripHtmlNewlines.php b/tests/phpunit/tests/formatting/stripHtmlNewlines.php new file mode 100644 index 0000000000000..aa2ed7608b6e3 --- /dev/null +++ b/tests/phpunit/tests/formatting/stripHtmlNewlines.php @@ -0,0 +1,54 @@ +assertSame( '', strip_html_newlines( '' ), 'Empty string should be returned as-is.' ); + $this->assertSame( '
No newlines here.
', strip_html_newlines( 'No newlines here.
' ), 'Text without newlines should be returned as-is.' ); + $this->assertSame( 'Line one Line two Line three
', strip_html_newlines( "Line one\n\nLine two\r\nLine three
" ), 'Multiple newlines and carriage returns should be collapsed to a single space.' ); + $this->assertSame( + 'This is a paragraph in which the wpautop() wrapping will happen in the middle of an anchor, which is an inline element.
', + strip_html_newlines( "This is a paragraph in which the\nwpautop() wrapping will\nhappen in the middle of an\nanchor, which is an inline element.
" ), + 'Newlines within and around inline elements should be stripped.' + ); + } + + /** + * + * @ticket 5678 + */ + public function test_preserves_newlines_in_preformatted_elements() { + $input = "Normal\ntext
\n\nPreformatted\nlines\n\n
More\ntext
"; + $result = strip_html_newlines( $input ); + + $this->assertStringContainsString( 'Normal text', $result, 'Newlines in normal text should be stripped.' ); + $this->assertStringContainsString( 'More text', $result, 'Newlines in trailing paragraph should be stripped.' ); + $this->assertStringContainsString( "\nPreformatted\nlines\n", $result, 'Newlines insideshould be preserved.' ); + + $preserved_cases = array( + 'code' => "A\nB
x\ny", + 'kbd' => "A\nB
x\ny", + 'script' => "A\nB
", + 'style' => "A\nB
", + ); + + foreach ( $preserved_cases as $tag => $html ) { + $out = strip_html_newlines( $html ); + $this->assertStringContainsString( 'A B', $out, "Text node newline should be stripped around <{$tag}>." ); + $this->assertStringContainsString( "x\ny", $out, "Newline inside <{$tag}> should be preserved." ); + } + } +}