PluginProbe
YayMail – WooCommerce Email Customizer / trunk
YayMail – WooCommerce Email Customizer vtrunk
4.4.4 4.4.3 4.4.2 4.4.1 trunk 1.9.6 2.1.4 2.1.5 3.2.2 3.2.6 3.2.7.1 3.2.8.1 3.2.9 3.3 3.3.1 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4 3.4.1 3.4.2 3.4.3 All 55 releases
yaymail / src / Utils / TemplateHelpers.php

TemplateHelpers.php in YayMail – WooCommerce Email Customizer trunk, at src/Utils/TemplateHelpers.php

601 lines 21.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace YayMail\Utils;
4
5 use YayMail\Constants\AttributesData;
6 use YayMail\Constants\TemplatesData;
7 use YayMail\Shortcodes\ShortcodesExecutor;
8 use YayMail\YayMailTemplate;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * TemplateHelpers Classes
14 * Define all utility functions to be used inside templates
15 */
16 class TemplateHelpers {
17
18 /**
19 * @deprecated
20 */
21 public static function get_attribute_data( $attribute_data ) {
22 $data = [];
23 foreach ( $attribute_data as $key => $attribute ) {
24 if ( 'image_box' === $key || 'image_list' === $key || 'text_list' === $key ) {
25 if ( isset( $attribute['column_1'] ) ) {
26 $data['column_1'] = self::get_attribute_data( $attribute['column_1'] );
27 }
28 if ( isset( $attribute['column_2'] ) ) {
29 $data['column_2'] = self::get_attribute_data( $attribute['column_2'] );
30 }
31 if ( isset( $attribute['column_3'] ) ) {
32 $data['column_3'] = self::get_attribute_data( $attribute['column_3'] );
33 }
34 } elseif ( 'inner_background_color' === $key ) {
35 $data[ $key ] = $attribute;
36 } else {
37 $data[ $key ] = $attribute['default_value'];
38 }
39 }
40 return $data;
41 }
42
43 public static function get_spacing_value( $spacing, $unit = 'px' ) {
44 $unit = esc_attr( $unit );
45 return sprintf(
46 '%d%s %d%s %d%s %d%s',
47 isset( $spacing['top'] ) ? $spacing['top'] : 0,
48 $unit,
49 isset( $spacing['right'] ) ? $spacing['right'] : 0,
50 $unit,
51 isset( $spacing['bottom'] ) ? $spacing['bottom'] : 0,
52 $unit,
53 isset( $spacing['left'] ) ? $spacing['left'] : 0,
54 $unit
55 );
56 }
57
58 public static function get_border_radius_value( $border_radius, $unit = 'px' ) {
59 $unit = esc_attr( $unit );
60 return sprintf(
61 '%d%s %d%s %d%s %d%s',
62 isset( $border_radius['top_left'] ) ? $border_radius['top_left'] : 0,
63 $unit,
64 isset( $border_radius['top_right'] ) ? $border_radius['top_right'] : 0,
65 $unit,
66 isset( $border_radius['bottom_right'] ) ? $border_radius['bottom_right'] : 0,
67 $unit,
68 isset( $border_radius['bottom_left'] ) ? $border_radius['bottom_left'] : 0,
69 $unit
70 );
71 }
72
73 /**
74 * Inner border radius for column_layout children: only the four outer corners of the row
75 * (first column left, last column right; single column gets all four).
76 *
77 * @param array $inner_border_radius Keys top_left, top_right, bottom_left, bottom_right.
78 * @param int $total_columns amount_of_columns from parent column_layout.
79 * @param int $column_index Zero-based column index.
80 * @return array{top_left:int,top_right:int,bottom_right:int,bottom_left:int}
81 */
82 public static function get_inner_column_border_radius( $inner_border_radius, $total_columns, $column_index ) {
83 $tl = isset( $inner_border_radius['top_left'] ) ? (int) $inner_border_radius['top_left'] : 0;
84 $tr = isset( $inner_border_radius['top_right'] ) ? (int) $inner_border_radius['top_right'] : 0;
85 $br = isset( $inner_border_radius['bottom_right'] ) ? (int) $inner_border_radius['bottom_right'] : 0;
86 $bl = isset( $inner_border_radius['bottom_left'] ) ? (int) $inner_border_radius['bottom_left'] : 0;
87
88 $total_columns = max( 1, (int) $total_columns );
89 $column_index = max( 0, (int) $column_index );
90
91 if ( 1 === $total_columns ) {
92 return [
93 'top_left' => $tl,
94 'top_right' => $tr,
95 'bottom_right' => $br,
96 'bottom_left' => $bl,
97 ];
98 }
99
100 if ( 0 === $column_index ) {
101 return [
102 'top_left' => $tl,
103 'top_right' => 0,
104 'bottom_right' => 0,
105 'bottom_left' => $bl,
106 ];
107 }
108
109 if ( $column_index === $total_columns - 1 ) {
110 return [
111 'top_left' => 0,
112 'top_right' => $tr,
113 'bottom_right' => $br,
114 'bottom_left' => 0,
115 ];
116 }
117
118 return [
119 'top_left' => 0,
120 'top_right' => 0,
121 'bottom_right' => 0,
122 'bottom_left' => 0,
123 ];
124 }
125
126 public static function get_dimension_value( $dimension, $unit = 'px' ) {
127 $unit = esc_attr( $unit );
128 $dimension = floatval( $dimension );
129 return "$dimension$unit";
130 }
131
132 public static function get_font_family_value( $font_family ) {
133 if ( empty( $font_family ) ) {
134 return 'inherit';
135 }
136 return str_replace( [ '\"','"' ], '', $font_family );
137 }
138
139 public static function wp_kses_allowed_html( $cus_attr_tags = [] ) {
140 $allowed_html_tags = wp_kses_allowed_html( 'post' );
141 $allowed_html_tags['style'] = true;
142 $allowed_html_tags['html'] = [];
143 $allowed_html_tags['header'] = [];
144 $allowed_html_tags['meta'] = [];
145 $allowed_html_attr = $cus_attr_tags;
146
147 $allowed_html_attr ['data-yaymail-element-type'] = true;
148 $allowed_html_attr ['charset'] = true;
149 $allowed_html_attr ['http-equiv'] = true;
150 $allowed_html_attr ['content'] = true;
151 $allowed_html_attr ['name'] = true;
152 return array_map(
153 function ( $item ) use ( $allowed_html_attr ) {
154 return is_array( $item ) ? array_merge( $item, $allowed_html_attr ) : $item;
155 },
156 $allowed_html_tags
157 );
158 }
159
160 public static function get_style( $css_properties = [] ) {
161 return implode(
162 ';',
163 array_map(
164 function ( $css_value, $css_name ) {
165 return "$css_name:$css_value";
166 },
167 $css_properties,
168 array_keys( $css_properties )
169 )
170 ) . ';';
171 }
172
173 public static function wrap_element_content( $content_html, $element, $wrapper_style = null ) {
174 $html = yaymail_get_content(
175 'templates/elements/element-wrapper.php',
176 [
177 'content_html' => $content_html,
178 'element' => $element,
179 'wrapper_style' => $wrapper_style,
180 ]
181 );
182
183 yaymail_kses_post_e( $html );
184 }
185
186 /**
187 * The function returns the value based on the provided key, default value, and placeholder
188 * flag.
189 *
190 * @param key Key parameter
191 * @param default The default value is the value that will be returned if the key is empty or if the
192 * is_placeholder parameter is false.
193 * @param is_placeholder A boolean value indicating whether the value should be treated as a
194 * placeholder or not.
195 *
196 * @return either the value of the variable or the placeholder "[[]]" depending on
197 * the values of the and variables.
198 */
199 public static function get_content_as_placeholder( $key, $default, $is_placeholder ) {
200 return $is_placeholder && ! empty( $key ) ? "[[{$key}]]" : $default;
201 }
202
203 public static function get_booking_from_order( $order ) {
204 $booking_ids = [];
205
206 if ( null !== $order ) {
207 if ( is_callable( 'WC_Booking_Data_Store::get_booking_ids_from_order_id' ) ) {
208 $booking_data = new \WC_Booking_Data_Store();
209 $booking_ids = $booking_data->get_booking_ids_from_order_id( $order->get_id() );
210 }
211
212 if ( ! empty( $booking_ids ) ) {
213 return new \WC_Booking( $booking_ids[0] );
214 }
215 }
216
217 return null;
218 }
219
220 public static function get_font_size( $size, $is_subtitle = false ) {
221 if ( 'default' === $size && $is_subtitle ) {
222 return '13px';
223 }
224 $result = isset( AttributesData::TITLE_SIZE_OPTIONS[ $size ] ) ? AttributesData::TITLE_SIZE_OPTIONS[ $size ] : 16;
225 $result .= 'px';
226 return $result;
227 }
228
229 /**
230 * Remove empty shortcodes from the content
231 *
232 * @param string $content The content to remove empty shortcodes from
233 * @return string The content with empty shortcodes removed
234 * @since 4.0.2
235 */
236 public static function remove_empty_shortcodes( $content ) {
237 $content = preg_replace( '/<p\b[^>]*>\[yaymail_[^\]]*\]<\/p>/i', '', $content );
238 $content = preg_replace( '/\[yaymail_[^\]]*\]/', '', $content );
239 return $content;
240 }
241
242 public static function convert_rgb_to_hex( $color ) {
243 if ( is_string( $color ) && strpos( $color, 'rgb' ) === 0 ) {
244 $rgb = str_replace( 'rgb(', '', $color );
245 $rgb = str_replace( ')', '', $rgb );
246 $rgb = explode( ',', $rgb );
247 $hex = '#';
248 $hex .= str_pad( dechex( $rgb[0] ), 2, '0', STR_PAD_LEFT );
249 $hex .= str_pad( dechex( $rgb[1] ), 2, '0', STR_PAD_LEFT );
250 $hex .= str_pad( dechex( $rgb[2] ), 2, '0', STR_PAD_LEFT );
251 return $hex;
252 } else {
253 return $color;
254 }
255 }
256
257 /**
258 * Find element by id in the list of elements
259 *
260 * @param string $id The id of the element to find.
261 * @param array $list_elements The list of elements to search in.
262 * @return array|null The element if found, null otherwise
263 * @since 4.1.0
264 */
265 public static function find_element_by_id( $id, $list_elements ) {
266 foreach ( $list_elements as $element ) {
267 if ( $element['id'] === $id ) {
268 return $element;
269 }
270 if ( $element['children'] && count( $element['children'] ) > 0 ) {
271 $result = self::find_element_by_id( $id, $element['children'] );
272 if ( $result ) {
273 return $result;
274 }
275 }
276 }
277 return null;
278 }
279
280 public static function find_parent_element( $id, $list_elements ) {
281
282 foreach ( $list_elements as $element ) {
283 if ( empty( $element['children'] ) ) {
284 continue;
285 }
286 if ( in_array( $id, array_column( $element['children'], 'id' ) ) ) {
287 return $element;
288 }
289 $sub_query = self::find_parent_element( $id, $element['children'] );
290 if ( $sub_query ) {
291 return $sub_query;
292 }
293 }
294
295 return null;
296 }
297
298 public static function get_current_column_index( $id, $list_elements ) {
299 $element = self::find_element_by_id( $id, $list_elements );
300 if ( empty( $element ) ) {
301 return 0;
302 }
303 $parent_element = self::find_parent_element( $element['id'], $list_elements );
304 if ( empty( $parent_element ) ) {
305 return 0;
306 }
307 $current_column_index = array_search( $element['id'], array_column( $parent_element['children'], 'id' ) );
308 return $current_column_index;
309 }
310
311 public static function get_border_css_value( $border ) {
312 if ( $border['side'] === 'none' ) {
313 return '';
314 }
315 if ( $border['side'] === 'all' ) {
316 return self::get_style(
317 [
318 'border' => self::get_border_style( $border ),
319 ]
320 );
321 }
322 if ( $border['side'] === 'top' ) {
323 return self::get_style(
324 [
325 'border-top' => self::get_border_style( $border ),
326 ]
327 );
328 }
329 if ( $border['side'] === 'bottom' ) {
330 return self::get_style(
331 [
332 'border-bottom' => self::get_border_style( $border ),
333 ]
334 );
335 }
336 if ( $border['side'] === 'right' ) {
337 return self::get_style(
338 [
339 'border-right' => self::get_border_style( $border ),
340 ]
341 );
342 }
343 if ( $border['side'] === 'left' ) {
344 return self::get_style(
345 [
346 'border-left' => self::get_border_style( $border ),
347 ]
348 );
349 }
350 if ( $border['side'] === 'custom' ) {
351 return self::get_style(
352 [
353 'border-top' => self::get_border_style(
354 [
355 'width' => $border['custom']['top'],
356 'style' => $border['style'],
357 'color' => $border['color'],
358 ]
359 ),
360 'border-right' => self::get_border_style(
361 [
362 'width' => $border['custom']['right'],
363 'style' => $border['style'],
364 'color' => $border['color'],
365 ]
366 ),
367 'border-bottom' => self::get_border_style(
368 [
369 'width' => $border['custom']['bottom'],
370 'style' => $border['style'],
371 'color' => $border['color'],
372 ]
373 ),
374 'border-left' => self::get_border_style(
375 [
376 'width' => $border['custom']['left'],
377 'style' => $border['style'],
378 'color' => $border['color'],
379 ]
380 ),
381 ]
382 );
383 }//end if
384 return '';
385 }
386
387 public static function get_border_style( $border, $unit = 'px' ) {
388 $unit = esc_attr( $unit );
389 return sprintf(
390 '%d%s %s %s',
391 $border['width'],
392 $unit,
393 $border['style'],
394 $border['color']
395 );
396 }
397
398 public static function sanitize_elements_recursive( $elements ) {
399 if ( ! is_array( $elements ) ) {
400 return [];
401 }
402
403 foreach ( $elements as &$element ) {
404 if ( isset( $element['data']['rich_text'] ) ) {
405 $element['data']['rich_text'] = yaymail_kses_post( $element['data']['rich_text'], $allowed );
406 }
407
408 if ( isset( $element['data']['title'] ) ) {
409 $element['data']['title'] = yaymail_kses_post( $element['data']['title'] );
410 }
411
412 // Recursive cho nested elements
413 if ( isset( $element['children'] ) ) {
414 $element['children'] = self::sanitize_elements_recursive( $element['children'] );
415 }
416 }
417
418 return $elements;
419 }
420
421 public static function replace_color_paths( $value ) {
422 return $value;
423 }
424
425 /**
426 * Ensure each nested element has parentId pointing to its direct parent.
427 * Root-level elements have no parentId (customizer "Select parent" relies on this).
428 *
429 * @param array $elements Template elements tree.
430 * @param string|null $parent_id Parent element id for direct children.
431 * @return array
432 */
433 public static function normalize_elements_parent_ids( $elements, $parent_id = null ) {
434 if ( ! is_array( $elements ) ) {
435 return [];
436 }
437
438 $normalized = [];
439
440 foreach ( $elements as $element ) {
441 if ( ! is_array( $element ) ) {
442 continue;
443 }
444
445 if ( null !== $parent_id && '' !== $parent_id ) {
446 $element['parentId'] = $parent_id;
447 } else {
448 unset( $element['parentId'] );
449 }
450
451 if ( ! empty( $element['children'] ) && is_array( $element['children'] ) ) {
452 $child_parent_id = isset( $element['id'] ) ? $element['id'] : null;
453 $element['children'] = self::normalize_elements_parent_ids( $element['children'], $child_parent_id );
454 }
455
456 $normalized[] = $element;
457 }
458
459 return $normalized;
460 }
461
462 /**
463 * Append invisible characters so inbox clients do not pull body text into the preview line.
464 *
465 * @param string $preheader Visible preheader text.
466 * @param int $target_length Approximate preview window (Gmail ~90–140, Outlook ~130).
467 * @return string
468 */
469 public static function pad_preheader( $preheader, $target_length = 150 ) {
470 $preheader = trim( (string) $preheader );
471
472 if ( '' === $preheader ) {
473 return '';
474 }
475
476 /**
477 * Filter the target length for invisible preheader padding.
478 *
479 * @param int $target_length Default padding target length.
480 * @param string $preheader Visible preheader text.
481 */
482 $target_length = (int) apply_filters( 'yaymail_email_preheader_pad_length', $target_length, $preheader );
483
484 if ( $target_length <= 0 ) {
485 return $preheader;
486 }
487
488 $visible_length = mb_strlen( $preheader );
489
490 if ( $visible_length >= $target_length ) {
491 return $preheader;
492 }
493
494 // Litmus preview-text hack: zero-width non-joiner + non-breaking space.
495 $pad_unit = "\u{200C}\u{00A0}";
496
497 return $preheader . str_repeat( $pad_unit, $target_length - $visible_length );
498 }
499
500 /**
501 * Process preheader text (YayMail shortcodes).
502 *
503 * @param string $preheader Raw preheader from template meta.
504 * @param YayMailTemplate $template Email template.
505 * @param array $render_data Render context.
506 * @return string Plain-text preheader for inbox preview.
507 */
508 public static function process_email_preheader( $preheader, $template, $args = [] ) {
509 $preheader = trim( (string) $preheader );
510
511 if ( '' === $preheader || ! $template instanceof YayMailTemplate ) {
512 return '';
513 }
514
515 $template_name = $template->get_name();
516
517 if ( false !== strpos( $preheader, '[' ) && $template_name ) {
518
519 $shortcodes = yaymail_get_email_shortcodes( $template_name );
520 new ShortcodesExecutor( $shortcodes, $args );
521 $preheader = do_shortcode( $preheader );
522 }//end if
523
524 $preheader = self::pad_preheader( wp_strip_all_tags( $preheader ) );
525
526 return $preheader;
527 }
528
529 /**
530 * Render YayMail shortcodes inside an email subject at send time.
531 *
532 * Twin of process_email_preheader: registers the template shortcodes with the
533 * given render context, then resolves them. Output is forced to a single
534 * plain-text line because subjects must not contain HTML/markup that some
535 * shortcodes (e.g. order details) emit.
536 *
537 * @param string $subject Raw subject after WooCommerce format_string().
538 * @param YayMailTemplate $template Email template.
539 * @param array $args Shortcode executor data (template, render_data, settings, ...).
540 * @return string Subject with YayMail shortcodes resolved.
541 */
542 public static function process_email_subject( $subject, $template, $args = [] ) {
543 $subject = (string) $subject;
544
545 // Cheap guard: nothing to resolve when there is no shortcode marker.
546 if ( '' === $subject || false === strpos( $subject, '[' ) || ! $template instanceof YayMailTemplate ) {
547 return $subject;
548 }
549
550 $template_name = $template->get_name();
551 if ( ! $template_name ) {
552 return $subject;
553 }
554
555 $shortcodes = yaymail_get_email_shortcodes( $template_name );
556 new ShortcodesExecutor( $shortcodes, $args );
557
558 // $remove_breaks = true collapses newlines so multi-line shortcode output stays a single subject line.
559 $subject = trim( wp_strip_all_tags( do_shortcode( $subject ), true ) );
560
561 // Extension point: no shortcode restriction by default; lets integrators trim length,
562 // blank structural shortcodes, etc. $args carries template + render_data (incl. order).
563 return apply_filters( 'yaymail_email_subject', $subject, $template, $args );
564 }
565
566 /**
567 * Output hidden preheader row at the top of the email body (Woo block-editor style).
568 *
569 * @param YayMailTemplate $template Email template.
570 * @param array $args Render context.
571 */
572 public static function render_email_preheader( $template, $args = [] ) {
573 if ( ! $template instanceof YayMailTemplate ) {
574 return;
575 }
576
577 $template_name = $template->get_name();
578
579 if ( str_starts_with( $template_name, 'pattern_' ) || in_array( $template_name, TemplatesData::GLOBAL_HEADER_FOOTER_TEMPLATE_IDS, true ) ) {
580 return;
581 }
582
583 $preheader = self::process_email_preheader( $template->get_preheader(), $template, $args );
584
585 if ( '' === $preheader ) {
586 return;
587 }
588
589 $preheader_html = yaymail_get_content(
590 'templates/emails/email-preheader.php',
591 [
592 'preheader' => $preheader,
593 ]
594 );
595
596 if ( '' !== $preheader_html ) {
597 yaymail_kses_post_e( $preheader_html );
598 }
599 }
600 }
601