Commit 5e8e78c9c6 for woocommerce

commit 5e8e78c9c6ede78719d884cc84cdcc70d8d4294e
Author: Allison Levine <1689238+allilevine@users.noreply.github.com>
Date:   Tue Jan 20 15:06:51 2026 -0500

    Email editor: Add support for horizontal blockGap settings on columns (#62838)

    * Email editor: Add support for blockgap settings on columns.

    * Add changefile(s) from automation for the following project(s): packages/php/email-editor

    * Get the padding value via the WP Styles engine.

    * Add default blockGap to match post editor.

    * Update test.

    ---------

    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/packages/php/email-editor/changelog/62838-add-column-blockgap-support b/packages/php/email-editor/changelog/62838-add-column-blockgap-support
new file mode 100644
index 0000000000..c6857dfb67
--- /dev/null
+++ b/packages/php/email-editor/changelog/62838-add-column-blockgap-support
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Email editor: Add support for horizontal blockGap settings on columns.
\ No newline at end of file
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
index 0f5b06fa9e..f56d909c43 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
@@ -49,10 +49,50 @@ class Spacing_Preprocessor implements Preprocessor {
 				$block['email_attrs']['margin-top'] = $gap;
 			}

+			// Handle horizontal gap for columns: apply padding-left to column children (except the first).
+			if ( 'core/columns' === $parent_block_name && 0 !== $key && null !== $parent_block ) {
+				$columns_gap = $this->get_columns_block_gap( $parent_block, $gap );
+				if ( $columns_gap ) {
+					$block['email_attrs']['padding-left'] = $columns_gap;
+				}
+			}
+
 			$block['innerBlocks']  = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block );
 			$parsed_blocks[ $key ] = $block;
 		}

 		return $parsed_blocks;
 	}
