diff --git a/.github/changelog/2595-from-description b/.github/changelog/2595-from-description new file mode 100644 index 0000000000..9906da334c --- /dev/null +++ b/.github/changelog/2595-from-description @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Preserve newlines inside pre elements when federating content. diff --git a/includes/class-sanitize.php b/includes/class-sanitize.php index 092a2b7bf3..6f131c1b87 100644 --- a/includes/class-sanitize.php +++ b/includes/class-sanitize.php @@ -201,4 +201,25 @@ public static function content( $content ) { return $content; } + + /** + * Strip newlines, carriage returns, and tabs from content while preserving them inside pre elements. + * + * Code blocks require whitespace to be preserved for proper formatting. + * + * @param string $content The content to process. + * + * @return string The trimmed content with whitespace stripped except inside pre elements. + */ + public static function strip_whitespace( $content ) { + return \trim( + \preg_replace_callback( + '/(
]*>.*?<\/pre>)|[\n\r\t]+/is',
+ static function ( $matches ) {
+ return $matches[1] ?? '';
+ },
+ $content
+ )
+ );
+ }
}
diff --git a/includes/class-shortcodes.php b/includes/class-shortcodes.php
index 1554fa646d..609752a306 100644
--- a/includes/class-shortcodes.php
+++ b/includes/class-shortcodes.php
@@ -191,7 +191,7 @@ public static function content( $attributes, $content, $tag ) {
// Replace script and style elements.
$content = \preg_replace( '@<(script|style)[^>]*?>.*?\\1>@si', '', $content );
$content = \strip_shortcodes( $content );
- $content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
+ $content = Sanitize::strip_whitespace( $content );
add_shortcode( 'ap_content', array( 'Activitypub\Shortcodes', 'content' ) );
diff --git a/includes/transformer/class-post.php b/includes/transformer/class-post.php
index a17b618733..c6f3f65d9a 100644
--- a/includes/transformer/class-post.php
+++ b/includes/transformer/class-post.php
@@ -12,6 +12,7 @@
use Activitypub\Collection\Interactions;
use Activitypub\Collection\Replies;
use Activitypub\Model\Blog;
+use Activitypub\Sanitize;
use Activitypub\Shortcodes;
use function Activitypub\esc_hashtag;
@@ -604,8 +605,7 @@ protected function get_content() {
\wp_reset_postdata();
$content = \wpautop( $content );
- $content = \preg_replace( '/[\n\r\t]/', '', $content );
- $content = \trim( $content );
+ $content = Sanitize::strip_whitespace( $content );
// Don't need these anymore, should never appear in a post.
Shortcodes::unregister();
diff --git a/tests/phpunit/tests/includes/transformer/class-test-post.php b/tests/phpunit/tests/includes/transformer/class-test-post.php
index 31aa150a31..6a131c8e21 100644
--- a/tests/phpunit/tests/includes/transformer/class-test-post.php
+++ b/tests/phpunit/tests/includes/transformer/class-test-post.php
@@ -1108,6 +1108,54 @@ public function test_get_post_content_template_with_scenarios( $post_data, $expe
$this->assertSame( $expected_template, $template, $description );
}
+ /**
+ * Test that code blocks preserve newlines when content is transformed.
+ *
+ * @covers ::get_content
+ */
+ public function test_code_blocks_preserve_newlines() {
+ $code_content = "function test() {\n\treturn true;\n}";
+ $post = self::factory()->post->create_and_get(
+ array(
+ 'post_title' => 'Code Example',
+ 'post_content' => '' . $code_content . '
',
+ 'post_status' => 'publish',
+ )
+ );
+
+ $object = Post::transform( $post )->to_object();
+ $content = $object->get_content();
+
+ // The pre block content should preserve newlines and tabs.
+ $this->assertStringContainsString( '', $content, 'Content should contain pre tag' );
+ $this->assertStringContainsString( "\n", $content, 'Content should preserve newlines inside pre blocks' );
+ $this->assertStringContainsString( "\t", $content, 'Content should preserve tabs inside pre blocks' );
+ }
+
+ /**
+ * Test that regular content has whitespace stripped while code blocks are preserved.
+ *
+ * @covers ::get_content
+ */
+ public function test_mixed_content_preserves_code_blocks() {
+ $post = self::factory()->post->create_and_get(
+ array(
+ 'post_title' => 'Mixed Content',
+ 'post_content' => "Regular paragraph
\n\nline1\nline2\nline3
\n\nAnother paragraph
",
+ 'post_status' => 'publish',
+ )
+ );
+
+ $object = Post::transform( $post )->to_object();
+ $content = $object->get_content();
+
+ // Pre block should preserve newlines.
+ $this->assertStringContainsString( "line1\nline2\nline3", $content, 'Pre block should preserve newlines' );
+
+ // Regular paragraphs should be joined without extra newlines.
+ $this->assertStringNotContainsString( "\n\n", $content, 'Paragraphs should not have newlines between them' );
+ }
+
/**
* Data provider for get_post_content_template tests with various scenarios.
*