Completed
Push — master ( 2e553c...66decd )
by James
10:10
created

WC_Emails   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 440
Duplicated Lines 2.73 %

Coupling/Cohesion

Components 3
Dependencies 3
Metric Value
wmc 54
lcom 3
cbo 3
dl 12
loc 440
rs 7.0642

25 Methods

Rating   Name   Duplication   Size   Complexity  
A instance() 0 6 2
A __clone() 0 3 1
A __wakeup() 0 3 1
B init_transactional_emails() 0 26 2
A send_transactional_email() 0 5 1
A __construct() 0 20 1
A init() 0 22 3
A get_emails() 0 3 1
A get_from_name() 0 3 1
A get_from_address() 0 3 1
A email_header() 0 3 1
A email_footer() 0 3 1
A wrap_message() 0 15 1
A send() 0 5 1
A customer_invoice() 0 4 1
A customer_new_account() 0 10 3
A order_details() 0 7 2
C order_meta() 6 52 15
A customer_detail_field_is_valid() 0 3 2
B customer_details() 6 25 4
A email_addresses() 0 7 2
A get_blogname() 0 3 1
A low_stock() 0 12 1
A no_stock() 0 12 1
B backorder() 0 24 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Emails often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Emails, and based on these observations, apply Extract Interface, too.

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * Transactional Emails Controller
9
 *
10
 * WooCommerce Emails Class which handles the sending on transactional emails and email templates. This class loads in available emails.
11
 *
12
 * @class 		WC_Emails
13
 * @version		2.3.0
14
 * @package		WooCommerce/Classes/Emails
15
 * @category	Class
16
 * @author 		WooThemes
17
 */
