From 4b3201c7036d09fdf5adae99c8fe48b2c6c0b45b Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 5 Dec 2025 11:54:40 -0600 Subject: [PATCH 1/2] Preserve whitespace inside pre elements when federating content --- includes/class-sanitize.php | 21 ++++++++ includes/class-shortcodes.php | 2 +- includes/transformer/class-post.php | 4 +- .../includes/transformer/class-test-post.php | 48 +++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) 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)[^>]*?>.*?@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 6ff5aa3db0..92323ad391 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; @@ -539,8 +540,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 6ea906bc77..fccaa7929c 100644 --- a/tests/phpunit/tests/includes/transformer/class-test-post.php +++ b/tests/phpunit/tests/includes/transformer/class-test-post.php @@ -1106,6 +1106,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\n
line1\nline2\nline3
\n\n

Another 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. * From 833b95333199f99e47149e4fe4be2d633c1a4b96 Mon Sep 17 00:00:00 2001 From: Automattic Bot Date: Fri, 5 Dec 2025 18:56:02 +0100 Subject: [PATCH 2/2] Add changelog --- .github/changelog/2595-from-description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/changelog/2595-from-description 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.