Add border support to the image renderer

This commit fixes how the border is applied in an image block.
- the border is applied to the wrapping table cell instead of the image
- we need to move classes related to border styles to the cell so that related styles are inlined in the proper place
- the caption is put in an additional table so that it doesn't extend the border when it is longer than the image
- rounded image styles are now applied to the wrapper and the image
[MAILPOET-6280]
This commit is contained in:
Rostislav Wolny
2024-11-01 15:12:49 +01:00
committed by Pavel Dohnal
parent 10ad46239d
commit 9829c96caa
2 changed files with 91 additions and 26 deletions

View File

@@ -7,7 +7,7 @@ use MailPoet\EmailEditor\Integrations\Utils\DomDocumentHelper;
class Image extends AbstractBlockRenderer { class Image extends AbstractBlockRenderer {
protected function renderContent($blockContent, array $parsedBlock, SettingsController $settingsController): string { protected function renderContent($blockContent, array $parsedBlock, SettingsController $settingsController): string {
$parsedHtml = $this->parseBlockContent($this->cleanupContent($blockContent)); $parsedHtml = $this->parseBlockContent($blockContent);
if (!$parsedHtml) { if (!$parsedHtml) {
return ''; return '';
@@ -16,28 +16,31 @@ class Image extends AbstractBlockRenderer {
$imageUrl = $parsedHtml['imageUrl']; $imageUrl = $parsedHtml['imageUrl'];
$image = $parsedHtml['image']; $image = $parsedHtml['image'];
$caption = $parsedHtml['caption']; $caption = $parsedHtml['caption'];
$class = $parsedHtml['class'];
$parsedBlock = $this->addImageSizeWhenMissing($parsedBlock, $imageUrl, $settingsController); $parsedBlock = $this->addImageSizeWhenMissing($parsedBlock, $imageUrl, $settingsController);
$image = $this->addImageDimensions($image, $parsedBlock, $settingsController); $image = $this->addImageDimensions($image, $parsedBlock, $settingsController);
$image = $this->applyImageBorderStyle($image, $parsedBlock, $settingsController);
$image = $this->applyRoundedStyle($image, $parsedBlock);
return str_replace( $imageWithWrapper = str_replace(
['{image_content}', '{caption_content}'], ['{image_content}', '{caption_content}'],
[$image, $caption], [$image, $caption],
$this->getBlockWrapper($parsedBlock, $settingsController) $this->getBlockWrapper($parsedBlock, $settingsController)
); );
$imageWithWrapper = $this->applyRoundedStyle($imageWithWrapper, $parsedBlock);
$imageWithWrapper = $this->applyImageBorderStyle($imageWithWrapper, $parsedBlock, $class);
return $imageWithWrapper;
} }
private function applyRoundedStyle(string $blockContent, array $parsedBlock): string { private function applyRoundedStyle(string $blockContent, array $parsedBlock): string {
// Because the isn't an attribute for definition of rounded style, we have to check the class name // Because the isn't an attribute for definition of rounded style, we have to check the class name
if (isset($parsedBlock['attrs']['className']) && strpos($parsedBlock['attrs']['className'], 'is-style-rounded') !== false) { if (isset($parsedBlock['attrs']['className']) && strpos($parsedBlock['attrs']['className'], 'is-style-rounded') !== false) {
// If the image should be in a circle, we need to set the border-radius to 9999px to make it the same as is in the editor // If the image should be in a circle, we need to set the border-radius to 9999px to make it the same as is in the editor
// This style cannot be applied on the wrapper, and we need to set it directly on the image // This style is applied to both wrapper and the image
$blockContent = $this->removeStyleAttributeFromElement($blockContent, ['tag_name' => 'td', 'class_name' => 'email-image-cell'], 'border-radius');
$blockContent = $this->addStyleToElement($blockContent, ['tag_name' => 'td', 'class_name' => 'email-image-cell'], 'border-radius: 9999px;');
$blockContent = $this->removeStyleAttributeFromElement($blockContent, ['tag_name' => 'img'], 'border-radius'); $blockContent = $this->removeStyleAttributeFromElement($blockContent, ['tag_name' => 'img'], 'border-radius');
$blockContent = $this->addStyleToElement($blockContent, ['tag_name' => 'img'], 'border-radius: 9999px;'); $blockContent = $this->addStyleToElement($blockContent, ['tag_name' => 'img'], 'border-radius: 9999px;');
} }
return $blockContent; return $blockContent;
} }
@@ -55,19 +58,14 @@ class Image extends AbstractBlockRenderer {
$maxWidth = $settingsController->parseNumberFromStringWithPixels($parsedBlock['email_attrs']['width']); $maxWidth = $settingsController->parseNumberFromStringWithPixels($parsedBlock['email_attrs']['width']);
$imageSize = wp_getimagesize($imageUrl); $imageSize = wp_getimagesize($imageUrl);
$imageSize = $imageSize ? $imageSize[0] : $maxWidth; $imageSize = $imageSize ? $imageSize[0] : $maxWidth;
// Because width is primarily used for the max-width property, we need to add the left and right border width to it
$borderWidth = $parsedBlock['attrs']['style']['border']['width'] ?? '0px';
$borderLeftWidth = $parsedBlock['attrs']['style']['border']['left']['width'] ?? $borderWidth;
$borderRightWidth = $parsedBlock['attrs']['style']['border']['right']['width'] ?? $borderWidth;
$width = min($imageSize, $maxWidth); $width = min($imageSize, $maxWidth);
$width += $settingsController->parseNumberFromStringWithPixels($borderLeftWidth ?? '0px');
$width += $settingsController->parseNumberFromStringWithPixels($borderRightWidth ?? '0px');
$parsedBlock['attrs']['width'] = "{$width}px"; $parsedBlock['attrs']['width'] = "{$width}px";
return $parsedBlock; return $parsedBlock;
} }
private function applyImageBorderStyle(string $blockContent, array $parsedBlock, SettingsController $settingsController): string { private function applyImageBorderStyle(string $blockContent, array $parsedBlock, string $class): string {
// Getting individual border properties // Getting individual border properties
$borderStyles = wp_style_engine_get_styles(['border' => $parsedBlock['attrs']['style']['border'] ?? []]); $borderStyles = wp_style_engine_get_styles(['border' => $parsedBlock['attrs']['style']['border'] ?? []]);
$borderStyles = $borderStyles['declarations'] ?? []; $borderStyles = $borderStyles['declarations'] ?? [];
@@ -75,8 +73,19 @@ class Image extends AbstractBlockRenderer {
$borderStyles['border-style'] = 'solid'; $borderStyles['border-style'] = 'solid';
$borderStyles['box-sizing'] = 'border-box'; $borderStyles['box-sizing'] = 'border-box';
} }
$borderElementTag = ['tag_name' => 'td', 'class_name' => 'email-image-cell'];
return $this->addStyleToElement($blockContent, ['tag_name' => 'img'], \WP_Style_Engine::compile_css($borderStyles, '')); $contentWithBorderStyles = $this->addStyleToElement($blockContent, $borderElementTag, \WP_Style_Engine::compile_css($borderStyles, ''));
// Add Border related classes to proper element. This is required for inlined border-color styles when defined via class
$borderClasses = array_filter(explode(' ', $class), function($className) {
return strpos($className, 'border') !== false;
});
$html = new \WP_HTML_Tag_Processor($contentWithBorderStyles);
if ($html->next_tag($borderElementTag)) {
$class = $html->get_attribute('class') ?? '';
$borderClasses[] = $class;
$html->set_attribute('class', implode(' ', $borderClasses));
}
return $html->get_updated_html();
} }
/** /**
@@ -112,7 +121,7 @@ class Image extends AbstractBlockRenderer {
$themeData = $settingsController->getTheme()->get_data(); $themeData = $settingsController->getTheme()->get_data();
$styles = [ $styles = [
'text-align' => 'center', 'text-align' => isset($parsedBlock['attrs']['align']) ? 'center' : 'left',
]; ];
$styles['font-size'] = $parsedBlock['email_attrs']['font-size'] ?? $themeData['styles']['typography']['fontSize']; $styles['font-size'] = $parsedBlock['email_attrs']['font-size'] ?? $themeData['styles']['typography']['fontSize'];
@@ -131,11 +140,16 @@ class Image extends AbstractBlockRenderer {
'width' => '100%', 'width' => '100%',
]; ];
// When the image is not aligned, the wrapper is set to 100% width due to caption that can be longer than the image $width = $parsedBlock['attrs']['width'] ?? '100%';
$wrapperWidth = isset($parsedBlock['attrs']['align']) ? ($parsedBlock['attrs']['width'] ?? '100%') : '100%'; $wrapperWidth = ($width && $width !== '100%') ? $width : 'auto';
$wrapperStyles = $styles; $wrapperStyles = $styles;
$wrapperStyles['width'] = $wrapperWidth; $wrapperStyles['width'] = $wrapperWidth;
$wrapperStyles['border-collapse'] = 'separate'; // Needed because of border radius
// When the image is not aligned, the wrapper is set to 100% width due to caption that can be longer than the image
$captionWidth = isset($parsedBlock['attrs']['align']) ? ($parsedBlock['attrs']['width'] ?? '100%') : '100%';
$captionWrapperStyles = $styles;
$captionWrapperStyles['width'] = $captionWidth;
$captionStyles = $this->getCaptionStyles($settingsController, $parsedBlock); $captionStyles = $this->getCaptionStyles($settingsController, $parsedBlock);
$styles['width'] = '100%'; $styles['width'] = '100%';
@@ -162,11 +176,21 @@ class Image extends AbstractBlockRenderer {
width="' . esc_attr($wrapperWidth) . '" width="' . esc_attr($wrapperWidth) . '"
> >
<tr> <tr>
<td>{image_content}</td> <td class="email-image-cell">{image_content}</td>
</tr> </tr>
</table>
<table
role="presentation"
class="email-table-with-width"
border="0"
cellpadding="0"
cellspacing="0"
style="' . esc_attr(\WP_Style_Engine::compile_css($captionWrapperStyles, '')) . '"
width="' . esc_attr($captionWidth) . '"
>
<tr> <tr>
<td style="' . esc_attr($captionStyles) . '">{caption_content}</td> <td style="' . esc_attr($captionStyles) . '">{caption_content}</td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
@@ -229,21 +253,21 @@ class Image extends AbstractBlockRenderer {
} }
$imageSrc = $domHelper->getAttributeValue($imgTag, 'src'); $imageSrc = $domHelper->getAttributeValue($imgTag, 'src');
$imageClass = $domHelper->getAttributeValue($imgTag, 'class');
$imageHtml = $domHelper->getOuterHtml($imgTag); $imageHtml = $domHelper->getOuterHtml($imgTag);
$figcaption = $domHelper->findElement('figcaption'); $figcaption = $domHelper->findElement('figcaption');
$figcaptionHtml = $figcaption ? $domHelper->getOuterHtml($figcaption) : ''; $figcaptionHtml = $figcaption ? $domHelper->getOuterHtml($figcaption) : '';
$figcaptionHtml = str_replace(['<figcaption', '</figcaption>'], ['<span', '</span>'], $figcaptionHtml); $figcaptionHtml = str_replace(['<figcaption', '</figcaption>'], ['<span', '</span>'], $figcaptionHtml);
return [ return [
'imageUrl' => $imageSrc ?: '', 'imageUrl' => $imageSrc ?: '',
'image' => $imageHtml, 'image' => $this->cleanupImageHtml($imageHtml),
'caption' => $figcaptionHtml ?: '', 'caption' => $figcaptionHtml ?: '',
'class' => $imageClass ?: '',
]; ];
} }
private function cleanupContent(string $contentHtml): string { private function cleanupImageHtml(string $contentHtml): string {
$html = new \WP_HTML_Tag_Processor($contentHtml); $html = new \WP_HTML_Tag_Processor($contentHtml);
if ($html->next_tag(['tag_name' => 'img'])) { if ($html->next_tag(['tag_name' => 'img'])) {
$html->remove_attribute('srcset'); $html->remove_attribute('srcset');

View File

@@ -96,4 +96,45 @@ class ImageTest extends \MailPoetTest {
$this->assertStringContainsString('height:300px;', $rendered); $this->assertStringContainsString('height:300px;', $rendered);
$this->assertStringContainsString('width:400px;', $rendered); $this->assertStringContainsString('width:400px;', $rendered);
} }
public function testItRendersBorders(): void {
$imageContent = $this->imageContent;
$parsedImage = $this->parsedImage;
$parsedImage['attrs']['style']['border'] = [
'width' => '10px',
'color' => '#000001',
'radius' => '20px',
];
$rendered = $this->imageRenderer->render($imageContent, $parsedImage, $this->settingsController);
$html = new \WP_HTML_Tag_Processor($rendered);
// Border is rendered on the wrapping table cell
$html->next_tag(['tag_name' => 'td', 'class_name' => 'email-image-cell']);
$tableCellStyle = $html->get_attribute('style');
$this->assertStringContainsString('border-color:#000001', $tableCellStyle);
$this->assertStringContainsString('border-radius:20px', $tableCellStyle);
$this->assertStringContainsString('border-style:solid;', $tableCellStyle);
$html->next_tag(['tag_name' => 'img']);
$imgStyle = $html->get_attribute('style');
$this->assertStringNotContainsString('border', $imgStyle);
}
public function testItMovesBorderRelatedClasses(): void {
$imageContent = str_replace('<img', '<img class="custom-class has-border-color has-border-red-color"',$this->imageContent);
$parsedImage = $this->parsedImage;
$parsedImage['attrs']['style']['border'] = [
'width' => '10px',
'color' => '#000001',
'radius' => '20px',
];
$rendered = $this->imageRenderer->render($imageContent, $parsedImage, $this->settingsController);
$html = new \WP_HTML_Tag_Processor($rendered);
// Border is rendered on the wrapping table cell and the border classes are moved to the wrapping table cell
$html->next_tag(['tag_name' => 'td', 'class_name' => 'email-image-cell']);
$tableCellClass = $html->get_attribute('class');
$this->assertStringContainsString('has-border-red-color', $tableCellClass);
$this->assertStringContainsString('has-border-color', $tableCellClass);
$this->assertStringNotContainsString('custom-class', $tableCellClass);
}
} }