Refactor renderers to use templates
This commit is contained in:
committed by
Rostislav Wolný
parent
888be622e2
commit
b4acac7c52
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -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
|
||||
|
@@ -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');
|
||||
|
Reference in New Issue
Block a user