woocommerce
/
packages
/
email-editor
/
src
/
Engine
/
Renderer
/
ContentRenderer
/
Layout
/
class-flex-layout-renderer.php
woocommerce
/
packages
/
email-editor
/
src
/
Engine
/
Renderer
/
ContentRenderer
/
Layout
Last commit date
class-flex-layout-renderer.php
1 month ago
class-flex-layout-renderer.php
123 lines
| 1 | <?php |
| 2 | /** |
| 3 | * This file is part of the WooCommerce Email Editor package |
| 4 | * |
| 5 | * @package Automattic\WooCommerce\EmailEditor |
| 6 | */ |
| 7 | |
| 8 | declare(strict_types = 1); |
| 9 | namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Layout; |
| 10 | |
| 11 | use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context; |
| 12 | use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper; |
| 13 | |
| 14 | /** |
| 15 | * This class provides functionality to render inner blocks of a block that supports reduced flex layout. |
| 16 | */ |
| 17 | class Flex_Layout_Renderer { |
| 18 | /** |
| 19 | * Render inner blocks in flex layout. |
| 20 | * |
| 21 | * @param array $parsed_block Parsed block. |
| 22 | * @param Rendering_Context $rendering_context Rendering context. |
| 23 | * @return string |
| 24 | */ |
| 25 | public function render_inner_blocks_in_layout( array $parsed_block, Rendering_Context $rendering_context ): string { |
| 26 | $theme_styles = $rendering_context->get_theme_styles(); |
| 27 | $flex_gap = $theme_styles['spacing']['blockGap'] ?? '0px'; |
| 28 | $flex_gap_number = Styles_Helper::parse_value( $flex_gap ); |
| 29 | |
| 30 | $margin_top = $parsed_block['email_attrs']['margin-top'] ?? '0px'; |
| 31 | $justify = $rendering_context->resolve_text_align( $parsed_block['attrs']['layout']['justifyContent'] ?? null ); |
| 32 | $styles = wp_style_engine_get_styles( $parsed_block['attrs']['style'] ?? array() )['css'] ?? ''; |
| 33 | $styles .= 'margin-top: ' . $margin_top . ';'; |
| 34 | $styles .= 'text-align: ' . $justify; |
| 35 | |
| 36 | // MS Outlook doesn't support style attribute in divs so we conditionally wrap the buttons in a table and repeat styles. |
| 37 | $output_html = sprintf( |
| 38 | '<!--[if mso | IE]><table align="%2$s" role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%%"><tr><td style="%1$s" ><![endif]--> |
| 39 | <div style="%1$s"><table class="layout-flex-wrapper" style="display:inline-block"><tbody><tr>', |
| 40 | esc_attr( $styles ), |
| 41 | esc_attr( $justify ) |
| 42 | ); |
| 43 | |
| 44 | $inner_blocks = $this->compute_widths_for_flex_layout( $parsed_block, $flex_gap_number ); |
| 45 | |
| 46 | foreach ( $inner_blocks as $key => $block ) { |
| 47 | $styles = array(); |
| 48 | if ( $block['email_attrs']['layout_width'] ?? null ) { |
| 49 | $styles['width'] = $block['email_attrs']['layout_width']; |
| 50 | } |
| 51 | if ( $key > 0 ) { |
| 52 | $styles[ 'padding-' . $rendering_context->get_start_side() ] = $flex_gap; |
| 53 | } |
| 54 | $output_html .= '<td class="layout-flex-item" style="' . esc_attr( \WP_Style_Engine::compile_css( $styles, '' ) ) . '">' . render_block( $block ) . '</td>'; |
| 55 | } |
| 56 | $output_html .= '</tr></table></div> |
| 57 | <!--[if mso | IE]></td></tr></table><![endif]-->'; |
| 58 | |
| 59 | return $output_html; |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Compute widths for blocks in flex layout. |
| 64 | * |
| 65 | * @param array $parsed_block Parsed block. |
| 66 | * @param float $flex_gap Flex gap. |
| 67 | * @return array |
| 68 | */ |
| 69 | private function compute_widths_for_flex_layout( array $parsed_block, float $flex_gap ): array { |
| 70 | // When there is no parent width we can't compute widths so auto width will be used. |
| 71 | if ( ! isset( $parsed_block['email_attrs']['width'] ) ) { |
| 72 | return $parsed_block['innerBlocks'] ?? array(); |
| 73 | } |
| 74 | $blocks_count = count( $parsed_block['innerBlocks'] ); |
| 75 | $total_used_width = 0; // Total width assuming items without set width would consume proportional width. |
| 76 | $parent_width = Styles_Helper::parse_value( $parsed_block['email_attrs']['width'] ); |
| 77 | $inner_blocks = $parsed_block['innerBlocks'] ?? array(); |
| 78 | |
| 79 | foreach ( $inner_blocks as $key => $block ) { |
| 80 | $block_width_percent = ( $block['attrs']['width'] ?? 0 ) ? intval( $block['attrs']['width'] ) : 0; |
| 81 | $block_width = floor( $parent_width * ( $block_width_percent / 100 ) ); |
| 82 | // If width is not set, we assume it's 25% of the parent width. |
| 83 | $total_used_width += $block_width ? $block_width : floor( $parent_width * ( 25 / 100 ) ); |
| 84 | |
| 85 | if ( ! $block_width ) { |
| 86 | $inner_blocks[ $key ]['email_attrs']['layout_width'] = null; // Will be rendered as auto. |
| 87 | continue; |
| 88 | } |
| 89 | $inner_blocks[ $key ]['email_attrs']['layout_width'] = $this->get_width_without_gap( $block_width, $flex_gap, $block_width_percent ) . 'px'; |
| 90 | } |
| 91 | |
| 92 | // When there is only one block, or percentage is set reasonably we don't need to adjust and just render as set by user. |
| 93 | if ( $blocks_count <= 1 || ( $total_used_width <= $parent_width ) ) { |
| 94 | return $inner_blocks; |
| 95 | } |
| 96 | |
| 97 | foreach ( $inner_blocks as $key => $block ) { |
| 98 | $proportional_space_overflow = $parent_width / $total_used_width; |
| 99 | $block_width = $block['email_attrs']['layout_width'] ? Styles_Helper::parse_value( $block['email_attrs']['layout_width'] ) : 0; |
| 100 | $block_proportional_width = $block_width * $proportional_space_overflow; |
| 101 | $block_proportional_percentage = ( $block_proportional_width / $parent_width ) * 100; |
| 102 | $inner_blocks[ $key ]['email_attrs']['layout_width'] = $block_width ? $this->get_width_without_gap( $block_proportional_width, $flex_gap, $block_proportional_percentage ) . 'px' : null; |
| 103 | } |
| 104 | return $inner_blocks; |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * How much of width we will strip to keep some space for the gap |
| 109 | * This is computed based on CSS rule used in the editor: |
| 110 | * For block with width set to X percent |
| 111 | * width: calc(X% - (var(--wp--style--block-gap) * (100 - X)/100))); |
| 112 | * |
| 113 | * @param float $block_width Block width in pixels. |
| 114 | * @param float $flex_gap Flex gap in pixels. |
| 115 | * @param float $block_width_percent Block width in percent. |
| 116 | * @return int |
| 117 | */ |
| 118 | private function get_width_without_gap( float $block_width, float $flex_gap, float $block_width_percent ): int { |
| 119 | $width_gap_reduction = $flex_gap * ( ( 100 - $block_width_percent ) / 100 ); |
| 120 | return intval( floor( $block_width - $width_gap_reduction ) ); |
| 121 | } |
| 122 | } |
| 123 |