PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.35.0
MailPoet – Newsletters, Email Marketing, and Automation v5.35.0
5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 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 / vendor / woocommerce / email-editor / src / Engine / class-personalizer.php
mailpoet / vendor / woocommerce / email-editor / src / Engine Last commit date
Logger 1 year ago Patterns 1 year ago PersonalizationTags 9 months ago Renderer 3 weeks ago Templates 4 months ago class-assets-manager.php 6 months ago class-dependency-check.php 1 year ago class-email-api-controller.php 6 months ago class-email-editor.php 3 weeks ago class-email-styles-schema.php 1 year ago class-personalizer.php 6 months ago class-send-preview-email.php 5 months ago class-settings-controller.php 3 months ago class-site-style-sync-controller.php 4 months ago class-theme-controller.php 3 months ago class-user-theme.php 1 year ago content-editor.css 3 weeks ago content-shared.css 1 year ago index.php 1 year ago theme.json 3 months ago
class-personalizer.php
121 lines
1 <?php
2 declare(strict_types = 1);
3 namespace Automattic\WooCommerce\EmailEditor\Engine;
4 if (!defined('ABSPATH')) exit;
5 use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\HTML_Tag_Processor;
6 use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
7 class Personalizer {
8 private const TAG_NAME_PATTERN = '[a-zA-Z0-9\-\/]+';
9 private Personalization_Tags_Registry $tags_registry;
10 private array $context;
11 public function __construct( Personalization_Tags_Registry $tags_registry ) {
12 $this->tags_registry = $tags_registry;
13 $this->context = array();
14 }
15 public function set_context( array $context ) {
16 $this->context = $context;
17 }
18 public function get_context(): array {
19 return $this->context;
20 }
21 public function personalize_content( string $content ): string {
22 $content_processor = new HTML_Tag_Processor( $content );
23 while ( $content_processor->next_token() ) {
24 if ( $content_processor->get_token_type() === '#comment' ) {
25 $modifiable_text = $content_processor->get_modifiable_text();
26 $token = $this->parse_token( $modifiable_text );
27 $tag = $this->tags_registry->get_by_token( $token['token'] );
28 if ( ! $tag ) {
29 continue;
30 }
31 $value = $tag->execute_callback( $this->context, $token['arguments'] );
32 $content_processor->replace_token( $value );
33 } elseif ( $content_processor->get_token_type() === '#tag' && $content_processor->get_tag() === 'TITLE' ) {
34 // The title tag contains the subject of the email which should be personalized. HTML_Tag_Processor does parse the header tags.
35 $modifiable_text = $content_processor->get_modifiable_text();
36 $title = $this->personalize_content( $modifiable_text );
37 $content_processor->set_modifiable_text( $title );
38 } elseif ( $content_processor->get_token_type() === '#tag' && $content_processor->get_tag() === 'A' && $content_processor->get_attribute( 'data-link-href' ) ) {
39 // The anchor tag contains the data-link-href attribute which should be personalized.
40 $href = (string) $content_processor->get_attribute( 'data-link-href' );
41 $token = $this->parse_token( $href );
42 $tag = $this->tags_registry->get_by_token( $token['token'] );
43 if ( ! $tag ) {
44 continue;
45 }
46 $value = $tag->execute_callback( $this->context, $token['arguments'] );
47 $value = $this->replace_link_href( $href, $tag->get_token(), $value );
48 if ( $value ) {
49 $content_processor->set_attribute( 'href', $value );
50 $content_processor->remove_attribute( 'data-link-href' );
51 $content_processor->remove_attribute( 'contenteditable' );
52 }
53 } elseif ( $content_processor->get_token_type() === '#tag' && $content_processor->get_tag() === 'A' ) {
54 $href = $content_processor->get_attribute( 'href' );
55 if ( ! is_string( $href ) ) {
56 continue;
57 }
58 // Decode both URL encoding (%XX) and HTML entities (&#039;) to handle various encoding scenarios.
59 $decoded_href = html_entity_decode( urldecode( $href ), ENT_QUOTES, 'UTF-8' );
60 if ( ! preg_match( '/\[' . self::TAG_NAME_PATTERN . '(?:\s+[^\]]+)?\]/', $decoded_href, $matches ) ) {
61 continue;
62 }
63 $token = $this->parse_token( $matches[0] );
64 $tag = $this->tags_registry->get_by_token( $token['token'] );
65 if ( ! $tag ) {
66 continue;
67 }
68 $value = $tag->execute_callback( $this->context, $token['arguments'] );
69 if ( $value ) {
70 $content_processor->set_attribute( 'href', $value );
71 }
72 }
73 }
74 $content_processor->flush_updates();
75 return $content_processor->get_updated_html();
76 }
77 private function parse_token( string $token ): array {
78 $result = array(
79 'token' => '',
80 'arguments' => array(),
81 );
82 // Step 1: Separate the tag and attributes.
83 if ( preg_match( '/^\[(' . self::TAG_NAME_PATTERN . ')\s*(.*?)\]$/', trim( $token ), $matches ) ) {
84 $result['token'] = "[{$matches[1]}]"; // The tag part (e.g., "[mailpoet/subscriber-firstname]").
85 $attributes_string = $matches[2]; // The attributes part (e.g., 'default="subscriber"').
86 // Step 2: Extract attributes from the attribute string.
87 // Match quoted values (double or single quotes separately to avoid mixing) and unquoted values.
88 // Unquoted values can occur when esc_url() strips quotes from personalization tags.
89 // For unquoted values with spaces, capture until the next key= pattern or closing bracket.
90 // The negative lookahead (?!\w+=) is critical for preventing ReDoS:
91 // it ensures the inner loop terminates as soon as the next key= pattern appears,
92 // preventing excessive backtracking despite the nested quantifiers.
93 if ( preg_match_all( '/(\w+)=(?:"([^"]*)"|\'([^\']*)\'|([^\s\]]+(?:\s+(?!\w+=)[^\s\]]+)*))/', $attributes_string, $attribute_matches, PREG_SET_ORDER ) ) {
94 foreach ( $attribute_matches as $attribute ) {
95 // $attribute[2] is double-quoted value, $attribute[3] is single-quoted value,
96 // $attribute[4] is unquoted value (may contain spaces).
97 // Use null coalescing as only one of these will be populated depending on which pattern matched.
98 $double_quoted_value = $attribute[2] ?? '';
99 $single_quoted_value = $attribute[3] ?? '';
100 $unquoted_value = $attribute[4] ?? '';
101 if ( '' !== $double_quoted_value ) {
102 $result['arguments'][ $attribute[1] ] = $double_quoted_value;
103 } elseif ( '' !== $single_quoted_value ) {
104 $result['arguments'][ $attribute[1] ] = $single_quoted_value;
105 } else {
106 $result['arguments'][ $attribute[1] ] = $unquoted_value;
107 }
108 }
109 }
110 }
111 return $result;
112 }
113 private function replace_link_href( string $content, string $token, string $replacement ) {
114 // Escape the shortcode name for safe regex usage and strip the brackets.
115 $escaped_shortcode = preg_quote( substr( $token, 1, strlen( $token ) - 2 ), '/' );
116 // Create a regex pattern dynamically.
117 $pattern = '/\[' . $escaped_shortcode . '(?:\s+[^\]]+)?\]/';
118 return trim( (string) preg_replace( $pattern, $replacement, $content ) );
119 }
120 }
121