Refactor renderers to use templates

This commit is contained in:
Mike Jolley
2024-03-22 15:50:08 +00:00
committed by Rostislav Wolný
parent 888be622e2
commit b4acac7c52
5 changed files with 92 additions and 182 deletions

View File

@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
use WP_Block_Parser;
class BlocksParser extends WP_Block_Parser {
/**
* List of parsed blocks
*
* @var \WP_Block_Parser_Block[]
*/
public $output;
public function parse($document) {
parent::parse($document);
return apply_filters('mailpoet_blocks_renderer_parsed_blocks', $this->output);
}
}

View File

@@ -3,83 +3,67 @@
namespace MailPoet\EmailEditor\Engine\Renderer\ContentRenderer;
use MailPoet\EmailEditor\Engine\SettingsController;
use MailPoet\EmailEditor\Engine\ThemeController;
use MailPoet\Util\pQuery\DomNode;
use WP_Block_Template;
use WP_Post;
class ContentRenderer {
private \MailPoetVendor\CSS $cssInliner;
private BlocksRegistry $blocksRegistry;
private ProcessManager $processManager;
private SettingsController $settingsController;
private $layoutSettings;
private $themeStyles;
private ThemeController $themeController;
const CONTENT_STYLES_FILE = 'content.css';
/**
* @param \MailPoetVendor\CSS $cssInliner
*/
public function __construct(
\MailPoetVendor\CSS $cssInliner,
ProcessManager $preprocessManager,
BlocksRegistry $blocksRegistry,
SettingsController $settingsController,
ThemeController $themeController
) {
$this->cssInliner = $cssInliner;
$this->processManager = $preprocessManager;
$this->blocksRegistry = $blocksRegistry;
$this->settingsController = $settingsController;
$this->themeController = $themeController;
}
public function render(\WP_Post $post): string {
$parser = new \WP_Block_Parser();
$parsedBlocks = $parser->parse($post->post_content); // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
$layout = $this->settingsController->getLayout();
$themeStyles = $this->settingsController->getEmailStyles();
$parsedBlocks = $this->processManager->preprocess($parsedBlocks, $layout, $themeStyles);
$renderedBody = $this->renderBlocks($parsedBlocks);
$styles = (string)file_get_contents(dirname(__FILE__) . '/' . self::CONTENT_STYLES_FILE);
$styles .= $this->themeController->getStylesheetForRendering();
$styles = apply_filters('mailpoet_email_content_renderer_styles', $styles, $post);
$renderedBodyDom = $this->inlineCSSStyles("<style>$styles</style>" . $renderedBody);
$renderedBody = $this->postProcessTemplate($renderedBodyDom);
$renderedBody = $this->processManager->postprocess($renderedBody);
return $renderedBody;
}
public function renderBlocks(array $parsedBlocks): string {
private function initialize() {
$this->layoutSettings = $this->settingsController->getLayout();
$this->themeStyles = $this->settingsController->getEmailStyles();
add_filter('block_parser_class', [$this, 'blockParser']);
add_filter('mailpoet_blocks_renderer_parsed_blocks', [$this, 'preprocessParsedBlocks']);
do_action('mailpoet_blocks_renderer_initialized', $this->blocksRegistry);
}
$content = '';
foreach ($parsedBlocks as $parsedBlock) {
$content .= render_block($parsedBlock);
private function setTemplateGlobals(WP_Post $post, WP_Block_Template $template) {
global $_wp_current_template_content, $_wp_current_template_id;
$_wp_current_template_id = $template->id;
$_wp_current_template_content = $template->content;
$GLOBALS['post'] = $post;
}
/**
* As we use default WordPress filters, we need to remove them after email rendering
* so that we don't interfere with possible post rendering that might happen later.
*/
private function reset() {
$this->blocksRegistry->removeAllBlockRendererFilters();
return $content;
remove_filter('block_parser_class', [$this, 'blockParser']);
remove_filter('mailpoet_blocks_renderer_parsed_blocks', [$this, 'preprocessParsedBlocks']);
}
private function inlineCSSStyles(string $template): DomNode {
return $this->cssInliner->inlineCSS($template);
public function blockParser() {
return 'MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\BlocksParser';
}
private function postProcessTemplate(DomNode $templateDom): string {
// because tburry/pquery contains a bug and replaces the opening non mso condition incorrectly we have to replace the opening tag with correct value
$template = $templateDom->__toString();
$template = str_replace('<!--[if !mso]><![endif]-->', '<!--[if !mso]><!-- -->', $template);
return $template;
public function preprocessParsedBlocks(array $parsedBlocks): array {
return $this->processManager->preprocess($parsedBlocks, $this->layoutSettings, $this->themeStyles);
}
public function render(WP_Post $post, WP_Block_Template $template): string {
$this->initialize();
$this->setTemplateGlobals($post, $template);
$renderedHtml = $this->processManager->postprocess(get_the_block_template_html());
$this->reset();
return $renderedHtml;
}
}

View File

@@ -1,50 +0,0 @@
/**
CSS reset for email clients for elements used in email content
StyleLint is disabled because some rules contain properties that linter marks as unknown (e.g. mso- prefix), but they are valid for email rendering
*/
/* stylelint-disable property-no-unknown */
table,
td {
border-collapse: collapse;
mso-table-lspace: 0;
mso-table-rspace: 0;
}
img {
border: 0;
height: auto;
-ms-interpolation-mode: bicubic;
line-height: 100%;
max-width: 100%;
outline: none;
text-decoration: none;
}
p {
display: block;
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-bottom: 0;
margin-top: 0;
}
/* Wa want ensure the same design for all email clients */
ul,
ol {
/* When margin attribute is set to zero, Outlook doesn't render the list properly. As a possible workaround, we can reset only margin for top and bottom */
margin-bottom: 0;
margin-top: 0;
padding: 0 0 0 40px;
}
/* Outlook was adding weird spaces around lists in some versions. Resetting vertical margin for list items solved it */
li {
margin-bottom: 0;
margin-top: 0;
}

View File

@@ -4,111 +4,72 @@ namespace MailPoet\EmailEditor\Engine\Renderer;
use MailPoet\Config\ServicesChecker;
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\ContentRenderer;
use MailPoet\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\VariablesPostprocessor;
use MailPoet\EmailEditor\Engine\Renderer\Templates\Templates;
use MailPoet\EmailEditor\Engine\SettingsController;
use MailPoet\EmailEditor\Engine\ThemeController;
use MailPoet\Util\CdnAssetUrl;
use MailPoet\Util\pQuery\DomNode;
use MailPoetVendor\CSS as CssInliner;
use MailPoetVendor\Html2Text\Html2Text;
class Renderer {
private \MailPoetVendor\CSS $cssInliner;
private CssInliner $cssInliner;
private SettingsController $settingsController;
private ThemeController $themeController;
private ContentRenderer $contentRenderer;
private CdnAssetUrl $cdnAssetUrl;
private ServicesChecker $servicesChecker;
private Templates $templates;
private ThemeController $themeController;
const TEMPLATE_FILE = 'template.html';
const TEMPLATE_STYLES_FILE = 'template.css';
const TEMPLATE_FILE = 'template-canvas.php';
const TEMPLATE_STYLES_FILE = 'template-canvas.css';
/**
* @param \MailPoetVendor\CSS $cssInliner
*/
public function __construct(
\MailPoetVendor\CSS $cssInliner,
CssInliner $cssInliner,
SettingsController $settingsController,
ContentRenderer $contentRenderer,
CdnAssetUrl $cdnAssetUrl,
ServicesChecker $servicesChecker,
ThemeController $themeController
Templates $templates,
ThemeController $themeController,
ServicesChecker $servicesChecker
) {
$this->cssInliner = $cssInliner;
$this->settingsController = $settingsController;
$this->contentRenderer = $contentRenderer;
$this->cdnAssetUrl = $cdnAssetUrl;
$this->templates = $templates;
$this->themeController = $themeController;
$this->servicesChecker = $servicesChecker;
$this->themeController = $themeController;
}
public function render(\WP_Post $post, string $subject, string $preHeader, string $language, $metaRobots = ''): array {
$layout = $this->settingsController->getLayout();
$theme = $this->themeController->getTheme();
$theme = apply_filters('mailpoet_email_editor_rendering_theme_styles', $theme, $post);
$themeStyles = $theme->get_data()['styles'];
$themeStyles = $this->settingsController->getEmailStyles();
$width = $layout['contentSize'];
$padding = $themeStyles['spacing']['padding'];
$contentBackground = $themeStyles['color']['background']['content'];
$layoutBackground = $themeStyles['color']['background']['layout'];
$contentFontFamily = $themeStyles['typography']['fontFamily'];
$renderedBody = $this->contentRenderer->render($post);
$logoHtml = $this->servicesChecker->isPremiumPluginActive() ? '' : '<img src="' . esc_attr($this->cdnAssetUrl->generateCdnUrl('email-editor/logo-footer.png')) . '" alt="MailPoet" style="margin: 24px auto; display: block;" />';
$styles = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_STYLES_FILE);
$styles = apply_filters('mailpoet_email_renderer_styles', $styles, $post);
$templateStyles = file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_STYLES_FILE);
$templateStyles = apply_filters('mailpoet_email_renderer_styles', $templateStyles . $this->themeController->getStylesheetForRendering(), $post);
$templateHtml = $this->contentRenderer->render($post, $this->templates->getBlockTemplateFromFile('email-general.html'));
$template = (string)file_get_contents(dirname(__FILE__) . '/' . self::TEMPLATE_FILE);
// Replace settings placeholders with values
$template = str_replace(
['{{width}}', '{{layout_background}}', '{{content_background}}', '{{content_font_family}}', '{{padding_top}}', '{{padding_right}}', '{{padding_bottom}}', '{{padding_left}}'],
[$layout['contentSize'], $layoutBackground, $contentBackground, $contentFontFamily, $padding['top'], $padding['right'], $padding['bottom'], $padding['left']],
$template
);
$logo = $this->cdnAssetUrl->generateCdnUrl('email-editor/logo-footer.png');
$footerLogo = $this->servicesChecker->isPremiumPluginActive() ? '' : '<img src="' . esc_attr($logo) . '" alt="MailPoet" style="margin: 24px auto; display: block;" />';
/**
* Replace template variables
* {{email_language}}
* {{email_subject}}
* {{email_meta_robots}}
* {{email_template_styles}}
* {{email_preheader}}
* {{email_body}}
* {{email_footer_logo}}
*/
$templateWithContents = $this->injectContentIntoTemplate(
$template,
[
$language,
esc_html($subject),
$metaRobots,
$styles,
esc_html($preHeader),
$renderedBody,
$footerLogo,
]
);
$templateWithContentsDom = $this->inlineCSSStyles($templateWithContents);
$templateWithContents = $this->postProcessTemplate($templateWithContentsDom);
// Because the padding can be defined by variables, we need to postprocess the HTML by VariablesPostprocessor
$variablesPostprocessor = new VariablesPostprocessor($this->themeController);
$templateWithContents = $variablesPostprocessor->postprocess($templateWithContents);
ob_start();
include self::TEMPLATE_FILE;
$renderedTemplate = (string)ob_get_clean();
$renderedTemplate = $this->inlineCSSStyles($renderedTemplate);
$renderedTemplate = $this->postProcessTemplate($renderedTemplate);
return [
'html' => $templateWithContents,
'text' => $this->renderTextVersion($templateWithContents),
'html' => $renderedTemplate,
'text' => $this->renderTextVersion($renderedTemplate),
];
}
private function injectContentIntoTemplate($template, array $content) {
return preg_replace_callback('/{{\w+}}/', function ($matches) use (&$content) {
return array_shift($content);
}, $template);
}
/**
* @param string $template
* @return DomNode

View File

@@ -21,25 +21,21 @@ class ContentRendererTest extends \MailPoetTest {
}
public function testItRendersContent(): void {
$template = new \WP_Block_Template();
$template->id = 'template-id';
$template->content = '<!-- wp:post-content /-->';
$content = $this->renderer->render(
$this->emailPost
$this->emailPost,
$template
);
verify($content)->stringContainsString('Hello!');
}
public function testItInlinesStylesAddedViaHook(): void {
$stylesCallback = function ($styles) {
return $styles . 'p { color: pink; }';
};
add_filter('mailpoet_email_content_renderer_styles', $stylesCallback);
$rendered = $this->renderer->render($this->emailPost);
$paragraphStyles = $this->getStylesValueForTag($rendered, 'p');
verify($paragraphStyles)->stringContainsString('color:pink');
remove_filter('mailpoet_email_content_renderer_styles', $stylesCallback);
}
public function testItInlinesContentStyles(): void {
$rendered = $this->renderer->render($this->emailPost);
$template = new \WP_Block_Template();
$template->id = 'template-id';
$template->content = '<!-- wp:post-content /-->';
$rendered = $this->renderer->render($this->emailPost, $template);
$paragraphStyles = $this->getStylesValueForTag($rendered, 'p');
verify($paragraphStyles)->stringContainsString('margin:0');
verify($paragraphStyles)->stringContainsString('display:block');