18
class WC_Emails {
19
20
	/** @var array Array of email notification classes */
21
	public $emails;
22
23
	/** @var WC_Emails The single instance of the class */
24
	protected static $_instance = null;
25
26
	/**
27
	 * Main WC_Emails Instance.
28
	 *
29
	 * Ensures only one instance of WC_Emails is loaded or can be loaded.
30
	 *
31
	 * @since 2.1
32
	 * @static
33
	 * @return WC_Emails Main instance
34
	 */
35
	public static function instance() {
36
		if ( is_null( self::$_instance ) ) {
37
			self::$_instance = new self();
38
		}
39
		return self::$_instance;
40
	}
41
42
	/**
43
	 * Cloning is forbidden.
44
	 *
45
	 * @since 2.1
46
	 */
47
	public function __clone() {
48
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
49
	}
50
51
	/**
52
	 * Unserializing instances of this class is forbidden.
53
	 *
54
	 * @since 2.1
55
	 */
56
	public function __wakeup() {
57
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
58
	}
59
60
	/**
61
	 * Hook in all transactional emails.
62
	 */
63
	public static function init_transactional_emails() {
64
		$email_actions = apply_filters( 'woocommerce_email_actions', array(
65
			'woocommerce_low_stock',
66
			'woocommerce_no_stock',
67
			'woocommerce_product_on_backorder',
68
			'woocommerce_order_status_pending_to_processing',
69
			'woocommerce_order_status_pending_to_completed',
70
			'woocommerce_order_status_pending_to_cancelled',
71
			'woocommerce_order_status_pending_to_failed',
72
			'woocommerce_order_status_pending_to_on-hold',
73
			'woocommerce_order_status_failed_to_processing',
74
			'woocommerce_order_status_failed_to_completed',
75
			'woocommerce_order_status_on-hold_to_processing',
76
			'woocommerce_order_status_on-hold_to_cancelled',
77
			'woocommerce_order_status_on-hold_to_failed',
78
			'woocommerce_order_status_completed',
79
			'woocommerce_order_fully_refunded',
80
			'woocommerce_order_partially_refunded',
81
			'woocommerce_new_customer_note',
82
			'woocommerce_created_customer'
83
		) );
84
85
		foreach ( $email_actions as $action ) {
86
			add_action( $action, array( __CLASS__, 'send_transactional_email' ), 10, 10 );
87
		}
88
	}
89
90
	/**
91
	 * Init the mailer instance and call the notifications for the current filter.
92
	 * @internal param array $args (default: array())
93
	 */
94
	public static function send_transactional_email() {
95
		self::instance();
96
		$args = func_get_args();
97
		do_action_ref_array( current_filter() . '_notification', $args );
98
	}
99
100
	/**
101
	 * Constructor for the email class hooks in all emails that can be sent.
102
	 *
103
	 */
104
	public function __construct() {
105
		$this->init();
106
107
		// Email Header, Footer and content hooks
108
		add_action( 'woocommerce_email_header', array( $this, 'email_header' ) );
109
		add_action( 'woocommerce_email_footer', array( $this, 'email_footer' ) );
110
		add_action( 'woocommerce_email_order_details', array( $this, 'order_details' ), 10, 4 );
111
		add_action( 'woocommerce_email_order_meta', array( $this, 'order_meta' ), 10, 3 );
112
		add_action( 'woocommerce_email_customer_details', array( $this, 'customer_details' ), 10, 3 );
113
		add_action( 'woocommerce_email_customer_details', array( $this, 'email_addresses' ), 20, 3 );
114
115
		// Hooks for sending emails during store events
116
		add_action( 'woocommerce_low_stock_notification', array( $this, 'low_stock' ) );
117
		add_action( 'woocommerce_no_stock_notification', array( $this, 'no_stock' ) );
118
		add_action( 'woocommerce_product_on_backorder_notification', array( $this, 'backorder' ) );
119
		add_action( 'woocommerce_created_customer_notification', array( $this, 'customer_new_account' ), 10, 3 );
120
121
		// Let 3rd parties unhook the above via this hook
122
		do_action( 'woocommerce_email', $this );
123
	}
124
125
	/**
126
	 * Init email classes.
127
	 */
128
	public function init() {
129
		// Include email classes
130
		include_once( 'emails/class-wc-email.php' );
131
132
		$this->emails['WC_Email_New_Order'] 		                 = include( 'emails/class-wc-email-new-order.php' );
133
		$this->emails['WC_Email_Cancelled_Order'] 		             = include( 'emails/class-wc-email-cancelled-order.php' );
134
		$this->emails['WC_Email_Failed_Order'] 		                 = include( 'emails/class-wc-email-failed-order.php' );
135
		$this->emails['WC_Email_Customer_Processing_Order'] 		 = include( 'emails/class-wc-email-customer-processing-order.php' );
136
		$this->emails['WC_Email_Customer_Completed_Order'] 		     = include( 'emails/class-wc-email-customer-completed-order.php' );
137
		$this->emails['WC_Email_Customer_Refunded_Order'] 		     = include( 'emails/class-wc-email-customer-refunded-order.php' );
138
		$this->emails['WC_Email_Customer_Invoice'] 		             = include( 'emails/class-wc-email-customer-invoice.php' );
139
		$this->emails['WC_Email_Customer_Note'] 		             = include( 'emails/class-wc-email-customer-note.php' );
140
		$this->emails['WC_Email_Customer_Reset_Password'] 		     = include( 'emails/class-wc-email-customer-reset-password.php' );
141
		$this->emails['WC_Email_Customer_New_Account'] 		         = include( 'emails/class-wc-email-customer-new-account.php' );
142
143
		$this->emails = apply_filters( 'woocommerce_email_classes', $this->emails );
144
145
		// include css inliner
146
		if ( ! class_exists( 'Emogrifier' ) && class_exists( 'DOMDocument' ) ) {
147
			include_once( 'libraries/class-emogrifier.php' );
148
		}
149
	}
150
151
	/**
152
	 * Return the email classes - used in admin to load settings.
153
	 *
154
	 * @return array
155
	 */
156
	public function get_emails() {
157
		return $this->emails;
158
	}
159
160
	/**
161
	 * Get from name for email.
162
	 *
163
	 * @return string
164
	 */
165
	public function get_from_name() {
166
		return wp_specialchars_decode( get_option( 'woocommerce_email_from_name' ), ENT_QUOTES );
167
	}
168
169
	/**
170
	 * Get from email address.
171
	 *
172
	 * @return string
173
	 */
174
	public function get_from_address() {
175
		return sanitize_email( get_option( 'woocommerce_email_from_address' ) );
176
	}
177
178
	/**
179
	 * Get the email header.
180
	 *
181
	 * @param mixed $email_heading heading for the email
182
	 */
183
	public function email_header( $email_heading ) {
184
		wc_get_template( 'emails/email-header.php', array( 'email_heading' => $email_heading ) );
185
	}
186
187
	/**
188
	 * Get the email footer.
189
	 */
190
	public function email_footer() {
191
		wc_get_template( 'emails/email-footer.php' );
192
	}
193
194
	/**
195
	 * Wraps a message in the woocommerce mail template.
196
	 *
197
	 * @param mixed $email_heading
198
	 * @param string $message
199
	 * @return string
200
	 */
201
	public function wrap_message( $email_heading, $message, $plain_text = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $plain_text is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
202
		// Buffer
203
		ob_start();
204
205
		do_action( 'woocommerce_email_header', $email_heading );
206
207
		echo wpautop( wptexturize( $message ) );
208
209
		do_action( 'woocommerce_email_footer' );
210
211
		// Get contents
212
		$message = ob_get_clean();
213
214
		return $message;
215
	}
216
217
	/**
218
	 * Send the email.
219
	 *
220
	 * @param mixed $to
221
	 * @param mixed $subject
222
	 * @param mixed $message
223
	 * @param string $headers (default: "Content-Type: text/html\r\n")
224
	 * @param string $attachments (default: "")
225
	 * @return bool
226
	 */
227
	public function send( $to, $subject, $message, $headers = "Content-Type: text/html\r\n", $attachments = "" ) {
228
		// Send
229
		$email = new WC_Email();
230
		return $email->send( $to, $subject, $message, $headers, $attachments );
231
	}
232
233
	/**
234
	 * Prepare and send the customer invoice email on demand.
235
	 */
236
	public function customer_invoice( $order ) {
237
		$email = $this->emails['WC_Email_Customer_Invoice'];
238
		$email->trigger( $order );
239
	}
240
241
	/**
242
	 * Customer new account welcome email.
243
	 *
244
	 * @param int $customer_id
245
	 * @param array $new_customer_data
246
	 */
247
	public function customer_new_account( $customer_id, $new_customer_data = array(), $password_generated = false ) {
248
		if ( ! $customer_id ) {
249
			return;
250
		}
251
252
		$user_pass = ! empty( $new_customer_data['user_pass'] ) ? $new_customer_data['user_pass'] : '';
253
254
		$email = $this->emails['WC_Email_Customer_New_Account'];
255
		$email->trigger( $customer_id, $user_pass, $password_generated );
256
	}
257
258
	/**
259
	 * Show the order details table
260
	 */
261
	public function order_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
262
		if ( $plain_text ) {
263
			wc_get_template( 'emails/plain/email-order-details.php', array( 'order' => $order, 'sent_to_admin' => $sent_to_admin, 'plain_text' => $plain_text, 'email' => $email ) );
264
		} else {
265
			wc_get_template( 'emails/email-order-details.php', array( 'order' => $order, 'sent_to_admin' => $sent_to_admin, 'plain_text' => $plain_text, 'email' => $email ) );
266
		}
267
	}
