PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / WooCommerce / TransactionalEmails / Renderer.php
mailpoet / lib / WooCommerce / TransactionalEmails Last commit date
ContentPreprocessor.php 1 year ago FontFamilyValidator.php 10 months ago Renderer.php 10 months ago Template.php 1 year ago index.php 3 years ago
Renderer.php
264 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\WooCommerce\TransactionalEmails;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Entities\NewsletterEntity;
9 use MailPoet\Newsletter\Renderer\Renderer as NewsletterRenderer;
10 use MailPoet\Newsletter\Shortcodes\Shortcodes;
11 use MailPoetVendor\csstidy;
12 use MailPoetVendor\csstidy_print;
13
14 class Renderer {
15 const CONTENT_CONTAINER_ID = 'mailpoet_woocommerce_container';
16
17 /** @var csstidy */
18 private $cssParser;
19
20 /** @var NewsletterRenderer */
21 private $renderer;
22
23 /** @var string */
24 private $htmlBeforeContent;
25
26 /** @var string */
27 private $htmlAfterContent;
28
29 /** @var Shortcodes */
30 private $shortcodes;
31
32 /** @var FontFamilyValidator */
33 private $fontFamilyValidator;
34
35 public function __construct(
36 csstidy $cssParser,
37 NewsletterRenderer $renderer,
38 Shortcodes $shortcodes,
39 FontFamilyValidator $fontFamilyValidator
40 ) {
41 $this->cssParser = $cssParser;
42 $this->htmlBeforeContent = '';
43 $this->htmlAfterContent = '';
44 $this->renderer = $renderer;
45 $this->shortcodes = $shortcodes;
46 $this->fontFamilyValidator = $fontFamilyValidator;
47 }
48
49 public function render(NewsletterEntity $newsletter, ?string $subject = null) {
50 $preparedNewsletter = $this->prepareNewsletterForRendering($newsletter);
51 $renderedNewsletter = $this->renderer->renderAsPreview($preparedNewsletter, 'html', $subject);
52 $headingText = $subject ?? '';
53
54 $renderedHtml = $this->processShortcodes($preparedNewsletter, $renderedNewsletter);
55
56 $renderedHtml = str_replace(ContentPreprocessor::WC_HEADING_PLACEHOLDER, $headingText, $renderedHtml);
57 $html = explode(ContentPreprocessor::WC_CONTENT_PLACEHOLDER, $renderedHtml);
58 $this->htmlBeforeContent = $html[0];
59 $this->htmlAfterContent = $html[1];
60 }
61
62 public function getHTMLBeforeContent() {
63 if (empty($this->htmlBeforeContent)) {
64 throw new \Exception("You should call 'render' before 'getHTMLBeforeContent'");
65 }
66 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;">';
67 }
68
69 public function getHTMLAfterContent() {
70 if (empty($this->htmlAfterContent)) {
71 throw new \Exception("You should call 'render' before 'getHTMLAfterContent'");
72 }
73 return '<!--WooContent--></td></tr></table></div></div></div>' . $this->htmlAfterContent;
74 }
75
76 /**
77 * In this method we alter the rendered content that is output when processing the WooCommerce email template.
78 * - We update inlined font-family rules in the content block generated by Woo
79 */
80 public function updateRenderedContent(NewsletterEntity $newsletter, string $content): string {
81 $isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
82 // For Backward compatibility do not apply styles for content unless the template was edited with the editor
83 // and user visually checked and is aware of updated styles feature.
84 if (!$isSavedWithStyledWooBlock) {
85 return $content;
86 }
87 $contentParts = explode('<!--WooContent-->', $content);
88 if (count($contentParts) !== 3) {
89 return $content;
90 }
91 [$beforeWooContent, $wooContent, $afterWooContent] = $contentParts;
92 $fontFamily = $newsletter->getGlobalStyle('text', 'fontFamily');
93
94 // Only replace font family if user provided a custom font
95 if (!empty($fontFamily)) {
96 // Validate and sanitize font family
97 $safeFontFamily = $this->fontFamilyValidator->validateFontFamily($fontFamily);
98 $wooContent = $this->replaceFontFamily($wooContent, $safeFontFamily);
99 }
100
101 return implode('', [$beforeWooContent, $wooContent, $afterWooContent]);
102 }
103
104 private function replaceFontFamily(string $content, string $fontFamily): string {
105 $processor = new \WP_HTML_Tag_Processor($content);
106
107 // Process all tags that might have style attributes
108 while ($processor->next_tag()) {
109 $styleAttr = $processor->get_attribute('style');
110 if (empty($styleAttr)) {
111 continue;
112 }
113
114 // Fast path: avoid parsing when no font-family present
115 if (stripos($styleAttr, 'font-family') === false) {
116 continue;
117 }
118
119 // Use CSS parser for CSS manipulation
120 // Wrap inline style in a selector so csstidy can parse it properly
121 $cssParser = new csstidy();
122 $cssParser->settings['compress_colors'] = false;
123 $cssParser->settings['remove_last_;'] = false;
124 $cssParser->parse('x { ' . $styleAttr . ' }');
125
126 // Update font-family in parsed CSS
127 $updated = false;
128 foreach ($cssParser->css as $index => $rules) {
129 foreach ($rules as $selector => $properties) {
130 if (isset($properties['font-family'])) {
131 $properties['font-family'] = $fontFamily;
132 $cssParser->css[$index][$selector] = $properties;
133 $updated = true;
134 }
135 }
136 }
137
138 // Only update the attribute if we made changes
139 if ($updated) {
140 /** @var csstidy_print $print */
141 $print = $cssParser->print;
142 $fullCss = $print->plain();
143 // Extract the properties from "x { ... }" format
144 if (preg_match('/x\s*{\s*(.*)\s*}/s', $fullCss, $matches)) {
145 $updatedStyle = trim($matches[1]);
146 $processor->set_attribute('style', $updatedStyle);
147 }
148 }
149 }
150
151 return $processor->get_updated_html();
152 }
153
154 /**
155 * In this method we alter CSS that is later inlined into the WooCommerce email template. WooCommerce use Emogrifier to inline CSS.
156 * The inlining is called after the rendering and after the modifications we apply to the rendered content in self::updateRenderedContent
157 * - We prefix the original selectors to avoid inlining those rules into content added int the MailPoet's editor.
158 * - We update the font-family in the original CSS if it's set in the editor.
159 * - We update the font-size for the inner content if it's set in the editor.
160 */
161 public function enhanceCss(string $css, NewsletterEntity $newsletter): string {
162 $this->cssParser->settings['compress_colors'] = false;
163 $this->cssParser->parse($css);
164 foreach ($this->cssParser->css as $index => $rules) {
165 $this->cssParser->css[$index] = [];
166 foreach ($rules as $selectors => $properties) {
167 $properties = $this->updateStyleDefinition($selectors, $newsletter, $properties);
168 $selectors = explode(',', $selectors);
169 $selectors = array_map(function($selector) {
170 return '#' . self::CONTENT_CONTAINER_ID . ' ' . $selector;
171 }, $selectors);
172 $selectors = implode(',', $selectors);
173
174 $this->cssParser->css[$index][$selectors] = $properties;
175 }
176 }
177
178 /** @var csstidy_print */
179 $print = $this->cssParser->print;
180 $css = $print->plain();
181
182 // Enforce the special heading color for the WooCommerce email header
183 $wooHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'headingFontColor');
184 if ($wooHeadingColor) {
185 $css .= "#mailpoet-woo-email-header { color: $wooHeadingColor !important; }";
186 }
187 return $css;
188 }
189
190 private function processShortcodes(NewsletterEntity $newsletter, $content) {
191 $this->shortcodes->setQueue(null);
192 $this->shortcodes->setSubscriber(null);
193 $this->shortcodes->setNewsletter($newsletter);
194 return $this->shortcodes->replace($content);
195 }
196
197 /**
198 * This method prepares the newsletter for rendering
199 * - We ensure that the font-family and branding color are used as default for all headings
200 */
201 private function prepareNewsletterForRendering(NewsletterEntity $newsletter): NewsletterEntity {
202 $newsletterClone = clone($newsletter);
203 $headingFontFamily = $this->fontFamilyValidator->validateFontFamily($newsletter->getGlobalStyle('woocommerce', 'headingFontFamily'));
204 if ($headingFontFamily !== FontFamilyValidator::DEFAULT_FONT_FAMILY) {
205 $newsletterClone->setGlobalStyle('h1', 'fontFamily', $headingFontFamily);
206 $newsletterClone->setGlobalStyle('h2', 'fontFamily', $headingFontFamily);
207 $newsletterClone->setGlobalStyle('h3', 'fontFamily', $headingFontFamily);
208 }
209 $brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
210 $contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;
211 if ($contentHeadingColor) {
212 $newsletterClone->setGlobalStyle('h1', 'color', $contentHeadingColor);
213 $newsletterClone->setGlobalStyle('h2', 'color', $contentHeadingColor);
214 $newsletterClone->setGlobalStyle('h3', 'color', $contentHeadingColor);
215 }
216 return $newsletterClone;
217 }
218
219 private function updateStyleDefinition(string $selectors, NewsletterEntity $newsletter, $properties) {
220 // For Backward compatibility do not apply styles for content unless the template was edited with the editor
221 // and user visually checked and is aware of updated styles feature.
222 $isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
223 if (!$isSavedWithStyledWooBlock) {
224 return $properties;
225 }
226
227 if (!is_array($properties)) {
228 $properties = [];
229 }
230 $fontFamilyRaw = $newsletter->getGlobalStyle('text', 'fontFamily');
231 $headingFontFamilyRaw = $newsletter->getGlobalStyle('woocommerce', 'headingFontFamily');
232 $fontFamily = $fontFamilyRaw !== null ? $this->fontFamilyValidator->validateFontFamily($fontFamilyRaw) : null;
233 $headingFontFamily = $headingFontFamilyRaw !== null ? $this->fontFamilyValidator->validateFontFamily($headingFontFamilyRaw) : null;
234 $fontSize = $newsletter->getGlobalStyle('text', 'fontSize');
235 $brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
236 $contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;
237
238 // Update font family if it's set in the editor
239 if (!empty($fontFamilyRaw) && !empty($properties['font-family'])) {
240 $properties['font-family'] = $fontFamily;
241 }
242 // Update font size for inner content
243 if ($fontSize && ($selectors === '#body_content_inner')) {
244 $properties['font-size'] = $fontSize;
245 }
246
247 // Update heading font sizes and font family
248 $supportedHeadings = ['h1', 'h2', 'h3'];
249 foreach ($supportedHeadings as $heading) {
250 $headingFontSize = $newsletter->getGlobalStyle($heading, 'fontSize');
251 if ($headingFontSize && ($selectors === $heading)) {
252 $properties['font-size'] = $headingFontSize;
253 }
254 if (!empty($headingFontFamilyRaw) && ($selectors === $heading)) {
255 $properties['font-family'] = $headingFontFamily;
256 }
257 if ($contentHeadingColor && ($selectors === $heading)) {
258 $properties['color'] = $contentHeadingColor;
259 }
260 }
261 return $properties;
262 }
263 }
264