+
+	/**
+	 * Extracts the horizontal blockGap from a columns block.
+	 *
+	 * @param array  $columns_block The columns block.
+	 * @param string $default_gap Default gap value to use if blockGap is not set on the columns block.
+	 * @return string|null The horizontal gap value (e.g., "30px" or "var:preset|spacing|30") or null if not set.
+	 */
+	private function get_columns_block_gap( array $columns_block, string $default_gap = '' ): ?string {
+		$block_gap = $columns_block['attrs']['style']['spacing']['blockGap'] ?? null;
+
+		// Columns block uses object format: { "top": "...", "left": "..." }.
+		// If blockGap.left is explicitly set, use it.
+		if ( is_array( $block_gap ) && isset( $block_gap['left'] ) && is_string( $block_gap['left'] ) ) {
+			$gap_value = $block_gap['left'];
+
+			// Validate against potentially malicious values.
+			if ( preg_match( '/[<>"\']/', $gap_value ) ) {
+				return null;
+			}
+
+			// Return the value as-is. WP's styles engine will handle transformation of preset variables.
+			return $gap_value;
+		}
+
+		// If blockGap.left is not set, use the default gap value if provided.
+		if ( $default_gap ) {
+			return $default_gap;
+		}
+
+		return null;
+	}
 }
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
index d572884235..6a71b96e29 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
@@ -109,6 +109,13 @@ class Column extends Abstract_Block_Renderer {

 		$inner_table = Table_Wrapper_Helper::render_table_wrapper( '{column_content}', $inner_table_attrs, $inner_cell_attrs );

+		// Apply padding-left from email_attrs (set by Spacing_Preprocessor for columns blockGap).
+		$padding_left = $parsed_block['email_attrs']['padding-left'] ?? null;
+		if ( $padding_left ) {
+			$gap_padding_styles = wp_style_engine_get_styles( array( 'spacing' => array( 'padding' => array( 'left' => $padding_left ) ) ) );
+			$wrapper_styles     = Styles_Helper::extend_block_styles( $wrapper_styles, $gap_padding_styles['declarations'] ?? array() );
+		}
+
 		// Create the outer td element (since this is meant to be used within a columns structure).
 		$wrapper_cell_attrs = array(
 			'class' => $wrapper_classname,
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
index 1f8b1ed000..95e4cdc860 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
@@ -18,8 +18,8 @@ use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper;
  */
 class Columns extends Abstract_Block_Renderer {
 	/**
-	 * Override this method to disable spacing (block gap) for columns.
-	 * Spacing is applied on wrapping columns block. Columns are rendered side by side so no spacer is needed.
+	 * Renders the block content.
+	 * BlockGap spacing is handled by Spacing_Preprocessor which sets padding-left on column children.
 	 *
 	 * @param string            $block_content Block content.
 	 * @param array             $parsed_block Parsed block.
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Column_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Column_Test.php
index 2974a17c00..4ce9cfe063 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Column_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Column_Test.php
@@ -181,4 +181,28 @@ class Column_Test extends \Email_Editor_Integration_Test_Case {
 		$this->checkValidHTML( $rendered );
 		$this->assertStringContainsString( 'wp-block-column editor-class-1 another-class', $rendered );
 	}
+
+	/**
+	 * Test it applies padding-left from email_attrs (set by Spacing_Preprocessor for columns blockGap)
+	 */
+	public function testItAppliesPaddingLeftFromEmailAttrs(): void {
+		$parsed_column                                = $this->parsed_column;
+		$parsed_column['email_attrs']['padding-left'] = '30px';
+		$rendered                                     = $this->column_renderer->render( '<p>Column content</p>', $parsed_column, $this->rendering_context );
+		$this->checkValidHTML( $rendered );
+		$this->assertStringContainsString( 'padding-left:30px', $rendered );
+	}
+
+	/**
+	 * Test it applies padding-left with preset variable from email_attrs (set by Spacing_Preprocessor for columns blockGap)
+	 * Verifies that wp_style_engine_get_styles transforms var:preset|spacing|30 to CSS variable format
+	 */
+	public function testItAppliesPaddingLeftWithPresetVariableFromEmailAttrs(): void {
+		$parsed_column                                = $this->parsed_column;
+		$parsed_column['email_attrs']['padding-left'] = 'var:preset|spacing|30';
+		$rendered                                     = $this->column_renderer->render( '<p>Column content</p>', $parsed_column, $this->rendering_context );
+		$this->checkValidHTML( $rendered );
+		// wp_style_engine_get_styles transforms var:preset|spacing|30 to var(--wp--preset--spacing--30).
+		$this->assertStringContainsString( 'var(--wp--preset--spacing--30)', $rendered );
+	}
 }
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
index 6fa1eb8325..066bbe44ad 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
@@ -112,4 +112,173 @@ class Spacing_Preprocessor_Test extends \Email_Editor_Unit_Test {
 		$this->assertArrayHasKey( 'margin-top', $nested_column_second_item['email_attrs'] );
 		$this->assertEquals( '10px', $nested_column_second_item['email_attrs']['margin-top'] );
 	}
+
+	/**
+	 * Test it adds padding-left to column blocks when parent columns has blockGap.left
+	 */
+	public function testItAddsPaddingLeftToColumnsWithBlockGap(): void {
+		$blocks = array(
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(
+					'style' => array(
+						'spacing' => array(
+							'blockGap' => array(
+								'top'  => '20px',
+								'left' => '30px',
+							),
+						),
+					),
+				),
+				'innerBlocks' => array(
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+				),
+			),
+		);
+
+		$result        = $this->preprocessor->preprocess( $blocks, $this->layout, $this->styles );
+		$columns_block = $result[0];
+		$first_column  = $columns_block['innerBlocks'][0];
+		$second_column = $columns_block['innerBlocks'][1];
+		$third_column  = $columns_block['innerBlocks'][2];
+
+		// First column should not have padding-left.
+		$this->assertArrayNotHasKey( 'padding-left', $first_column['email_attrs'] );
+
+		// Second and third columns should have padding-left.
+		$this->assertArrayHasKey( 'padding-left', $second_column['email_attrs'] );
+		$this->assertEquals( '30px', $second_column['email_attrs']['padding-left'] );
+		$this->assertArrayHasKey( 'padding-left', $third_column['email_attrs'] );
+		$this->assertEquals( '30px', $third_column['email_attrs']['padding-left'] );
+	}
+
+	/**
+	 * Test it passes preset variables through for columns blockGap (WP styles engine will handle transformation)
+	 */
+	public function testItPassesPresetVariablesThroughForColumnsBlockGap(): void {
+		$blocks = array(
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(
+					'style' => array(
+						'spacing' => array(
+							'blockGap' => array(
+								'left' => 'var:preset|spacing|40',
+							),
+						),
+					),
+				),
+				'innerBlocks' => array(
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+				),
+			),
+		);
+
+		$result        = $this->preprocessor->preprocess( $blocks, $this->layout, $this->styles );
+		$second_column = $result[0]['innerBlocks'][1];
+
+		// Should pass through "var:preset|spacing|40" as-is. WP's styles engine will handle transformation.
+		$this->assertEquals( 'var:preset|spacing|40', $second_column['email_attrs']['padding-left'] );
+	}
+
+	/**
+	 * Test it adds default padding-left when columns has no blockGap.left
+	 */
+	public function testItAddsDefaultPaddingLeftWithoutBlockGapLeft(): void {
+		$blocks = array(
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(
+					'style' => array(
+						'spacing' => array(
+							'blockGap' => array(
+								'top' => '20px',
+								// No 'left' key.
+							),
+						),
+					),
+				),
+				'innerBlocks' => array(
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+				),
+			),
+		);
+
+		$result        = $this->preprocessor->preprocess( $blocks, $this->layout, $this->styles );
+		$second_column = $result[0]['innerBlocks'][1];
+
+		// Should have padding-left with default gap value since blockGap.left is not set.
+		$this->assertArrayHasKey( 'padding-left', $second_column['email_attrs'] );
+		$this->assertEquals( '10px', $second_column['email_attrs']['padding-left'] );
+	}
+
+	/**
+	 * Test it rejects malicious values in blockGap
+	 */
+	public function testItRejectsMaliciousBlockGapValues(): void {
+		$blocks = array(
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(
+					'style' => array(
+						'spacing' => array(
+							'blockGap' => array(
+								'left' => '30px"><script>alert("xss")</script>',
+							),
+						),
+					),
+				),
+				'innerBlocks' => array(
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+				),
+			),
+		);
+
+		$result        = $this->preprocessor->preprocess( $blocks, $this->layout, $this->styles );
+		$second_column = $result[0]['innerBlocks'][1];
+
+		// Should not have padding-left due to malicious value.
+		$this->assertArrayNotHasKey( 'padding-left', $second_column['email_attrs'] );
+	}
 }