268
269
	/**
270
	 * Add order meta to email templates.
271
	 *
272
	 * @param mixed $order
273
	 * @param bool $sent_to_admin (default: false)
274
	 * @param bool $plain_text (default: false)
275
	 * @return string
276
	 */
277
	public function order_meta( $order, $sent_to_admin = false, $plain_text = false ) {
278
		$fields = array();
279
280 View Code Duplication
		if ( $order->customer_note ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
281
			$fields['customer_note'] = array(
282
				'label' => __( 'Note', 'woocommerce' ),
283
				'value' => wptexturize( $order->customer_note )
284
			);
285
		}
286
287
		$fields = apply_filters( 'woocommerce_email_order_meta_fields', $fields, $sent_to_admin, $order );
288
289
		/**
290
		 * Deprecated woocommerce_email_order_meta_keys filter.
291
		 *
292
		 * @since 2.3.0
293
		 */
294
		$_fields = apply_filters( 'woocommerce_email_order_meta_keys', array(), $sent_to_admin );
295
296
		if ( $_fields ) {
297
			foreach ( $_fields as $key => $field ) {
298
				if ( is_numeric( $key ) ) {
299
					$key = $field;
300
				}
301
302
				$fields[ $key ] = array(
303
					'label' => wptexturize( $key ),
304
					'value' => wptexturize( get_post_meta( $order->id, $field, true ) )
305
				);
306
			}
307
		}
308
309
		if ( $fields ) {
310
311
			if ( $plain_text ) {
312
313
				foreach ( $fields as $field ) {
314
					if ( isset( $field['label'] ) && isset( $field['value'] ) && $field['value'] ) {
315
						echo $field['label'] . ': ' . $field['value'] . "\n";
316
					}
317
				}
318
319
			} else {
320
321
				foreach ( $fields as $field ) {
322
					if ( isset( $field['label'] ) && isset( $field['value'] ) && $field['value'] ) {
323
						echo '<p><strong>' . $field['label'] . ':</strong> ' . $field['value'] . '</p>';
324
					}
325
				}
326
			}
327
		}
328
	}
329
330
	/**
331
	 * Is customer detail field valid?
332
	 * @param  array  $field
333
	 * @return boolean
334
	 */
335
	public function customer_detail_field_is_valid( $field ) {
336
		return isset( $field['label'] ) && ! empty( $field['value'] );
337
	}
338
339
	/**
340
	 * Add customer details to email templates.
341
	 *
342
	 * @param mixed $order
343
	 * @param bool $sent_to_admin (default: false)
344
	 * @param bool $plain_text (default: false)
345
	 * @return string
346
	 */
