Commit d28cce04ad6 for woocommerce

commit d28cce04ad67dc1aced28d5708098076d5bf0c75
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date:   Mon Nov 2 19:38:04 2026 +0500

    [Email Editor] Keep text blocks whose only content is a personalization tag (#70454)

    * [Email Editor] Keep text blocks whose only content is a personalization tag

    * Accept closing brackets in quoted personalization tag attributes

diff --git a/packages/php/email-editor/changelog/fix-70318-text-block-personalization-tag b/packages/php/email-editor/changelog/fix-70318-text-block-personalization-tag
new file mode 100644
index 00000000000..fd04633a8bb
--- /dev/null
+++ b/packages/php/email-editor/changelog/fix-70318-text-block-personalization-tag
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep paragraphs and headings in emails when their only content is a personalization tag.
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
index 710f69845b7..eef2528fc59 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
@@ -27,7 +27,7 @@ class Text extends Abstract_Block_Renderer {
 	 */
 	protected function render_content( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string {
 		// Do not render empty blocks.
-		if ( empty( trim( wp_strip_all_tags( $block_content ) ) ) ) {
+		if ( empty( trim( wp_strip_all_tags( $block_content ) ) ) && ! $this->has_personalization_tag( $block_content ) ) {
 			return '';
 		}

@@ -98,6 +98,21 @@ class Text extends Abstract_Block_Renderer {
 		return Table_Wrapper_Helper::render_table_wrapper( $block_content, $table_attrs, $cell_attrs );
 	}

+	/**
+	 * Checks whether the content contains a personalization tag, e.g. <!--[woocommerce/customer-first-name]-->.
+	 *
+	 * @param string $block_content Block content.
+	 */
+	private function has_personalization_tag( string $block_content ): bool {
+		$html = new \WP_HTML_Tag_Processor( $block_content );
+		while ( $html->next_token() ) {
+			if ( '#comment' === $html->get_token_type() && preg_match( '/^\[[a-zA-Z0-9\-\/]+(?:\s+.*)?\]$/s', trim( $html->get_modifiable_text() ) ) ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
 	/**
 	 * 1) We need to remove padding because we render padding on wrapping table cell
 	 * 2) We also need to replace font-size to avoid clamp() because clamp() is not supported in many email clients.
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Heading_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Heading_Test.php
index b9114b150b6..533f0391346 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Heading_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Heading_Test.php
@@ -70,6 +70,14 @@ class Heading_Test extends \Email_Editor_Integration_Test_Case {
 		$this->rendering_context = new Rendering_Context( $theme_controller->get_theme() );
 	}

+	/**
+	 * Test it renders a heading that only contains a personalization tag
+	 */
+	public function testItRendersHeadingWithOnlyPersonalizationTag(): void {
+		$rendered = $this->heading_renderer->render( '<h1><!--[woocommerce/customer-first-name]--></h1>', $this->parsed_heading, $this->rendering_context );
+		$this->assertStringContainsString( '<!--[woocommerce/customer-first-name]-->', $rendered );
+	}
+
 	/**
 	 * Test it renders content
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
index f48bc024238..10d40a07d69 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
@@ -74,6 +74,34 @@ class Paragraph_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'align="left"', $rendered ); // Check the default align.
 	}

+	/**
+	 * Test it renders a paragraph that only contains a personalization tag
+	 */
+	public function testItRendersParagraphWithOnlyPersonalizationTag(): void {
+		$content  = '<p><!--[woocommerce/customer-first-name]--></p>';
+		$rendered = $this->paragraph_renderer->render( $content, $this->parsed_paragraph, $this->rendering_context );
+		$this->assertStringContainsString( '<!--[woocommerce/customer-first-name]-->', $rendered );
+
+		$content  = "<p>\n\t<!--[woocommerce/customer-first-name default=\"there\"]--> </p>";
+		$rendered = $this->paragraph_renderer->render( $content, $this->parsed_paragraph, $this->rendering_context );
+		$this->assertStringContainsString( '<!--[woocommerce/customer-first-name default="there"]-->', $rendered );
+
+		$content  = '<p><!--[woocommerce/customer-first-name default="[guest]"]--></p>';
+		$rendered = $this->paragraph_renderer->render( $content, $this->parsed_paragraph, $this->rendering_context );
+		$this->assertStringContainsString( '<!--[woocommerce/customer-first-name default="[guest]"]-->', $rendered );
+	}
+
+	/**
+	 * Test it does not render an empty paragraph
+	 */
+	public function testItDoesNotRenderEmptyParagraph(): void {
+		$rendered = $this->paragraph_renderer->render( '<p> </p>', $this->parsed_paragraph, $this->rendering_context );
+		$this->assertStringNotContainsString( 'email-text-block', $rendered );
+
+		$rendered = $this->paragraph_renderer->render( '<p><!-- comment --></p>', $this->parsed_paragraph, $this->rendering_context );
+		$this->assertStringNotContainsString( 'email-text-block', $rendered );
+	}
+
 	/**
 	 * Test it uses RTL default alignment when alignment is absent.
 	 */