PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 5.4.0-rc.1
WooCommerce v5.4.0-rc.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 / includes / emails / class-wc-email.php
woocommerce / includes / emails Last commit date
class-wc-email-cancelled-order.php 5 years ago class-wc-email-customer-completed-order.php 5 years ago class-wc-email-customer-invoice.php 5 years ago class-wc-email-customer-new-account.php 5 years ago class-wc-email-customer-note.php 5 years ago class-wc-email-customer-on-hold-order.php 5 years ago class-wc-email-customer-processing-order.php 5 years ago class-wc-email-customer-refunded-order.php 5 years ago class-wc-email-customer-reset-password.php 5 years ago class-wc-email-failed-order.php 5 years ago class-wc-email-new-order.php 5 years ago class-wc-email.php 5 years ago
class-wc-email.php
1083 lines
1 <?php
2 /**
3 * Class WC_Email file.
4 *
5 * @package WooCommerce\Emails
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 if ( class_exists( 'WC_Email', false ) ) {
13 return;
14 }
15
16 /**
17 * Email Class
18 *
19 * WooCommerce Email Class which is extended by specific email template classes to add emails to WooCommerce
20 *
21 * @class WC_Email
22 * @version 2.5.0
23 * @package WooCommerce\Classes\Emails
24 * @extends WC_Settings_API
25 */
26 class WC_Email extends WC_Settings_API {
27
28 /**
29 * Email method ID.
30 *
31 * @var String
32 */
33 public $id;
34
35 /**
36 * Email method title.
37 *
38 * @var string
39 */
40 public $title;
41
42 /**
43 * 'yes' if the method is enabled.
44 *
45 * @var string yes, no
46 */
47 public $enabled;
48
49 /**
50 * Description for the email.
51 *
52 * @var string
53 */
54 public $description;
55
56 /**
57 * Default heading.
58 *
59 * Supported for backwards compatibility but we recommend overloading the
60 * get_default_x methods instead so localization can be done when needed.
61 *
62 * @var string
63 */
64 public $heading = '';
65
66 /**
67 * Default subject.
68 *
69 * Supported for backwards compatibility but we recommend overloading the
70 * get_default_x methods instead so localization can be done when needed.
71 *
72 * @var string
73 */
74 public $subject = '';
75
76 /**
77 * Plain text template path.
78 *
79 * @var string
80 */
81 public $template_plain;
82
83 /**
84 * HTML template path.
85 *
86 * @var string
87 */
88 public $template_html;
89
90 /**
91 * Template path.
92 *
93 * @var string
94 */
95 public $template_base;
96
97 /**
98 * Recipients for the email.
99 *
100 * @var string
101 */
102 public $recipient;
103
104 /**
105 * Object this email is for, for example a customer, product, or email.
106 *
107 * @var object|bool
108 */
109 public $object;
110
111 /**
112 * Mime boundary (for multipart emails).
113 *
114 * @var string
115 */
116 public $mime_boundary;
117
118 /**
119 * Mime boundary header (for multipart emails).
120 *
121 * @var string
122 */
123 public $mime_boundary_header;
124
125 /**
126 * True when email is being sent.
127 *
128 * @var bool
129 */
130 public $sending;
131
132 /**
133 * True when the email notification is sent manually only.
134 *
135 * @var bool
136 */
137 protected $manual = false;
138
139 /**
140 * True when the email notification is sent to customers.
141 *
142 * @var bool
143 */
144 protected $customer_email = false;
145
146 /**
147 * List of preg* regular expression patterns to search for,
148 * used in conjunction with $plain_replace.
149 * https://raw.github.com/ushahidi/wp-silcc/master/class.html2text.inc
150 *
151 * @var array $plain_search
152 * @see $plain_replace
153 */
154 public $plain_search = array(
155 "/\r/", // Non-legal carriage return.
156 '/&(nbsp|#0*160);/i', // Non-breaking space.
157 '/&(quot|rdquo|ldquo|#0*8220|#0*8221|#0*147|#0*148);/i', // Double quotes.
158 '/&(apos|rsquo|lsquo|#0*8216|#0*8217);/i', // Single quotes.
159 '/&gt;/i', // Greater-than.
160 '/&lt;/i', // Less-than.
161 '/&#0*38;/i', // Ampersand.
162 '/&amp;/i', // Ampersand.
163 '/&(copy|#0*169);/i', // Copyright.
164 '/&(trade|#0*8482|#0*153);/i', // Trademark.
165 '/&(reg|#0*174);/i', // Registered.
166 '/&(mdash|#0*151|#0*8212);/i', // mdash.
167 '/&(ndash|minus|#0*8211|#0*8722);/i', // ndash.
168 '/&(bull|#0*149|#0*8226);/i', // Bullet.
169 '/&(pound|#0*163);/i', // Pound sign.
170 '/&(euro|#0*8364);/i', // Euro sign.
171 '/&(dollar|#0*36);/i', // Dollar sign.
172 '/&[^&\s;]+;/i', // Unknown/unhandled entities.
173 '/[ ]{2,}/', // Runs of spaces, post-handling.
174 );
175
176 /**
177 * List of pattern replacements corresponding to patterns searched.
178 *
179 * @var array $plain_replace
180 * @see $plain_search
181 */
182 public $plain_replace = array(
183 '', // Non-legal carriage return.
184 ' ', // Non-breaking space.
185 '"', // Double quotes.
186 "'", // Single quotes.
187 '>', // Greater-than.
188 '<', // Less-than.
189 '&', // Ampersand.
190 '&', // Ampersand.
191 '(c)', // Copyright.
192 '(tm)', // Trademark.
193 '(R)', // Registered.
194 '--', // mdash.
195 '-', // ndash.
196 '*', // Bullet.
197 '£', // Pound sign.
198 'EUR', // Euro sign. € ?.
199 '$', // Dollar sign.
200 '', // Unknown/unhandled entities.
201 ' ', // Runs of spaces, post-handling.
202 );
203
204 /**
205 * Strings to find/replace in subjects/headings.
206 *
207 * @var array
208 */
209 protected $placeholders = array();
210
211 /**
212 * Strings to find in subjects/headings.
213 *
214 * @deprecated 3.2.0 in favour of placeholders
215 * @var array
216 */
217 public $find = array();
218
219 /**
220 * Strings to replace in subjects/headings.
221 *
222 * @deprecated 3.2.0 in favour of placeholders
223 * @var array
224 */
225 public $replace = array();
226
227 /**
228 * Constructor.
229 */
230 public function __construct() {
231 // Find/replace.
232 $this->placeholders = array_merge(
233 array(
234 '{site_title}' => $this->get_blogname(),
235 '{site_address}' => wp_parse_url( home_url(), PHP_URL_HOST ),
236 '{site_url}' => wp_parse_url( home_url(), PHP_URL_HOST ),
237 ),
238 $this->placeholders
239 );
240
241 // Init settings.
242 $this->init_form_fields();
243 $this->init_settings();
244
245 // Default template base if not declared in child constructor.
246 if ( is_null( $this->template_base ) ) {
247 $this->template_base = WC()->plugin_path() . '/templates/';
248 }
249
250 $this->email_type = $this->get_option( 'email_type' );
251 $this->enabled = $this->get_option( 'enabled' );
252
253 add_action( 'phpmailer_init', array( $this, 'handle_multipart' ) );
254 add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) );
255 }
256
257 /**
258 * Handle multipart mail.
259 *
260 * @param PHPMailer $mailer PHPMailer object.
261 * @return PHPMailer
262 */
263 public function handle_multipart( $mailer ) {
264 if ( $this->sending && 'multipart' === $this->get_email_type() ) {
265 $mailer->AltBody = wordwrap( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
266 preg_replace( $this->plain_search, $this->plain_replace, wp_strip_all_tags( $this->get_content_plain() ) )
267 );
268 $this->sending = false;
269 }
270 return $mailer;
271 }
272
273 /**
274 * Format email string.
275 *
276 * @param mixed $string Text to replace placeholders in.
277 * @return string
278 */
279 public function format_string( $string ) {
280 $find = array_keys( $this->placeholders );
281 $replace = array_values( $this->placeholders );
282
283 // If using legacy find replace, add those to our find/replace arrays first. @todo deprecate in 4.0.0.
284 $find = array_merge( (array) $this->find, $find );
285 $replace = array_merge( (array) $this->replace, $replace );
286
287 // Take care of blogname which is no longer defined as a valid placeholder.
288 $find[] = '{blogname}';
289 $replace[] = $this->get_blogname();
290
291 // If using the older style filters for find and replace, ensure the array is associative and then pass through filters. @todo deprecate in 4.0.0.
292 if ( has_filter( 'woocommerce_email_format_string_replace' ) || has_filter( 'woocommerce_email_format_string_find' ) ) {
293 $legacy_find = $this->find;
294 $legacy_replace = $this->replace;
295
296 foreach ( $this->placeholders as $find => $replace ) {
297 $legacy_key = sanitize_title( str_replace( '_', '-', trim( $find, '{}' ) ) );
298 $legacy_find[ $legacy_key ] = $find;
299 $legacy_replace[ $legacy_key ] = $replace;
300 }
301
302 $string = str_replace( apply_filters( 'woocommerce_email_format_string_find', $legacy_find, $this ), apply_filters( 'woocommerce_email_format_string_replace', $legacy_replace, $this ), $string );
303 }
304
305 /**
306 * Filter for main find/replace.
307 *
308 * @since 3.2.0
309 */
310 return apply_filters( 'woocommerce_email_format_string', str_replace( $find, $replace, $string ), $this );
311 }
312
313 /**
314 * Set the locale to the store locale for customer emails to make sure emails are in the store language.
315 */
316 public function setup_locale() {
317 if ( $this->is_customer_email() && apply_filters( 'woocommerce_email_setup_locale', true ) ) {
318 wc_switch_to_site_locale();
319 }
320 }
321
322 /**
323 * Restore the locale to the default locale. Use after finished with setup_locale.
324 */
325 public function restore_locale() {
326 if ( $this->is_customer_email() && apply_filters( 'woocommerce_email_restore_locale', true ) ) {
327 wc_restore_locale();
328 }
329 }
330
331 /**
332 * Get email subject.
333 *
334 * @since 3.1.0
335 * @return string
336 */
337 public function get_default_subject() {
338 return $this->subject;
339 }
340
341 /**
342 * Get email heading.
343 *
344 * @since 3.1.0
345 * @return string
346 */
347 public function get_default_heading() {
348 return $this->heading;
349 }
350
351 /**
352 * Default content to show below main email content.
353 *
354 * @since 3.7.0
355 * @return string
356 */
357 public function get_default_additional_content() {
358 return '';
359 }
360
361 /**
362 * Return content from the additional_content field.
363 *
364 * Displayed above the footer.
365 *
366 * @since 3.7.0
367 * @return string
368 */
369 public function get_additional_content() {
370 $content = $this->get_option( 'additional_content', '' );
371
372 return apply_filters( 'woocommerce_email_additional_content_' . $this->id, $this->format_string( $content ), $this->object, $this );
373 }
374
375 /**
376 * Get email subject.
377 *
378 * @return string
379 */
380 public function get_subject() {
381 return apply_filters( 'woocommerce_email_subject_' . $this->id, $this->format_string( $this->get_option( 'subject', $this->get_default_subject() ) ), $this->object, $this );
382 }
383
384 /**
385 * Get email heading.
386 *
387 * @return string
388 */
389 public function get_heading() {
390 return apply_filters( 'woocommerce_email_heading_' . $this->id, $this->format_string( $this->get_option( 'heading', $this->get_default_heading() ) ), $this->object, $this );
391 }
392
393 /**
394 * Get valid recipients.
395 *
396 * @return string
397 */
398 public function get_recipient() {
399 $recipient = apply_filters( 'woocommerce_email_recipient_' . $this->id, $this->recipient, $this->object, $this );
400 $recipients = array_map( 'trim', explode( ',', $recipient ) );
401 $recipients = array_filter( $recipients, 'is_email' );
402 return implode( ', ', $recipients );
403 }
404
405 /**
406 * Get email headers.
407 *
408 * @return string
409 */
410 public function get_headers() {
411 $header = 'Content-Type: ' . $this->get_content_type() . "\r\n";
412
413 if ( in_array( $this->id, array( 'new_order', 'cancelled_order', 'failed_order' ), true ) ) {
414 if ( $this->object && $this->object->get_billing_email() && ( $this->object->get_billing_first_name() || $this->object->get_billing_last_name() ) ) {
415 $header .= 'Reply-to: ' . $this->object->get_billing_first_name() . ' ' . $this->object->get_billing_last_name() . ' <' . $this->object->get_billing_email() . ">\r\n";
416 }
417 } elseif ( $this->get_from_address() && $this->get_from_name() ) {
418 $header .= 'Reply-to: ' . $this->get_from_name() . ' <' . $this->get_from_address() . ">\r\n";
419 }
420
421 return apply_filters( 'woocommerce_email_headers', $header, $this->id, $this->object, $this );
422 }
423
424 /**
425 * Get email attachments.
426 *
427 * @return array
428 */
429 public function get_attachments() {
430 return apply_filters( 'woocommerce_email_attachments', array(), $this->id, $this->object, $this );
431 }
432
433 /**
434 * Return email type.
435 *
436 * @return string
437 */
438 public function get_email_type() {
439 return $this->email_type && class_exists( 'DOMDocument' ) ? $this->email_type : 'plain';
440 }
441
442 /**
443 * Get email content type.
444 *
445 * @param string $default_content_type Default wp_mail() content type.
446 * @return string
447 */
448 public function get_content_type( $default_content_type = '' ) {
449 switch ( $this->get_email_type() ) {
450 case 'html':
451 $content_type = 'text/html';
452 break;
453 case 'multipart':
454 $content_type = 'multipart/alternative';
455 break;
456 default:
457 $content_type = 'text/plain';
458 break;
459 }
460
461 return apply_filters( 'woocommerce_email_content_type', $content_type, $this, $default_content_type );
462 }
463
464 /**
465 * Return the email's title
466 *
467 * @return string
468 */
469 public function get_title() {
470 return apply_filters( 'woocommerce_email_title', $this->title, $this );
471 }
472
473 /**
474 * Return the email's description
475 *
476 * @return string
477 */
478 public function get_description() {
479 return apply_filters( 'woocommerce_email_description', $this->description, $this );
480 }
481
482 /**
483 * Proxy to parent's get_option and attempt to localize the result using gettext.
484 *
485 * @param string $key Option key.
486 * @param mixed $empty_value Value to use when option is empty.
487 * @return string
488 */
489 public function get_option( $key, $empty_value = null ) {
490 $value = parent::get_option( $key, $empty_value );
491 return apply_filters( 'woocommerce_email_get_option', $value, $this, $value, $key, $empty_value );
492 }
493
494 /**
495 * Checks if this email is enabled and will be sent.
496 *
497 * @return bool
498 */
499 public function is_enabled() {
500 return apply_filters( 'woocommerce_email_enabled_' . $this->id, 'yes' === $this->enabled, $this->object, $this );
501 }
502
503 /**
504 * Checks if this email is manually sent
505 *
506 * @return bool
507 */
508 public function is_manual() {
509 return $this->manual;
510 }
511
512 /**
513 * Checks if this email is customer focussed.
514 *
515 * @return bool
516 */
517 public function is_customer_email() {
518 return $this->customer_email;
519 }
520
521 /**
522 * Get WordPress blog name.
523 *
524 * @return string
525 */
526 public function get_blogname() {
527 return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
528 }
529
530 /**
531 * Get email content.
532 *
533 * @return string
534 */
535 public function get_content() {
536 $this->sending = true;
537
538 if ( 'plain' === $this->get_email_type() ) {
539 $email_content = wordwrap( preg_replace( $this->plain_search, $this->plain_replace, wp_strip_all_tags( $this->get_content_plain() ) ), 70 );
540 } else {
541 $email_content = $this->get_content_html();
542 }
543
544 return $email_content;
545 }
546
547 /**
548 * Apply inline styles to dynamic content.
549 *
550 * We only inline CSS for html emails, and to do so we use Emogrifier library (if supported).
551 *
552 * @version 4.0.0
553 * @param string|null $content Content that will receive inline styles.
554 * @return string
555 */
556 public function style_inline( $content ) {
557 if ( in_array( $this->get_content_type(), array( 'text/html', 'multipart/alternative' ), true ) ) {
558 ob_start();
559 wc_get_template( 'emails/email-styles.php' );
560 $css = apply_filters( 'woocommerce_email_styles', ob_get_clean(), $this );
561
562 $emogrifier_class = 'Pelago\\Emogrifier';
563
564 if ( $this->supports_emogrifier() && class_exists( $emogrifier_class ) ) {
565 try {
566 $emogrifier = new $emogrifier_class( $content, $css );
567
568 do_action( 'woocommerce_emogrifier', $emogrifier, $this );
569
570 $content = $emogrifier->emogrify();
571 $html_prune = \Pelago\Emogrifier\HtmlProcessor\HtmlPruner::fromHtml( $content );
572 $html_prune->removeElementsWithDisplayNone();
573 $content = $html_prune->render();
574 } catch ( Exception $e ) {
575 $logger = wc_get_logger();
576 $logger->error( $e->getMessage(), array( 'source' => 'emogrifier' ) );
577 }
578 } else {
579 $content = '<style type="text/css">' . $css . '</style>' . $content;
580 }
581 }
582
583 return $content;
584 }
585
586 /**
587 * Return if emogrifier library is supported.
588 *
589 * @version 4.0.0
590 * @since 3.5.0
591 * @return bool
592 */
593 protected function supports_emogrifier() {
594 return class_exists( 'DOMDocument' );
595 }
596
597 /**
598 * Get the email content in plain text format.
599 *
600 * @return string
601 */
602 public function get_content_plain() {
603 return '';
604 }
605
606 /**
607 * Get the email content in HTML format.
608 *
609 * @return string
610 */
611 public function get_content_html() {
612 return '';
613 }
614
615 /**
616 * Get the from name for outgoing emails.
617 *
618 * @param string $from_name Default wp_mail() name associated with the "from" email address.
619 * @return string
620 */
621 public function get_from_name( $from_name = '' ) {
622 $from_name = apply_filters( 'woocommerce_email_from_name', get_option( 'woocommerce_email_from_name' ), $this, $from_name );
623 return wp_specialchars_decode( esc_html( $from_name ), ENT_QUOTES );
624 }
625
626 /**
627 * Get the from address for outgoing emails.
628 *
629 * @param string $from_email Default wp_mail() email address to send from.
630 * @return string
631 */
632 public function get_from_address( $from_email = '' ) {
633 $from_email = apply_filters( 'woocommerce_email_from_address', get_option( 'woocommerce_email_from_address' ), $this, $from_email );
634 return sanitize_email( $from_email );
635 }
636
637 /**
638 * Send an email.
639 *
640 * @param string $to Email to.
641 * @param string $subject Email subject.
642 * @param string $message Email message.
643 * @param string $headers Email headers.
644 * @param array $attachments Email attachments.
645 * @return bool success
646 */
647 public function send( $to, $subject, $message, $headers, $attachments ) {
648 add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
649 add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
650 add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
651
652 $message = apply_filters( 'woocommerce_mail_content', $this->style_inline( $message ) );
653 $mail_callback = apply_filters( 'woocommerce_mail_callback', 'wp_mail', $this );
654 $mail_callback_params = apply_filters( 'woocommerce_mail_callback_params', array( $to, $subject, $message, $headers, $attachments ), $this );
655 $return = $mail_callback( ...$mail_callback_params );
656
657 remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
658 remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
659 remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
660
661 return $return;
662 }
663
664 /**
665 * Initialise Settings Form Fields - these are generic email options most will use.
666 */
667 public function init_form_fields() {
668 /* translators: %s: list of placeholders */
669 $placeholder_text = sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '<code>' . esc_html( implode( '</code>, <code>', array_keys( $this->placeholders ) ) ) . '</code>' );
670 $this->form_fields = array(
671 'enabled' => array(
672 'title' => __( 'Enable/Disable', 'woocommerce' ),
673 'type' => 'checkbox',
674 'label' => __( 'Enable this email notification', 'woocommerce' ),
675 'default' => 'yes',
676 ),
677 'subject' => array(
678 'title' => __( 'Subject', 'woocommerce' ),
679 'type' => 'text',
680 'desc_tip' => true,
681 'description' => $placeholder_text,
682 'placeholder' => $this->get_default_subject(),
683 'default' => '',
684 ),
685 'heading' => array(
686 'title' => __( 'Email heading', 'woocommerce' ),
687 'type' => 'text',
688 'desc_tip' => true,
689 'description' => $placeholder_text,
690 'placeholder' => $this->get_default_heading(),
691 'default' => '',
692 ),
693 'additional_content' => array(
694 'title' => __( 'Additional content', 'woocommerce' ),
695 'description' => __( 'Text to appear below the main email content.', 'woocommerce' ) . ' ' . $placeholder_text,
696 'css' => 'width:400px; height: 75px;',
697 'placeholder' => __( 'N/A', 'woocommerce' ),
698 'type' => 'textarea',
699 'default' => $this->get_default_additional_content(),
700 'desc_tip' => true,
701 ),
702 'email_type' => array(
703 'title' => __( 'Email type', 'woocommerce' ),
704 'type' => 'select',
705 'description' => __( 'Choose which format of email to send.', 'woocommerce' ),
706 'default' => 'html',
707 'class' => 'email_type wc-enhanced-select',
708 'options' => $this->get_email_type_options(),
709 'desc_tip' => true,
710 ),
711 );
712 }
713
714 /**
715 * Email type options.
716 *
717 * @return array
718 */
719 public function get_email_type_options() {
720 $types = array( 'plain' => __( 'Plain text', 'woocommerce' ) );
721
722 if ( class_exists( 'DOMDocument' ) ) {
723 $types['html'] = __( 'HTML', 'woocommerce' );
724 $types['multipart'] = __( 'Multipart', 'woocommerce' );
725 }
726
727 return $types;
728 }
729
730 /**
731 * Admin Panel Options Processing.
732 */
733 public function process_admin_options() {
734 // Save regular options.
735 parent::process_admin_options();
736
737 $post_data = $this->get_post_data();
738
739 // Save templates.
740 if ( isset( $post_data['template_html_code'] ) ) {
741 $this->save_template( $post_data['template_html_code'], $this->template_html );
742 }
743 if ( isset( $post_data['template_plain_code'] ) ) {
744 $this->save_template( $post_data['template_plain_code'], $this->template_plain );
745 }
746 }
747
748 /**
749 * Get template.
750 *
751 * @param string $type Template type. Can be either 'template_html' or 'template_plain'.
752 * @return string
753 */
754 public function get_template( $type ) {
755 $type = basename( $type );
756
757 if ( 'template_html' === $type ) {
758 return $this->template_html;
759 } elseif ( 'template_plain' === $type ) {
760 return $this->template_plain;
761 }
762 return '';
763 }
764
765 /**
766 * Save the email templates.
767 *
768 * @since 2.4.0
769 * @param string $template_code Template code.
770 * @param string $template_path Template path.
771 */
772 protected function save_template( $template_code, $template_path ) {
773 if ( current_user_can( 'edit_themes' ) && ! empty( $template_code ) && ! empty( $template_path ) ) {
774 $saved = false;
775 $file = get_stylesheet_directory() . '/' . WC()->template_path() . $template_path;
776 $code = wp_unslash( $template_code );
777
778 if ( is_writeable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writeable
779 $f = fopen( $file, 'w+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
780
781 if ( false !== $f ) {
782 fwrite( $f, $code ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
783 fclose( $f ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
784 $saved = true;
785 }
786 }
787
788 if ( ! $saved ) {
789 $redirect = add_query_arg( 'wc_error', rawurlencode( __( 'Could not write to template file.', 'woocommerce' ) ) );
790 wp_safe_redirect( $redirect );
791 exit;
792 }
793 }
794 }
795
796 /**
797 * Get the template file in the current theme.
798 *
799 * @param string $template Template name.
800 *
801 * @return string
802 */
803 public function get_theme_template_file( $template ) {
804 return get_stylesheet_directory() . '/' . apply_filters( 'woocommerce_template_directory', 'woocommerce', $template ) . '/' . $template;
805 }
806
807 /**
808 * Move template action.
809 *
810 * @param string $template_type Template type.
811 */
812 protected function move_template_action( $template_type ) {
813 $template = $this->get_template( $template_type );
814 if ( ! empty( $template ) ) {
815 $theme_file = $this->get_theme_template_file( $template );
816
817 if ( wp_mkdir_p( dirname( $theme_file ) ) && ! file_exists( $theme_file ) ) {
818
819 // Locate template file.
820 $core_file = $this->template_base . $template;
821 $template_file = apply_filters( 'woocommerce_locate_core_template', $core_file, $template, $this->template_base, $this->id );
822
823 // Copy template file.
824 copy( $template_file, $theme_file );
825
826 /**
827 * Action hook fired after copying email template file.
828 *
829 * @param string $template_type The copied template type
830 * @param string $email The email object
831 */
832 do_action( 'woocommerce_copy_email_template', $template_type, $this );
833
834 ?>
835 <div class="updated">
836 <p><?php echo esc_html__( 'Template file copied to theme.', 'woocommerce' ); ?></p>
837 </div>
838 <?php
839 }
840 }
841 }
842
843 /**
844 * Delete template action.
845 *
846 * @param string $template_type Template type.
847 */
848 protected function delete_template_action( $template_type ) {
849 $template = $this->get_template( $template_type );
850
851 if ( $template ) {
852 if ( ! empty( $template ) ) {
853 $theme_file = $this->get_theme_template_file( $template );
854
855 if ( file_exists( $theme_file ) ) {
856 unlink( $theme_file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
857
858 /**
859 * Action hook fired after deleting template file.
860 *
861 * @param string $template The deleted template type
862 * @param string $email The email object
863 */
864 do_action( 'woocommerce_delete_email_template', $template_type, $this );
865 ?>
866 <div class="updated">
867 <p><?php echo esc_html__( 'Template file deleted from theme.', 'woocommerce' ); ?></p>
868 </div>
869 <?php
870 }
871 }
872 }
873 }
874
875 /**
876 * Admin actions.
877 */
878 protected function admin_actions() {
879 // Handle any actions.
880 if (
881 ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) )
882 && ( ! empty( $_GET['move_template'] ) || ! empty( $_GET['delete_template'] ) )
883 && 'GET' === $_SERVER['REQUEST_METHOD'] // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
884 ) {
885 if ( empty( $_GET['_wc_email_nonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wc_email_nonce'] ) ), 'woocommerce_email_template_nonce' ) ) {
886 wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
887 }
888
889 if ( ! current_user_can( 'edit_themes' ) ) {
890 wp_die( esc_html__( 'You don&#8217;t have permission to do this.', 'woocommerce' ) );
891 }
892
893 if ( ! empty( $_GET['move_template'] ) ) {
894 $this->move_template_action( wc_clean( wp_unslash( $_GET['move_template'] ) ) );
895 }
896
897 if ( ! empty( $_GET['delete_template'] ) ) {
898 $this->delete_template_action( wc_clean( wp_unslash( $_GET['delete_template'] ) ) );
899 }
900 }
901 }
902
903 /**
904 * Admin Options.
905 *
906 * Setup the email settings screen.
907 * Override this in your email.
908 *
909 * @since 1.0.0
910 */
911 public function admin_options() {
912 // Do admin actions.
913 $this->admin_actions();
914 ?>
915 <h2><?php echo esc_html( $this->get_title() ); ?> <?php wc_back_link( __( 'Return to emails', 'woocommerce' ), admin_url( 'admin.php?page=wc-settings&tab=email' ) ); ?></h2>
916
917 <?php echo wpautop( wp_kses_post( $this->get_description() ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
918
919 <?php
920 /**
921 * Action hook fired before displaying email settings.
922 *
923 * @param string $email The email object
924 */
925 do_action( 'woocommerce_email_settings_before', $this );
926 ?>
927
928 <table class="form-table">
929 <?php $this->generate_settings_html(); ?>
930 </table>
931
932 <?php
933 /**
934 * Action hook fired after displaying email settings.
935 *
936 * @param string $email The email object
937 */
938 do_action( 'woocommerce_email_settings_after', $this );
939 ?>
940
941 <?php
942
943 if ( current_user_can( 'edit_themes' ) && ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) ) ) {
944 ?>
945 <div id="template">
946 <?php
947 $templates = array(
948 'template_html' => __( 'HTML template', 'woocommerce' ),
949 'template_plain' => __( 'Plain text template', 'woocommerce' ),
950 );
951
952 foreach ( $templates as $template_type => $title ) :
953 $template = $this->get_template( $template_type );
954
955 if ( empty( $template ) ) {
956 continue;
957 }
958
959 $local_file = $this->get_theme_template_file( $template );
960 $core_file = $this->template_base . $template;
961 $template_file = apply_filters( 'woocommerce_locate_core_template', $core_file, $template, $this->template_base, $this->id );
962 $template_dir = apply_filters( 'woocommerce_template_directory', 'woocommerce', $template );
963 ?>
964 <div class="template <?php echo esc_attr( $template_type ); ?>">
965 <h4><?php echo wp_kses_post( $title ); ?></h4>
966
967 <?php if ( file_exists( $local_file ) ) : ?>
968 <p>
969 <a href="#" class="button toggle_editor"></a>
970
971 <?php if ( is_writable( $local_file ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable ?>
972 <a href="<?php echo esc_url( wp_nonce_url( remove_query_arg( array( 'move_template', 'saved' ), add_query_arg( 'delete_template', $template_type ) ), 'woocommerce_email_template_nonce', '_wc_email_nonce' ) ); ?>" class="delete_template button">
973 <?php esc_html_e( 'Delete template file', 'woocommerce' ); ?>
974 </a>
975 <?php endif; ?>
976
977 <?php
978 /* translators: %s: Path to template file */
979 printf( esc_html__( 'This template has been overridden by your theme and can be found in: %s.', 'woocommerce' ), '<code>' . esc_html( trailingslashit( basename( get_stylesheet_directory() ) ) . $template_dir . '/' . $template ) . '</code>' );
980 ?>
981 </p>
982
983 <div class="editor" style="display:none">
984 <textarea class="code" cols="25" rows="20"
985 <?php
986 if ( ! is_writable( $local_file ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
987 ?>
988 readonly="readonly" disabled="disabled"
989 <?php else : ?>
990 data-name="<?php echo esc_attr( $template_type ) . '_code'; ?>"<?php endif; ?>><?php echo esc_html( file_get_contents( $local_file ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents ?></textarea>
991 </div>
992 <?php elseif ( file_exists( $template_file ) ) : ?>
993 <p>
994 <a href="#" class="button toggle_editor"></a>
995
996 <?php
997 $emails_dir = get_stylesheet_directory() . '/' . $template_dir . '/emails';
998 $templates_dir = get_stylesheet_directory() . '/' . $template_dir;
999 $theme_dir = get_stylesheet_directory();
1000
1001 if ( is_dir( $emails_dir ) ) {
1002 $target_dir = $emails_dir;
1003 } elseif ( is_dir( $templates_dir ) ) {
1004 $target_dir = $templates_dir;
1005 } else {
1006 $target_dir = $theme_dir;
1007 }
1008
1009 if ( is_writable( $target_dir ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
1010 ?>
1011 <a href="<?php echo esc_url( wp_nonce_url( remove_query_arg( array( 'delete_template', 'saved' ), add_query_arg( 'move_template', $template_type ) ), 'woocommerce_email_template_nonce', '_wc_email_nonce' ) ); ?>" class="button">
1012 <?php esc_html_e( 'Copy file to theme', 'woocommerce' ); ?>
1013 </a>
1014 <?php endif; ?>
1015
1016 <?php
1017 /* translators: 1: Path to template file 2: Path to theme folder */
1018 printf( esc_html__( 'To override and edit this email template copy %1$s to your theme folder: %2$s.', 'woocommerce' ), '<code>' . esc_html( plugin_basename( $template_file ) ) . '</code>', '<code>' . esc_html( trailingslashit( basename( get_stylesheet_directory() ) ) . $template_dir . '/' . $template ) . '</code>' );
1019 ?>
1020 </p>
1021
1022 <div class="editor" style="display:none">
1023 <textarea class="code" readonly="readonly" disabled="disabled" cols="25" rows="20"><?php echo esc_html( file_get_contents( $template_file ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents ?></textarea>
1024 </div>
1025 <?php else : ?>
1026 <p><?php esc_html_e( 'File was not found.', 'woocommerce' ); ?></p>
1027 <?php endif; ?>
1028 </div>
1029 <?php endforeach; ?>
1030 </div>
1031
1032 <?php
1033 wc_enqueue_js(
1034 "jQuery( 'select.email_type' ).on( 'change', function() {
1035
1036 var val = jQuery( this ).val();
1037
1038 jQuery( '.template_plain, .template_html' ).show();
1039
1040 if ( val != 'multipart' && val != 'html' ) {
1041 jQuery('.template_html').hide();
1042 }
1043
1044 if ( val != 'multipart' && val != 'plain' ) {
1045 jQuery('.template_plain').hide();
1046 }
1047
1048 }).trigger( 'change' );
1049
1050 var view = '" . esc_js( __( 'View template', 'woocommerce' ) ) . "';
1051 var hide = '" . esc_js( __( 'Hide template', 'woocommerce' ) ) . "';
1052
1053 jQuery( 'a.toggle_editor' ).text( view ).on( 'click', function() {
1054 var label = hide;
1055
1056 if ( jQuery( this ).closest(' .template' ).find( '.editor' ).is(':visible') ) {
1057 var label = view;
1058 }
1059
1060 jQuery( this ).text( label ).closest(' .template' ).find( '.editor' ).slideToggle();
1061 return false;
1062 } );
1063
1064 jQuery( 'a.delete_template' ).on( 'click', function() {
1065 if ( window.confirm('" . esc_js( __( 'Are you sure you want to delete this template file?', 'woocommerce' ) ) . "') ) {
1066 return true;
1067 }
1068
1069 return false;
1070 });
1071
1072 jQuery( '.editor textarea' ).on( 'change', function() {
1073 var name = jQuery( this ).attr( 'data-name' );
1074
1075 if ( name ) {
1076 jQuery( this ).attr( 'name', name );
1077 }
1078 });"
1079 );
1080 }
1081 }
1082 }
1083