347
	public function customer_details( $order, $sent_to_admin = false, $plain_text = false ) {
348
		$fields = array();
349
350
		if ( $order->billing_email ) {
351
			$fields['billing_email'] = array(
352
				'label' => __( 'Email', 'woocommerce' ),
353
				'value' => wptexturize( $order->billing_email )
354
			);
355
	    }
356
357 View Code Duplication
	    if ( $order->billing_phone ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
			$fields['billing_phone'] = array(
359
				'label' => __( 'Tel', 'woocommerce' ),
360
				'value' => wptexturize( $order->billing_phone )
361
			);
362
	    }
363
364
		$fields = array_filter( apply_filters( 'woocommerce_email_customer_details_fields', $fields, $sent_to_admin, $order ), array( $this, 'customer_detail_field_is_valid' ) );
365
366
		if ( $plain_text ) {
367
			wc_get_template( 'emails/plain/email-customer-details.php', array( 'fields' => $fields ) );
368
		} else {
369
			wc_get_template( 'emails/email-customer-details.php', array( 'fields' => $fields ) );
370
		}
371
	}
372
373
	/**
374
	 * Get the email addresses.
375
	 */
376
	public function email_addresses( $order, $sent_to_admin = false, $plain_text = false ) {
377
		if ( $plain_text ) {
378
			wc_get_template( 'emails/plain/email-addresses.php', array( 'order' => $order ) );
379
		} else {
380
			wc_get_template( 'emails/email-addresses.php', array( 'order' => $order ) );
381
		}
382
	}
383
384
	/**
385
	 * Get blog name formatted for emails.
386
	 * @return string
387
	 */
388
	private function get_blogname() {
389
		return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
390
	}
391
392
	/**
393
	 * Low stock notification email.
394
	 *
395
	 * @param WC_Product $product
396
	 */
397
	public function low_stock( $product ) {
398
		$subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product low in stock', 'woocommerce' ) );
399
		$message = sprintf( __( '%s is low in stock.', 'woocommerce' ), html_entity_decode( strip_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ) ) . ' ' . sprintf( __( 'There are %d left', 'woocommerce' ), html_entity_decode( strip_tags( $product->get_total_stock() ) ) );
400
401
		wp_mail(
402
			apply_filters( 'woocommerce_email_recipient_low_stock', get_option( 'woocommerce_stock_email_recipient' ), $product ),
403
			apply_filters( 'woocommerce_email_subject_low_stock', $subject, $product ),
404
			apply_filters( 'woocommerce_email_content_low_stock', $message, $product ),
405
			apply_filters( 'woocommerce_email_headers', '', 'low_stock', $product ),
406
			apply_filters( 'woocommerce_email_attachments', array(), 'low_stock', $product )
407
		);
408
	}
409
410
	/**
411
	 * No stock notification email.
412
	 *
413
	 * @param WC_Product $product
414
	 */
415
	public function no_stock( $product ) {
416
		$subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product out of stock', 'woocommerce' ) );
417
		$message = sprintf( __( '%s is out of stock.', 'woocommerce' ), html_entity_decode( strip_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
418
419
		wp_mail(
420
			apply_filters( 'woocommerce_email_recipient_no_stock', get_option( 'woocommerce_stock_email_recipient' ), $product ),
421
			apply_filters( 'woocommerce_email_subject_no_stock', $subject, $product ),
422
			apply_filters( 'woocommerce_email_content_no_stock', $message, $product ),
423
			apply_filters( 'woocommerce_email_headers', '', 'no_stock', $product ),
424
			apply_filters( 'woocommerce_email_attachments', array(), 'no_stock', $product )
425
		);
426
	}
427
428
	/**
429
	 * Backorder notification email.
430
	 *
431
	 * @param array $args
432
	 */
433
	public function backorder( $args ) {
434
		$args = wp_parse_args( $args, array(
435
			'product'  => '',
436
			'quantity' => '',
437
			'order_id' => ''
438
		) );
439
440
		extract( $args );
441
442
		if ( ! $product || ! $quantity || ! ( $order = wc_get_order( $order_id ) ) ) {
443
			return;
444
		}
445
446
		$subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product Backorder', 'woocommerce' ) );
447
		$message = sprintf( __( '%s units of %s have been backordered in order #%s.', 'woocommerce' ), $quantity, html_entity_decode( strip_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ), $order->get_order_number() );
448
449
		wp_mail(
450
			apply_filters( 'woocommerce_email_recipient_backorder', get_option( 'woocommerce_stock_email_recipient' ), $args ),
451
			apply_filters( 'woocommerce_email_subject_backorder', $subject, $args ),
452
			apply_filters( 'woocommerce_email_content_backorder', $message, $args ),
453
			apply_filters( 'woocommerce_email_headers', '', 'backorder', $args ),
454
			apply_filters( 'woocommerce_email_attachments', array(), 'backorder', $args )
455
		);
456
	}
457
}
458