PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.3
WooCommerce v10.9.3
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / packages / email-editor / src / Engine / Renderer / class-renderer.php
woocommerce / packages / email-editor / src / Engine / Renderer Last commit date
ContentRenderer 1 month ago class-html2text-exception.php 10 months ago class-html2text.php 10 months ago class-renderer.php 1 month ago interface-css-inliner.php 1 year ago template-canvas.css 1 month ago template-canvas.php 1 month ago
class-renderer.php
305 lines
1 <?php
2 /**
3 * This file is part of the WooCommerce Email Editor package.
4 *
5 * @package Automattic\WooCommerce\EmailEditor
6 */
7
8 declare(strict_types = 1);
9 namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer;
10
11 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer;
12 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Process_Manager;
13 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
14 use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates;
15 use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
16 use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
17 use WP_Style_Engine;
18
19 /**
20 * Class Renderer
21 */
22 class Renderer {
23 /**
24 * Theme controller
25 *
26 * @var Theme_Controller
27 */
28 private Theme_Controller $theme_controller;
29
30 /**
31 * Content renderer
32 *
33 * @var Content_Renderer
34 */
35 private Content_Renderer $content_renderer;
36
37 /**
38 * Templates
39 *
40 * @var Templates
41 */
42 private Templates $templates;
43
44 /**
45 * Css inliner
46 *
47 * @var Css_Inliner
48 */
49 private Css_Inliner $css_inliner;
50
51 /**
52 * Process manager
53 *
54 * @var Process_Manager
55 */
56 private Process_Manager $process_manager;
57
58 /**
59 * Personalization tags registry
60 *
61 * @var Personalization_Tags_Registry
62 */
63 private Personalization_Tags_Registry $personalization_tags_registry;
64
65 /**
66 * Map of placeholders to full HTML comment tags for restoration.
67 *
68 * @var array
69 */
70 private array $personalization_tag_placeholders = array();
71
72 const TEMPLATE_FILE = 'template-canvas.php';
73 const TEMPLATE_STYLES_FILE = 'template-canvas.css';
74
75
76 /**
77 * Renderer constructor.
78 *
79 * @param Content_Renderer $content_renderer Content renderer.
80 * @param Templates $templates Templates.
81 * @param Css_Inliner $css_inliner CSS Inliner.
82 * @param Theme_Controller $theme_controller Theme controller.
83 * @param Personalization_Tags_Registry $personalization_tags_registry Personalization tags registry.
84 * @param Process_Manager $process_manager Process manager.
85 */
86 public function __construct(
87 Content_Renderer $content_renderer,
88 Templates $templates,
89 Css_Inliner $css_inliner,
90 Theme_Controller $theme_controller,
91 Personalization_Tags_Registry $personalization_tags_registry,
92 Process_Manager $process_manager
93 ) {
94 $this->content_renderer = $content_renderer;
95 $this->templates = $templates;
96 $this->theme_controller = $theme_controller;
97 $this->css_inliner = $css_inliner;
98 $this->personalization_tags_registry = $personalization_tags_registry;
99 $this->process_manager = $process_manager;
100 }
101
102 /**
103 * Renders the email template
104 *
105 * @param \WP_Post $post Post object.
106 * @param string $subject Email subject.
107 * @param string $pre_header An email preheader or preview text is the short snippet of text that follows the subject line in an inbox. See https://kb.mailpoet.com/article/418-preview-text.
108 * @param string $language Email language.
109 * @param string $meta_robots Optional string. Can be left empty for sending, but you can provide a value (e.g. noindex, nofollow) when you want to display email html in a browser.
110 * @param string $template_slug Optional block template slug used for cases when email doesn't have associated template.
111 * @return array
112 */
113 public function render( \WP_Post $post, string $subject, string $pre_header, string $language, string $meta_robots = '', string $template_slug = '' ): array {
114 if ( ! $template_slug ) {
115 $template_slug = get_page_template_slug( $post ) ? get_page_template_slug( $post ) : 'email-general';
116 }
117 /** @var \WP_Block_Template $template */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
118 $template = $this->templates->get_block_template( $template_slug );
119
120 $previous_rendering_context = $this->content_renderer->get_current_rendering_context();
121 try {
122 $rendering_context = $this->content_renderer->create_rendering_context( $language, $post, $template );
123 $this->content_renderer->set_rendering_context( $rendering_context );
124 $email_styles = $this->theme_controller->get_styles();
125 $content_result = $this->content_renderer->render_without_css_inline( $post, $template );
126 $template_html = $content_result['html'];
127 $content_styles = $content_result['styles'];
128 $layout = $this->theme_controller->get_layout_settings();
129
130 ob_start();
131 include self::TEMPLATE_FILE;
132 $rendered_template = (string) ob_get_clean();
133
134 $template_styles =
135 WP_Style_Engine::compile_css(
136 array(
137 'background-color' => $email_styles['color']['background'] ?? 'inherit',
138 'color' => $email_styles['color']['text'] ?? 'inherit',
139 'padding-top' => $email_styles['spacing']['padding']['top'] ?? '0px',
140 'padding-bottom' => $email_styles['spacing']['padding']['bottom'] ?? '0px',
141 'font-family' => $email_styles['typography']['fontFamily'] ?? 'inherit',
142 'line-height' => $email_styles['typography']['lineHeight'] ?? '1.5',
143 'font-size' => $email_styles['typography']['fontSize'] ?? 'inherit',
144 'direction' => $rendering_context->get_text_direction(),
145 'text-align' => $rendering_context->get_default_text_align(),
146 ),
147 'body, .email_layout_wrapper'
148 );
149 $template_styles .= WP_Style_Engine::compile_css(
150 array(
151 'direction' => $rendering_context->get_text_direction(),
152 'text-align' => $rendering_context->get_default_text_align(),
153 ),
154 '.email_content_wrapper, .email_preheader'
155 );
156 $template_styles .= '.email_layout_wrapper { box-sizing: border-box;}';
157 $template_styles .= file_get_contents( __DIR__ . '/' . self::TEMPLATE_STYLES_FILE );
158 $template_styles = wp_strip_all_tags( (string) apply_filters( 'woocommerce_email_renderer_styles', $template_styles, $post ) );
159
160 // Single CSS inlining pass: combine content and template styles, then inline all at once.
161 $all_styles = '<style>' . $template_styles . $content_styles . '</style>';
162 $rendered_template = $this->inline_css_styles( $all_styles . $rendered_template );
163
164 // Postprocess after CSS inlining (border normalization, CSS variable replacement, etc.).
165 $rendered_template = $this->process_manager->postprocess( $rendered_template );
166 $rendered_template = $this->apply_html_attributes( $rendered_template, $rendering_context );
167
168 // This is a workaround to support link :hover in some clients. Ideally we would remove the ability to set :hover
169 // however this is not possible using the color panel from Gutenberg.
170 if ( isset( $email_styles['elements']['link'][':hover']['color']['text'] ) ) {
171 $rendered_template = str_replace( '<!-- Forced Styles -->', '<style>a:hover { color: ' . esc_attr( $email_styles['elements']['link'][':hover']['color']['text'] ) . ' !important; }</style>', $rendered_template );
172 }
173
174 return array(
175 'html' => $rendered_template,
176 'text' => $this->render_text_version( $rendered_template ),
177 );
178 } finally {
179 $this->content_renderer->restore_rendering_context( $previous_rendering_context );
180 }
181 }
182
183 /**
184 * Inlines CSS styles into the HTML
185 *
186 * @param string $template HTML template.
187 * @return string
188 */
189 private function inline_css_styles( $template ) {
190 return $this->css_inliner->from_html( $template )->inline_css()->render();
191 }
192
193 /**
194 * Apply document-level language and direction attributes after CSS inlining.
195 *
196 * @param string $template HTML template.
197 * @param Rendering_Context $rendering_context Rendering context.
198 * @return string
199 */
200 private function apply_html_attributes( string $template, Rendering_Context $rendering_context ): string {
201 $processor = new \WP_HTML_Tag_Processor( $template );
202 if ( ! $processor->next_tag( array( 'tag_name' => 'html' ) ) ) {
203 return $template;
204 }
205
206 $language = $rendering_context->get_language();
207 if ( $language ) {
208 $processor->set_attribute( 'lang', str_replace( '_', '-', $language ) );
209 }
210 $processor->set_attribute( 'dir', $rendering_context->get_text_direction() );
211
212 return $processor->get_updated_html();
213 }
214
215 /**
216 * Renders the text version of the email template.
217 *
218 * @param string $template HTML template.
219 * @return string
220 */
221 private function render_text_version( $template ) {
222 $template = ( mb_detect_encoding( $template, 'UTF-8', true ) ) ? $template : mb_convert_encoding( $template, 'UTF-8', mb_list_encodings() );
223
224 // Ensure template is a string before processing.
225 if ( ! is_string( $template ) ) {
226 return '';
227 }
228
229 // Preserve personalization tags by temporarily replacing them with unique placeholders.
230 $template = $this->preserve_personalization_tags( $template );
231
232 $result = Html2Text::convert( (string) $template, array( 'ignore_errors' => true ) );
233 if ( ! $result ) {
234 return '';
235 }
236
237 // Restore personalization tags from placeholders.
238 $result = $this->restore_personalization_tags( $result );
239
240 return $result;
241 }
242
243 /**
244 * Preserves personalization tags by replacing them with unique placeholders (not inside comments).
245 *
246 * @param string $template HTML template.
247 * @return string
248 */
249 private function preserve_personalization_tags( string $template ): string {
250 $all_registered_tags = $this->personalization_tags_registry->get_all();
251 $this->personalization_tag_placeholders = array();
252 $counter = 0;
253
254 $base_tokens = array(); // All the tokens used in the email, e.g. [woocommerce/customer-username].
255 $token_prefixes = array(); // All the used prefixes, e.g. woocommerce, mailpoet, etc.
256 foreach ( $all_registered_tags as $tag ) {
257 $token = $tag->get_token(); // E.g. [woocommerce/customer-username].
258 $base_tokens[ $token ] = true;
259 // Remove brackets for regex matching, escape for regex.
260 $token_prefixes[] = preg_quote( substr( $token, 1, -1 ), '/' );
261 }
262
263 if ( empty( $token_prefixes ) ) {
264 return $template;
265 }
266
267 // Match all of the code comments that look like a personalization tags.
268 $pattern = '/<!--\[(' . implode( '|', $token_prefixes ) . ')(?:\s+[^\]]*)?\]-->/';
269
270 $template = preg_replace_callback(
271 $pattern,
272 function ( $matches ) use ( &$counter, $base_tokens ) {
273 // $matches[1] is the token without brackets, add brackets for lookup.
274 $base_token = '[' . $matches[1] . ']';
275 if ( isset( $base_tokens[ $base_token ] ) ) {
276 $placeholder = 'PERSONALIZATION_TAG_PLACEHOLDER_' . $counter;
277 $this->personalization_tag_placeholders[ $placeholder ] = $matches[0];
278 ++$counter;
279 return $placeholder;
280 }
281 return $matches[0];
282 },
283 $template
284 );
285
286 return $template ?? '';
287 }
288
289 /**
290 * Restores personalization tags from placeholders
291 *
292 * @param string $text Text content.
293 * @return string
294 */
295 private function restore_personalization_tags( string $text ): string {
296 if ( empty( $this->personalization_tag_placeholders ) ) {
297 return $text;
298 }
299 foreach ( $this->personalization_tag_placeholders as $placeholder => $html_comment ) {
300 $text = str_replace( $placeholder, $html_comment, $text );
301 }
302 return $text;
303 }
304 }
305