Commit be13dcdb4f for woocommerce

commit be13dcdb4f7c75d124ea76b062c3bf6d0eecc89a
Author: Rostislav Wolný <1082140+costasovo@users.noreply.github.com>
Date:   Fri Jan 16 16:37:46 2026 +0100

    Disable email editor renderer margin support (#62794)

    * Remove margin from generate styles

    * Remove margin inlined in text blocks styles

    * Add docs for filter allowing disabling styles props

    * Add tests checking margins are not rendered

    * Fix error log being printed when running tests

    The error is expected because we test an error state,
    but we can hide it from test output to prevent confusion.

    * Add change log

diff --git a/packages/php/email-editor/README.md b/packages/php/email-editor/README.md
index 4f487c4895..738ddfe0b9 100644
--- a/packages/php/email-editor/README.md
+++ b/packages/php/email-editor/README.md
@@ -99,12 +99,13 @@ We may add, update and delete any of them.
 | `woocommerce_email_editor_post_sent_status_args`                   | `Array` `sent` post status args                                  | `Array` register_post_status args                            | Allows update of the argument for the sent post status                                                                                                                 |
 | `woocommerce_email_blocks_renderer_parsed_blocks`                  | `Array` Parsed blocks data                                       | `Array` Parsed blocks data                                   | You can modify the parsed blocks before they are processed by email renderer.                                                                                          |
 | `woocommerce_email_editor_rendering_email_context`                 | `Array` $email_context                                           | `Array` $email_context                                       | Applied during email rendering to provide context data (e.g., `recipient_email`, `user_id`, `order_id`) to block renderers.                                            |
-| `woocommerce_email_editor_send_preview_email_rendered_data`        | `string` $data Rendered email, `WP_Post` $post                                     | `string` Rendered email                                      | Allows modifying the rendered email when displaying or sending it in preview                                                                                           |
+| `woocommerce_email_editor_send_preview_email_rendered_data`        | `string` $data Rendered email, `WP_Post` $post                   | `string` Rendered email                                      | Allows modifying the rendered email when displaying or sending it in preview                                                                                           |
 | `woocommerce_email_editor_send_preview_email_personalizer_context` | `string` $content_styles, `WP_Post` $post` $personalizer_context | `Array` Personalizer context data                            | Allows modifying the personalizer context data for the send preview email function                                                                                     |
 | `woocommerce_email_editor_synced_site_styles`                      | `Array` $synced_data, `Array` $site_data                         | `Array` Modified synced data                                 | Used to filter the synced site style data before applying to email theme.                                                                                              |
 | `woocommerce_email_editor_site_style_sync_enabled`                 | `bool` $enabled                                                  | `bool`                                                       | Use to control whether site style sync functionality is enabled or disabled. Returning `false` will disable site theme sync.                                           |
 | `woocommerce_email_editor_allowed_iframe_style_handles`            | `Array` $allowed_iframe_style_handles                            | `Array` $allowed_iframe_style_handles                        | Filter the list of allowed stylesheet handles in the editor iframe.                                                                                                    |
 | `woocommerce_email_editor_script_localization_data`                | `Array` $localization_data                                       | `Array` $localization_data                                   | Use to modify inlined JavaScript variables used by Email Editor client.                                                                                                |
+| `woocommerce_email_editor_styles_unsupported_props`                | `Array` $unsupported_props                                       | `Array` $unsupported_props                                   | Filter the list of unsupported style properties (as nested key paths) that will be removed before block styles are converted to CSS.                                   |

 ## Logging

diff --git a/packages/php/email-editor/changelog/fix-disable-email-editor-margin-support b/packages/php/email-editor/changelog/fix-disable-email-editor-margin-support
new file mode 100644
index 0000000000..dc8405d421
--- /dev/null
+++ b/packages/php/email-editor/changelog/fix-disable-email-editor-margin-support
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Prevent rendering of CSS margins in email HTML output
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 9e5a7b59f6..c2c98c03c4 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
@@ -116,15 +116,18 @@ class Text extends Abstract_Block_Renderer {
 			$element_style_value = $html->get_attribute( 'style' );
 			$element_style       = isset( $element_style_value ) ? strval( $element_style_value ) : '';
 			// Padding may contain value like 10px or variable like var(--spacing-10).
-			$element_style = preg_replace( '/padding[^:]*:.?[0-9a-z-()]+;?/', '', $element_style );
+			$element_style = (string) preg_replace( '/padding[^:]*:.?[0-9a-z-()]+;?/', '', $element_style );
+
+			// Margin is not supported in email renderer, so we need to remove it.
+			$element_style = (string) preg_replace( '/margin[^:]*:.?[0-9a-z-()]+;?/', '', $element_style );

 			// Remove border styles. We apply border styles on the wrapping table cell.
-			$element_style = preg_replace( '/border[^:]*:.?[0-9a-z-()#]+;?/', '', strval( $element_style ) );
+			$element_style = (string) preg_replace( '/border[^:]*:.?[0-9a-z-()#]+;?/', '', $element_style );

 			// We define the font-size on the wrapper element, but we need to keep font-size definition here
 			// to prevent CSS Inliner from adding a default value and overriding the value set by user, which is on the wrapper element.
 			// The value provided by WP uses clamp() function which is not supported in many email clients.
-			$element_style = preg_replace( '/font-size:[^;]+;?/', 'font-size: inherit;', strval( $element_style ) );
+			$element_style = (string) preg_replace( '/font-size:[^;]+;?/', 'font-size: inherit;', $element_style );
 			/** @var string $element_style */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
 			$html->set_attribute( 'style', esc_attr( $element_style ) );
 			$block_content = $html->get_updated_html();
diff --git a/packages/php/email-editor/src/Integrations/Utils/class-styles-helper.php b/packages/php/email-editor/src/Integrations/Utils/class-styles-helper.php
index d43e25decc..40e7c5c600 100644
--- a/packages/php/email-editor/src/Integrations/Utils/class-styles-helper.php
+++ b/packages/php/email-editor/src/Integrations/Utils/class-styles-helper.php
@@ -120,6 +120,32 @@ class Styles_Helper {
 	 * }
 	 */
 	public static function get_styles_from_block( array $block_styles, $skip_convert_vars = false ) {
+		$unsupported_props = array(
+			'margin' => array( 'spacing', 'margin' ),
+		);
+		$unsupported_props = apply_filters( 'woocommerce_email_editor_styles_unsupported_props', $unsupported_props );
+		foreach ( $unsupported_props as $path ) {
+			if ( ! is_array( $path ) || count( $path ) === 0 ) {
+				continue;
+			}
+
+			$pointer  = & $block_styles;
+			$last_key = array_pop( $path );
+
+			foreach ( $path as $segment ) {
+				if ( ! is_string( $segment ) && ! is_int( $segment ) ) {
+					continue 2;
+				}
+				if ( ! array_key_exists( $segment, $pointer ) || ! is_array( $pointer[ $segment ] ) ) {
+					continue 2;
+				}
+				$pointer = & $pointer[ $segment ];
+			}
+
+			if ( ( is_string( $last_key ) || is_int( $last_key ) ) && array_key_exists( $last_key, $pointer ) ) {
+				unset( $pointer[ $last_key ] );
+			}
+		}
 		return wp_parse_args(
 			wp_style_engine_get_styles( $block_styles, array( 'convert_vars_to_classnames' => $skip_convert_vars ) ),
 			self::$empty_block_styles
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 9d0263bd0e..58eaa2b729 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
@@ -109,6 +109,31 @@ class Heading_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'font-size:24px', $rendered );
 	}

+	/**
+	 * Test it removes inline margin styles (not supported in email renderer).
+	 */
+	public function testItRemovesInlineMarginStyles(): void {
+		$content                        = '<h1 style="margin-top:10px;margin-bottom:12px;">This is Heading 1</h1>';
+		$parsed_heading                 = $this->parsed_heading;
+		$parsed_heading['innerHTML']    = $content;
+		$parsed_heading['innerContent'] = array( $content );
+
+		$rendered = $this->heading_renderer->render( $content, $parsed_heading, $this->rendering_context );
+		$html     = new \WP_HTML_Tag_Processor( $rendered );
+		$html->next_tag( array( 'tag_name' => 'h1' ) );
+
+		$heading_style = $html->get_attribute( 'style' );
+		$this->assertIsString( $heading_style );
+		$this->assertStringNotContainsString( 'margin', $heading_style );
+
+		// Margin styles should also not leak to the wrapper table cell.
+		$html = new \WP_HTML_Tag_Processor( $rendered );
+		$html->next_tag( array( 'tag_name' => 'td' ) );
+		$table_cell_style = $html->get_attribute( 'style' );
+		$this->assertIsString( $table_cell_style );
+		$this->assertStringNotContainsString( 'margin', $table_cell_style );
+	}
+
 	/**
 	 * Test it uses inherited color from email_attrs when no color is specified
 	 */
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 982b73089d..8962fb082a 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
@@ -132,6 +132,31 @@ class Paragraph_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringNotContainsString( 'border', $paragraph_style );
 	}

+	/**
+	 * Test it removes inline margin styles (not supported in email renderer).
+	 */
+	public function testItRemovesInlineMarginStyles(): void {
+		$content                          = '<p style="margin-top:10px;margin-bottom:12px;">Lorem Ipsum</p>';
+		$parsed_paragraph                 = $this->parsed_paragraph;
+		$parsed_paragraph['innerHTML']    = $content;
+		$parsed_paragraph['innerContent'] = array( $content );
+
+		$rendered = $this->paragraph_renderer->render( $content, $parsed_paragraph, $this->rendering_context );
+		$html     = new \WP_HTML_Tag_Processor( $rendered );
+		$html->next_tag( array( 'tag_name' => 'p' ) );
+
+		$paragraph_style = $html->get_attribute( 'style' );
+		$this->assertIsString( $paragraph_style );
+		$this->assertStringNotContainsString( 'margin', $paragraph_style );
+
+		// Margin styles should also not leak to the wrapper table cell.
+		$html = new \WP_HTML_Tag_Processor( $rendered );
+		$html->next_tag( array( 'tag_name' => 'td' ) );
+		$table_cell_style = $html->get_attribute( 'style' );
+		$this->assertIsString( $table_cell_style );
+		$this->assertStringNotContainsString( 'margin', $table_cell_style );
+	}
+
 	/**
 	 * Test it converts block typography
 	 */
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/Html2Text_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/Html2Text_Test.php
index 3529e59bb9..3aa97cd556 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/Html2Text_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/Html2Text_Test.php
@@ -151,10 +151,19 @@ class Html2Text_Test extends \Email_Editor_Unit_Test {
 	 * Test invalid option throws exception
 	 */
 	public function test_invalid_option_throws_exception(): void {
-		$this->expectException( \InvalidArgumentException::class );
-		$this->expectExceptionMessage( 'Invalid option provided for html2text conversion.' );
-
-		Html2Text::convert( '<p>Test</p>', array( 'invalid_option' => true ) );
+		$previous_error_log = ini_get( 'error_log' );
+		ini_set( 'error_log', '/dev/null' ); // phpcs:ignore WordPress.PHP.IniSet.Risky -- this is to prevent the error from outputting to the screen.
+
+		try {
+			$this->expectException( \InvalidArgumentException::class );
+			$this->expectExceptionMessage( 'Invalid option provided for html2text conversion.' );
+
+			Html2Text::convert( '<p>Test</p>', array( 'invalid_option' => true ) );
+		} finally {
+			if ( false !== $previous_error_log ) {
+				ini_set( 'error_log', (string) $previous_error_log ); // phpcs:ignore WordPress.PHP.IniSet.Risky -- restore the previous value.
+			}
+		}
 	}

 	/**
diff --git a/packages/php/email-editor/tests/unit/Integrations/Utils/Styles_Helper_Test.php b/packages/php/email-editor/tests/unit/Integrations/Utils/Styles_Helper_Test.php
index 54e1f26a08..eb0695c801 100644
--- a/packages/php/email-editor/tests/unit/Integrations/Utils/Styles_Helper_Test.php
+++ b/packages/php/email-editor/tests/unit/Integrations/Utils/Styles_Helper_Test.php
@@ -260,6 +260,50 @@ class Styles_Helper_Test extends \Email_Editor_Unit_Test {
 		$this->assertSame( $expected, $result );
 	}

+	/**
+	 * Test it can unset unsupported props using variable depth paths.
+	 */
+	public function testItUnsetsUnsupportedPropsWithVariableDepthPaths(): void {
+		global $wp_filters, $__email_editor_last_wp_style_engine_get_styles_call;
+		$wp_filters = array();
+		$__email_editor_last_wp_style_engine_get_styles_call = null;
+
+		add_filter(
+			'woocommerce_email_editor_styles_unsupported_props',
+			function ( $unsupported_props ) {
+				$unsupported_props['padding-top'] = array( 'spacing', 'padding', 'top' );
+				return $unsupported_props;
+			}
+		);
+
+		$block_styles = array(
+			'spacing' => array(
+				'padding' => array(
+					'top'    => '12px',
+					'bottom' => '8px',
+				),
+				'margin'  => array(
+					'top' => '10px',
+				),
+			),
+		);
+
+		Styles_Helper::get_styles_from_block( $block_styles );
+
+		$this->assertIsArray( $__email_editor_last_wp_style_engine_get_styles_call );
+		$this->assertArrayHasKey( 'block_styles', $__email_editor_last_wp_style_engine_get_styles_call );
+		$passed_block_styles = $__email_editor_last_wp_style_engine_get_styles_call['block_styles'];
+
+		// Default behavior: margin is removed.
+		$this->assertArrayHasKey( 'spacing', $passed_block_styles );
+		$this->assertArrayNotHasKey( 'margin', $passed_block_styles['spacing'] );
+
+		// New behavior: deeper paths can be unset too.
+		$this->assertArrayHasKey( 'padding', $passed_block_styles['spacing'] );
+		$this->assertArrayNotHasKey( 'top', $passed_block_styles['spacing']['padding'] );
+		$this->assertSame( '8px', $passed_block_styles['spacing']['padding']['bottom'] );
+	}
+
 	/**
 	 * Test it extends block styles with CSS declarations.
 	 */
diff --git a/packages/php/email-editor/tests/unit/stubs.php b/packages/php/email-editor/tests/unit/stubs.php
index 260066e3bb..af8677c2fc 100644
--- a/packages/php/email-editor/tests/unit/stubs.php
+++ b/packages/php/email-editor/tests/unit/stubs.php
@@ -40,9 +40,17 @@ if ( ! function_exists( 'wp_style_engine_get_styles' ) ) {
 	 * Mock wp_style_engine_get_styles function.
 	 *
 	 * @param array $block_styles Array of block styles.
+	 * @param array $options Optional. Style engine options.
 	 * @return array
 	 */
-	function wp_style_engine_get_styles( $block_styles ) {
+	function wp_style_engine_get_styles( $block_styles, $options = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+		// Capture last call for assertions in unit tests.
+		global $__email_editor_last_wp_style_engine_get_styles_call;
+		$__email_editor_last_wp_style_engine_get_styles_call = array(
+			'block_styles' => $block_styles,
+			'options'      => $options,
+		);
+
 		// Return empty structure for empty input.
 		if ( empty( $block_styles ) ) {
 			return array(