PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 4.4.0-rc.1
WooCommerce v4.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 6 years ago class-wc-email-customer-completed-order.php 6 years ago class-wc-email-customer-invoice.php 6 years ago class-wc-email-customer-new-account.php 6 years ago class-wc-email-customer-note.php 6 years ago class-wc-email-customer-on-hold-order.php 6 years ago class-wc-email-customer-processing-order.php 6 years ago class-wc-email-customer-refunded-order.php 6 years ago class-wc-email-customer-reset-password.php 6 years ago class-wc-email-failed-order.php 6 years ago class-wc-email-new-order.php 6 years ago class-wc-email.php 6 years ago
class-wc-email.php
1078 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 * Get the email content in HTML format.
607 *
608 * @return string
609 */
610 public function get_content_html() {
611 return ''; }
612
613 /**
614 * Get the from name for outgoing emails.
615 *
616 * @param string $from_name Default wp_mail() name associated with the "from" email address.
617 * @return string
618 */
619 public function get_from_name( $from_name = '' ) {
620 $from_name = apply_filters( 'woocommerce_email_from_name', get_option( 'woocommerce_email_from_name' ), $this, $from_name );
621 return wp_specialchars_decode( esc_html( $from_name ), ENT_QUOTES );
622 }
623
624 /**
625 * Get the from address for outgoing emails.
626 *
627 * @param string $from_email Default wp_mail() email address to send from.
628 * @return string
629 */
630 public function get_from_address( $from_email = '' ) {
631 $from_email = apply_filters( 'woocommerce_email_from_address', get_option( 'woocommerce_email_from_address' ), $this, $from_email );
632 return sanitize_email( $from_email );
633 }
634
635 /**
636 * Send an email.
637 *
638 * @param string $to Email to.
639 * @param string $subject Email subject.
640 * @param string $message Email message.
641 * @param string $headers Email headers.
642 * @param array $attachments Email attachments.
643 * @return bool success
644 */
645 public function send( $to, $subject, $message, $headers, $attachments ) {
646 add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
647 add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
648 add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
649
650 $message = apply_filters( 'woocommerce_mail_content', $this->style_inline( $message ) );
651 $mail_callback = apply_filters( 'woocommerce_mail_callback', 'wp_mail', $this );
652 $mail_callback_params = apply_filters( 'woocommerce_mail_callback_params', array( $to, $subject, $message, $headers, $attachments ), $this );
653 $return = $mail_callback( ...$mail_callback_params );
654
655 remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
656 remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
657 remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
658
659 return $return;
660 }
661
662 /**
663 * Initialise Settings Form Fields - these are generic email options most will use.
664 */
665 public function init_form_fields() {
666 /* translators: %s: list of placeholders */
667 $placeholder_text = sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '<code>' . esc_html( implode( '</code>, <code>', array_keys( $this->placeholders ) ) ) . '</code>' );
668 $this->form_fields = array(
669 'enabled' => array(
670 'title' => __( 'Enable/Disable', 'woocommerce' ),
671 'type' => 'checkbox',
672 'label' => __( 'Enable this email notification', 'woocommerce' ),
673 'default' => 'yes',
674 ),
675 'subject' => array(
676 'title' => __( 'Subject', 'woocommerce' ),
677 'type' => 'text',
678 'desc_tip' => true,
679 'description' => $placeholder_text,
680 'placeholder' => $this->get_default_subject(),
681 'default' => '',
682 ),
683 'heading' => array(
684 'title' => __( 'Email heading', 'woocommerce' ),
685 'type' => 'text',
686 'desc_tip' => true,
687 'description' => $placeholder_text,
688 'placeholder' => $this->get_default_heading(),
689 'default' => '',
690 ),
691 'additional_content' => array(
692 'title' => __( 'Additional content', 'woocommerce' ),
693 'description' => __( 'Text to appear below the main email content.', 'woocommerce' ) . ' ' . $placeholder_text,
694 'css' => 'width:400px; height: 75px;',
695 'placeholder' => __( 'N/A', 'woocommerce' ),
696 'type' => 'textarea',
697 'default' => $this->get_default_additional_content(),
698 'desc_tip' => true,
699 ),
700 'email_type' => array(
701 'title' => __( 'Email type', 'woocommerce' ),
702 'type' => 'select',
703 'description' => __( 'Choose which format of email to send.', 'woocommerce' ),
704 'default' => 'html',
705 'class' => 'email_type wc-enhanced-select',
706 'options' => $this->get_email_type_options(),
707 'desc_tip' => true,
708 ),
709 );
710 }
711
712 /**
713 * Email type options.
714 *
715 * @return array
716 */
717 public function get_email_type_options() {
718 $types = array( 'plain' => __( 'Plain text', 'woocommerce' ) );
719
720 if ( class_exists( 'DOMDocument' ) ) {
721 $types['html'] = __( 'HTML', 'woocommerce' );
722 $types['multipart'] = __( 'Multipart', 'woocommerce' );
723 }
724
725 return $types;
726 }
727
728 /**
729 * Admin Panel Options Processing.
730 */
731 public function process_admin_options() {
732 // Save regular options.
733 parent::process_admin_options();
734
735 $post_data = $this->get_post_data();
736
737 // Save templates.
738 if ( isset( $post_data['template_html_code'] ) ) {
739 $this->save_template( $post_data['template_html_code'], $this->template_html );
740 }
741 if ( isset( $post_data['template_plain_code'] ) ) {
742 $this->save_template( $post_data['template_plain_code'], $this->template_plain );
743 }
744 }
745
746 /**
747 * Get template.
748 *
749 * @param string $type Template type. Can be either 'template_html' or 'template_plain'.
750 * @return string
751 */
752 public function get_template( $type ) {
753 $type = basename( $type );
754
755 if ( 'template_html' === $type ) {
756 return $this->template_html;
757 } elseif ( 'template_plain' === $type ) {
758 return $this->template_plain;
759 }
760 return '';
761 }
762
763 /**
764 * Save the email templates.
765 *
766 * @since 2.4.0
767 * @param string $template_code Template code.
768 * @param string $template_path Template path.
769 */
770 protected function save_template( $template_code, $template_path ) {
771 if ( current_user_can( 'edit_themes' ) && ! empty( $template_code ) && ! empty( $template_path ) ) {
772 $saved = false;
773 $file = get_stylesheet_directory() . '/' . WC()->template_path() . $template_path;
774 $code = wp_unslash( $template_code );
775
776 if ( is_writeable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writeable
777 $f = fopen( $file, 'w+' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
778
779 if ( false !== $f ) {
780 fwrite( $f, $code ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
781 fclose( $f ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
782 $saved = true;
783 }
784 }
785
786 if ( ! $saved ) {
787 $redirect = add_query_arg( 'wc_error', rawurlencode( __( 'Could not write to template file.', 'woocommerce' ) ) );
788 wp_safe_redirect( $redirect );
789 exit;
790 }
791 }
792 }
793
794 /**
795 * Get the template file in the current theme.
796 *
797 * @param string $template Template name.
798 *
799 * @return string
800 */
801 public function get_theme_template_file( $template ) {
802 return get_stylesheet_directory() . '/' . apply_filters( 'woocommerce_template_directory', 'woocommerce', $template ) . '/' . $template;
803 }
804
805 /**
806 * Move template action.
807 *
808 * @param string $template_type Template type.
809 */
810 protected function move_template_action( $template_type ) {
811 $template = $this->get_template( $template_type );
812 if ( ! empty( $template ) ) {
813 $theme_file = $this->get_theme_template_file( $template );
814
815 if ( wp_mkdir_p( dirname( $theme_file ) ) && ! file_exists( $theme_file ) ) {
816
817 // Locate template file.
818 $core_file = $this->template_base . $template;
819 $template_file = apply_filters( 'woocommerce_locate_core_template', $core_file, $template, $this->template_base, $this->id );
820
821 // Copy template file.
822 copy( $template_file, $theme_file );
823
824 /**
825 * Action hook fired after copying email template file.
826 *
827 * @param string $template_type The copied template type
828 * @param string $email The email object
829 */
830 do_action( 'woocommerce_copy_email_template', $template_type, $this );
831
832 ?>
833 <div class="updated">
834 <p><?php echo esc_html__( 'Template file copied to theme.', 'woocommerce' ); ?></p>
835 </div>
836 <?php
837 }
838 }
839 }
840
841 /**
842 * Delete template action.
843 *
844 * @param string $template_type Template type.
845 */
846 protected function delete_template_action( $template_type ) {
847 $template = $this->get_template( $template_type );
848
849 if ( $template ) {
850 if ( ! empty( $template ) ) {
851 $theme_file = $this->get_theme_template_file( $template );
852
853 if ( file_exists( $theme_file ) ) {
854 unlink( $theme_file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
855
856 /**
857 * Action hook fired after deleting template file.
858 *
859 * @param string $template The deleted template type
860 * @param string $email The email object
861 */
862 do_action( 'woocommerce_delete_email_template', $template_type, $this );
863 ?>
864 <div class="updated">
865 <p><?php echo esc_html__( 'Template file deleted from theme.', 'woocommerce' ); ?></p>
866 </div>
867 <?php
868 }
869 }
870 }
871 }
872
873 /**
874 * Admin actions.
875 */
876 protected function admin_actions() {
877 // Handle any actions.
878 if (
879 ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) )
880 && ( ! empty( $_GET['move_template'] ) || ! empty( $_GET['delete_template'] ) )
881 && 'GET' === $_SERVER['REQUEST_METHOD'] // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
882 ) {
883 if ( empty( $_GET['_wc_email_nonce'] ) || ! wp_verify_nonce( wc_clean( wp_unslash( $_GET['_wc_email_nonce'] ) ), 'woocommerce_email_template_nonce' ) ) {
884 wp_die( esc_html__( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
885 }
886
887 if ( ! current_user_can( 'edit_themes' ) ) {
888 wp_die( esc_html__( 'You don&#8217;t have permission to do this.', 'woocommerce' ) );
889 }
890
891 if ( ! empty( $_GET['move_template'] ) ) {
892 $this->move_template_action( wc_clean( wp_unslash( $_GET['move_template'] ) ) );
893 }
894
895 if ( ! empty( $_GET['delete_template'] ) ) {
896 $this->delete_template_action( wc_clean( wp_unslash( $_GET['delete_template'] ) ) );
897 }
898 }
899 }
900
901 /**
902 * Admin Options.
903 *
904 * Setup the email settings screen.
905 * Override this in your email.
906 *
907 * @since 1.0.0
908 */
909 public function admin_options() {
910 // Do admin actions.
911 $this->admin_actions();
912 ?>
913 <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>
914
915 <?php echo wpautop( wp_kses_post( $this->get_description() ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?>
916
917 <?php
918 /**
919 * Action hook fired before displaying email settings.
920 *
921 * @param string $email The email object
922 */
923 do_action( 'woocommerce_email_settings_before', $this );
924 ?>
925
926 <table class="form-table">
927 <?php $this->generate_settings_html(); ?>
928 </table>
929
930 <?php
931 /**
932 * Action hook fired after displaying email settings.
933 *
934 * @param string $email The email object
935 */
936 do_action( 'woocommerce_email_settings_after', $this );
937 ?>
938
939 <?php
940
941 if ( current_user_can( 'edit_themes' ) && ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) ) ) {
942 ?>
943 <div id="template">
944 <?php
945 $templates = array(
946 'template_html' => __( 'HTML template', 'woocommerce' ),
947 'template_plain' => __( 'Plain text template', 'woocommerce' ),
948 );
949
950 foreach ( $templates as $template_type => $title ) :
951 $template = $this->get_template( $template_type );
952
953 if ( empty( $template ) ) {
954 continue;
955 }
956
957 $local_file = $this->get_theme_template_file( $template );
958 $core_file = $this->template_base . $template;
959 $template_file = apply_filters( 'woocommerce_locate_core_template', $core_file, $template, $this->template_base, $this->id );
960 $template_dir = apply_filters( 'woocommerce_template_directory', 'woocommerce', $template );
961 ?>
962 <div class="template <?php echo esc_attr( $template_type ); ?>">
963 <h4><?php echo wp_kses_post( $title ); ?></h4>
964
965 <?php if ( file_exists( $local_file ) ) : ?>
966 <p>
967 <a href="#" class="button toggle_editor"></a>
968
969 <?php if ( is_writable( $local_file ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable ?>
970 <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">
971 <?php esc_html_e( 'Delete template file', 'woocommerce' ); ?>
972 </a>
973 <?php endif; ?>
974
975 <?php
976 /* translators: %s: Path to template file */
977 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>' );
978 ?>
979 </p>
980
981 <div class="editor" style="display:none">
982 <textarea class="code" cols="25" rows="20"
983 <?php
984 if ( ! is_writable( $local_file ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
985 ?>
986 readonly="readonly" disabled="disabled"
987 <?php else : ?>
988 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>
989 </div>
990 <?php elseif ( file_exists( $template_file ) ) : ?>
991 <p>
992 <a href="#" class="button toggle_editor"></a>
993
994 <?php
995 $emails_dir = get_stylesheet_directory() . '/' . $template_dir . '/emails';
996 $templates_dir = get_stylesheet_directory() . '/' . $template_dir;
997 $theme_dir = get_stylesheet_directory();
998
999 if ( is_dir( $emails_dir ) ) {
1000 $target_dir = $emails_dir;
1001 } elseif ( is_dir( $templates_dir ) ) {
1002 $target_dir = $templates_dir;
1003 } else {
1004 $target_dir = $theme_dir;
1005 }
1006
1007 if ( is_writable( $target_dir ) ) : // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
1008 ?>
1009 <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">
1010 <?php esc_html_e( 'Copy file to theme', 'woocommerce' ); ?>
1011 </a>
1012 <?php endif; ?>
1013
1014 <?php
1015 /* translators: 1: Path to template file 2: Path to theme folder */
1016 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>' );
1017 ?>
1018 </p>
1019
1020 <div class="editor" style="display:none">
1021 <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>
1022 </div>
1023 <?php else : ?>
1024 <p><?php esc_html_e( 'File was not found.', 'woocommerce' ); ?></p>
1025 <?php endif; ?>
1026 </div>
1027 <?php endforeach; ?>
1028 </div>
1029
1030 <?php
1031 wc_enqueue_js(
1032 "jQuery( 'select.email_type' ).change( function() {
1033
1034 var val = jQuery( this ).val();
1035
1036 jQuery( '.template_plain, .template_html' ).show();
1037
1038 if ( val != 'multipart' && val != 'html' ) {
1039 jQuery('.template_html').hide();
1040 }
1041
1042 if ( val != 'multipart' && val != 'plain' ) {
1043 jQuery('.template_plain').hide();
1044 }
1045
1046 }).change();
1047
1048 var view = '" . esc_js( __( 'View template', 'woocommerce' ) ) . "';
1049 var hide = '" . esc_js( __( 'Hide template', 'woocommerce' ) ) . "';
1050
1051 jQuery( 'a.toggle_editor' ).text( view ).toggle( function() {
1052 jQuery( this ).text( hide ).closest(' .template' ).find( '.editor' ).slideToggle();
1053 return false;
1054 }, function() {
1055 jQuery( this ).text( view ).closest( '.template' ).find( '.editor' ).slideToggle();
1056 return false;
1057 } );
1058
1059 jQuery( 'a.delete_template' ).click( function() {
1060 if ( window.confirm('" . esc_js( __( 'Are you sure you want to delete this template file?', 'woocommerce' ) ) . "') ) {
1061 return true;
1062 }
1063
1064 return false;
1065 });
1066
1067 jQuery( '.editor textarea' ).change( function() {
1068 var name = jQuery( this ).attr( 'data-name' );
1069
1070 if ( name ) {
1071 jQuery( this ).attr( 'name', name );
1072 }
1073 });"
1074 );
1075 }
1076 }
1077 }
1078