206 lines
8.6 KiB
PHP
206 lines
8.6 KiB
PHP
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
|
|
|
|
namespace MailPoet\WooCommerce\TransactionalEmails;
|
|
|
|
if (!defined('ABSPATH')) exit;
|
|
|
|
|
|
use MailPoet\Entities\NewsletterEntity;
|
|
use MailPoet\Newsletter\Renderer\Renderer as NewsletterRenderer;
|
|
use MailPoet\Newsletter\Shortcodes\Shortcodes;
|
|
use MailPoetVendor\csstidy;
|
|
use MailPoetVendor\csstidy_print;
|
|
|
|
class Renderer {
|
|
const CONTENT_CONTAINER_ID = 'mailpoet_woocommerce_container';
|
|
|
|
/** @var csstidy */
|
|
private $cssParser;
|
|
|
|
/** @var NewsletterRenderer */
|
|
private $renderer;
|
|
|
|
/** @var string */
|
|
private $htmlBeforeContent;
|
|
|
|
/** @var string */
|
|
private $htmlAfterContent;
|
|
|
|
/** @var Shortcodes */
|
|
private $shortcodes;
|
|
|
|
public function __construct(
|
|
csstidy $cssParser,
|
|
NewsletterRenderer $renderer,
|
|
Shortcodes $shortcodes
|
|
) {
|
|
$this->cssParser = $cssParser;
|
|
$this->htmlBeforeContent = '';
|
|
$this->htmlAfterContent = '';
|
|
$this->renderer = $renderer;
|
|
$this->shortcodes = $shortcodes;
|
|
}
|
|
|
|
public function render(NewsletterEntity $newsletter, ?string $subject = null) {
|
|
$preparedNewsletter = $this->prepareNewsletterForRendering($newsletter);
|
|
$renderedNewsletter = $this->renderer->renderAsPreview($preparedNewsletter, 'html', $subject);
|
|
$headingText = $subject ?? '';
|
|
|
|
$renderedHtml = $this->processShortcodes($preparedNewsletter, $renderedNewsletter);
|
|
|
|
$renderedHtml = str_replace(ContentPreprocessor::WC_HEADING_PLACEHOLDER, $headingText, $renderedHtml);
|
|
$html = explode(ContentPreprocessor::WC_CONTENT_PLACEHOLDER, $renderedHtml);
|
|
$this->htmlBeforeContent = $html[0];
|
|
$this->htmlAfterContent = $html[1];
|
|
}
|
|
|
|
public function getHTMLBeforeContent() {
|
|
if (empty($this->htmlBeforeContent)) {
|
|
throw new \Exception("You should call 'render' before 'getHTMLBeforeContent'");
|
|
}
|
|
return $this->htmlBeforeContent . '<!--WooContent--><div id="' . self::CONTENT_CONTAINER_ID . '"><div id="body_content"><div id="body_content_inner"><table style="width: 100%"><tr><td style="padding: 10px 20px;">';
|
|
}
|
|
|
|
public function getHTMLAfterContent() {
|
|
if (empty($this->htmlAfterContent)) {
|
|
throw new \Exception("You should call 'render' before 'getHTMLAfterContent'");
|
|
}
|
|
return '<!--WooContent--></td></tr></table></div></div></div>' . $this->htmlAfterContent;
|
|
}
|
|
|
|
/**
|
|
* In this method we alter the rendered content that is output when processing the WooCommerce email template.
|
|
* - We update inlined font-family rules in the content block generated by Woo
|
|
*/
|
|
public function updateRenderedContent(NewsletterEntity $newsletter, string $content): string {
|
|
$isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
|
|
// For Backward compatibility do not apply styles for content unless the template was edited with the editor
|
|
// and user visually checked and is aware of updated styles feature.
|
|
if (!$isSavedWithStyledWooBlock) {
|
|
return $content;
|
|
}
|
|
$contentParts = explode('<!--WooContent-->', $content);
|
|
if (count($contentParts) !== 3) {
|
|
return $content;
|
|
}
|
|
[$beforeWooContent, $wooContent, $afterWooContent] = $contentParts;
|
|
$fontFamily = $newsletter->getGlobalStyle('text', 'fontFamily');
|
|
$replaceFontFamilyCallback = function ($matches) use ($fontFamily) {
|
|
$pattern = '/font-family\s*:\s*[^;]+;/i';
|
|
$style = $matches[1];
|
|
$style = preg_replace($pattern, "font-family:$fontFamily;", $style);
|
|
return 'style="' . esc_attr($style) . '"';
|
|
};
|
|
$stylePattern = '/style="(.*?)"/i';
|
|
$wooContent = (string)preg_replace_callback($stylePattern, $replaceFontFamilyCallback, $wooContent);
|
|
return implode('', [$beforeWooContent, $wooContent, $afterWooContent]);
|
|
}
|
|
|
|
/**
|
|
* In this method we alter CSS that is later inlined into the WooCommerce email template. WooCommerce use Emogrifier to inline CSS.
|
|
* The inlining is called after the rendering and after the modifications we apply to the rendered content in self::updateRenderedContent
|
|
* - We prefix the original selectors to avoid inlining those rules into content added int the MailPoet's editor.
|
|
* - We update the font-family in the original CSS if it's set in the editor.
|
|
* - We update the font-size for the inner content if it's set in the editor.
|
|
*/
|
|
public function enhanceCss(string $css, NewsletterEntity $newsletter): string {
|
|
$this->cssParser->settings['compress_colors'] = false;
|
|
$this->cssParser->parse($css);
|
|
foreach ($this->cssParser->css as $index => $rules) {
|
|
$this->cssParser->css[$index] = [];
|
|
foreach ($rules as $selectors => $properties) {
|
|
$properties = $this->updateStyleDefinition($selectors, $newsletter, $properties);
|
|
$selectors = explode(',', $selectors);
|
|
$selectors = array_map(function($selector) {
|
|
return '#' . self::CONTENT_CONTAINER_ID . ' ' . $selector;
|
|
}, $selectors);
|
|
$selectors = implode(',', $selectors);
|
|
|
|
$this->cssParser->css[$index][$selectors] = $properties;
|
|
}
|
|
}
|
|
|
|
/** @var csstidy_print */
|
|
$print = $this->cssParser->print;
|
|
$css = $print->plain();
|
|
|
|
// Enforce the special heading color for the WooCommerce email header
|
|
$wooHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'headingFontColor');
|
|
if ($wooHeadingColor) {
|
|
$css .= "#mailpoet-woo-email-header { color: $wooHeadingColor !important; }";
|
|
}
|
|
return $css;
|
|
}
|
|
|
|
private function processShortcodes(NewsletterEntity $newsletter, $content) {
|
|
$this->shortcodes->setQueue(null);
|
|
$this->shortcodes->setSubscriber(null);
|
|
$this->shortcodes->setNewsletter($newsletter);
|
|
return $this->shortcodes->replace($content);
|
|
}
|
|
|
|
/**
|
|
* This method prepares the newsletter for rendering
|
|
* - We ensure that the font-family and branding color are used as default for all headings
|
|
*/
|
|
private function prepareNewsletterForRendering(NewsletterEntity $newsletter): NewsletterEntity {
|
|
$newsletterClone = clone($newsletter);
|
|
$headingFontFamily = $newsletter->getGlobalStyle('woocommerce', 'headingFontFamily');
|
|
if ($headingFontFamily) {
|
|
$newsletterClone->setGlobalStyle('h1', 'fontFamily', $headingFontFamily);
|
|
$newsletterClone->setGlobalStyle('h2', 'fontFamily', $headingFontFamily);
|
|
$newsletterClone->setGlobalStyle('h3', 'fontFamily', $headingFontFamily);
|
|
}
|
|
$brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
|
|
$contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;
|
|
if ($contentHeadingColor) {
|
|
$newsletterClone->setGlobalStyle('h1', 'color', $contentHeadingColor);
|
|
$newsletterClone->setGlobalStyle('h2', 'color', $contentHeadingColor);
|
|
$newsletterClone->setGlobalStyle('h3', 'color', $contentHeadingColor);
|
|
}
|
|
return $newsletterClone;
|
|
}
|
|
|
|
private function updateStyleDefinition(string $selectors, NewsletterEntity $newsletter, $properties) {
|
|
// For Backward compatibility do not apply styles for content unless the template was edited with the editor
|
|
// and user visually checked and is aware of updated styles feature.
|
|
$isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
|
|
if (!$isSavedWithStyledWooBlock) {
|
|
return $properties;
|
|
}
|
|
|
|
if (!is_array($properties)) {
|
|
$properties = [];
|
|
}
|
|
$fontFamily = $newsletter->getGlobalStyle('text', 'fontFamily');
|
|
$headingFontFamily = $newsletter->getGlobalStyle('woocommerce', 'headingFontFamily');
|
|
$fontSize = $newsletter->getGlobalStyle('text', 'fontSize');
|
|
$brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
|
|
$contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;
|
|
|
|
// Update font family if it's set in the editor
|
|
if ($fontFamily && !empty($properties['font-family'])) {
|
|
$properties['font-family'] = $fontFamily;
|
|
}
|
|
// Update font size for inner content
|
|
if ($fontSize && ($selectors === '#body_content_inner')) {
|
|
$properties['font-size'] = $fontSize;
|
|
}
|
|
|
|
// Update heading font sizes and font family
|
|
$supportedHeadings = ['h1', 'h2', 'h3'];
|
|
foreach ($supportedHeadings as $heading) {
|
|
$headingFontSize = $newsletter->getGlobalStyle($heading, 'fontSize');
|
|
if ($headingFontSize && ($selectors === $heading)) {
|
|
$properties['font-size'] = $headingFontSize;
|
|
}
|
|
if ($headingFontFamily && ($selectors === $heading)) {
|
|
$properties['font-family'] = $headingFontFamily;
|
|
}
|
|
if ($contentHeadingColor && ($selectors === $heading)) {
|
|
$properties['color'] = $contentHeadingColor;
|
|
}
|
|
}
|
|
return $properties;
|
|
}
|
|
}
|