PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 9.9.7
WooCommerce v9.9.7
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 / class-wc-emails.php
woocommerce / includes Last commit date
abstracts 11 months ago admin 11 months ago blocks 1 year ago cli 1 year ago customizer 1 year ago data-stores 1 year ago emails 11 months ago export 1 year ago gateways 1 year ago import 1 year ago integrations 2 years ago interfaces 1 year ago legacy 1 year ago libraries 1 year ago log-handlers 1 year ago payment-tokens 5 years ago product-usage 1 year ago queue 4 years ago react-admin 1 year ago rest-api 11 months ago shipping 1 year ago shortcodes 1 year ago theme-support 2 years ago tracks 1 year ago traits 5 years ago walkers 5 years ago wccom-site 1 year ago widgets 1 year ago class-wc-ajax.php 1 year ago class-wc-auth.php 1 year ago class-wc-autoloader.php 1 year ago class-wc-background-emailer.php 5 years ago class-wc-background-updater.php 5 years ago class-wc-brands-brand-settings-manager.php 1 year ago class-wc-brands-coupons.php 1 year ago class-wc-brands.php 1 year ago class-wc-breadcrumb.php 5 years ago class-wc-cache-helper.php 1 year ago class-wc-cart-fees.php 2 years ago class-wc-cart-session.php 1 year ago class-wc-cart-totals.php 1 year ago class-wc-cart.php 1 year ago class-wc-checkout.php 1 year ago class-wc-cli.php 1 year ago class-wc-comments.php 1 year ago class-wc-countries.php 1 year ago class-wc-coupon.php 1 year ago class-wc-customer-download-log.php 5 years ago class-wc-customer-download.php 1 year ago class-wc-customer.php 1 year ago class-wc-data-exception.php 8 years ago class-wc-data-store.php 3 years ago class-wc-datetime.php 4 years ago class-wc-deprecated-action-hooks.php 2 years ago class-wc-deprecated-filter-hooks.php 3 years ago class-wc-discounts.php 1 year ago class-wc-download-handler.php 1 year ago class-wc-emails.php 1 year ago class-wc-embed.php 1 year ago class-wc-form-handler.php 1 year ago class-wc-frontend-scripts.php 1 year ago class-wc-geo-ip.php 1 year ago class-wc-geolite-integration.php 6 years ago class-wc-geolocation.php 1 year ago class-wc-https.php 2 years ago class-wc-install.php 1 year ago class-wc-integrations.php 5 years ago class-wc-log-levels.php 2 years ago class-wc-logger.php 1 year ago class-wc-meta-data.php 4 years ago class-wc-order-factory.php 2 years ago class-wc-order-item-coupon.php 4 years ago class-wc-order-item-fee.php 1 year ago class-wc-order-item-meta.php 4 years ago class-wc-order-item-product.php 1 year ago class-wc-order-item-shipping.php 1 year ago class-wc-order-item-tax.php 4 years ago class-wc-order-item.php 1 year ago class-wc-order-query.php 4 years ago class-wc-order-refund.php 1 year ago class-wc-order.php 1 year ago class-wc-payment-gateways.php 1 year ago class-wc-payment-tokens.php 3 years ago class-wc-post-data.php 1 year ago class-wc-post-types.php 1 year ago class-wc-privacy-background-process.php 1 year ago class-wc-privacy-erasers.php 1 year ago class-wc-privacy-exporters.php 4 years ago class-wc-privacy.php 1 year ago class-wc-product-attribute.php 4 years ago class-wc-product-download.php 2 years ago class-wc-product-external.php 1 year ago class-wc-product-factory.php 1 year ago class-wc-product-grouped.php 1 year ago class-wc-product-query.php 1 year ago class-wc-product-simple.php 1 year ago class-wc-product-variable.php 1 year ago class-wc-product-variation.php 1 year ago class-wc-query.php 1 year ago class-wc-rate-limiter.php 4 years ago class-wc-regenerate-images-request.php 3 years ago class-wc-regenerate-images.php 1 year ago class-wc-register-wp-admin-settings.php 4 years ago class-wc-rest-authentication.php 1 year ago class-wc-rest-exception.php 5 years ago class-wc-session-handler.php 1 year ago class-wc-shipping-rate.php 1 year ago class-wc-shipping-zone.php 5 years ago class-wc-shipping-zones.php 5 years ago class-wc-shipping.php 1 year ago class-wc-shortcodes.php 1 year ago class-wc-structured-data.php 1 year ago class-wc-tax.php 2 years ago class-wc-template-loader.php 11 months ago class-wc-tracker.php 1 year ago class-wc-validation.php 2 years ago class-wc-webhook.php 1 year ago class-woocommerce.php 3 months ago wc-account-functions.php 1 year ago wc-attribute-functions.php 1 year ago wc-brands-functions.php 1 year ago wc-cart-functions.php 1 year ago wc-conditional-functions.php 1 year ago wc-core-functions.php 1 year ago wc-coupon-functions.php 1 year ago wc-deprecated-functions.php 1 year ago wc-formatting-functions.php 1 year ago wc-notice-functions.php 1 year ago wc-order-functions.php 1 year ago wc-order-item-functions.php 3 years ago wc-order-step-logger-functions.php 1 year ago wc-page-functions.php 1 year ago wc-product-functions.php 1 year ago wc-rest-functions.php 1 year ago wc-stock-functions.php 1 year ago wc-template-functions.php 1 year ago wc-template-hooks.php 1 year ago wc-term-functions.php 11 months ago wc-update-functions.php 1 year ago wc-user-functions.php 1 year ago wc-webhook-functions.php 1 year ago wc-widget-functions.php 5 years ago
class-wc-emails.php
886 lines
1 <?php
2 /**
3 * Transactional Emails Controller
4 *
5 * WooCommerce Emails Class which handles the sending on transactional emails and email templates. This class loads in available emails.
6 *
7 * @package WooCommerce\Classes\Emails
8 * @version 2.3.0
9 */
10
11 use Automattic\Jetpack\Constants;
12 use Automattic\WooCommerce\Admin\Features\Features;
13 use Automattic\WooCommerce\Blocks\Package;
14 use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
15 use Automattic\WooCommerce\Enums\ProductType;
16 use Automattic\WooCommerce\Utilities\FeaturesUtil;
17
18 defined( 'ABSPATH' ) || exit;
19
20 /**
21 * Emails class.
22 */
23 class WC_Emails {
24
25 /**
26 * Array of email notification classes
27 *
28 * @var WC_Email[]
29 */
30 public $emails = array();
31
32 /**
33 * The single instance of the class
34 *
35 * @var WC_Emails
36 */
37 protected static $_instance = null;
38
39 /**
40 * Background emailer class.
41 *
42 * @var WC_Background_Emailer
43 */
44 protected static $background_emailer = null;
45
46 /**
47 * Main WC_Emails Instance.
48 *
49 * Ensures only one instance of WC_Emails is loaded or can be loaded.
50 *
51 * @since 2.1
52 * @static
53 * @return WC_Emails Main instance
54 */
55 public static function instance() {
56 if ( is_null( self::$_instance ) ) {
57 self::$_instance = new self();
58 }
59 return self::$_instance;
60 }
61
62 /**
63 * Cloning is forbidden.
64 *
65 * @since 2.1
66 */
67 public function __clone() {
68 wc_doing_it_wrong( __FUNCTION__, __( 'Cloning is forbidden.', 'woocommerce' ), '2.1' );
69 }
70
71 /**
72 * Unserializing instances of this class is forbidden.
73 *
74 * @since 2.1
75 */
76 public function __wakeup() {
77 wc_doing_it_wrong( __FUNCTION__, __( 'Unserializing instances of this class is forbidden.', 'woocommerce' ), '2.1' );
78 }
79
80 /**
81 * Hook in all transactional emails.
82 */
83 public static function init_transactional_emails() {
84 $email_actions = apply_filters(
85 'woocommerce_email_actions',
86 array(
87 'woocommerce_low_stock',
88 'woocommerce_no_stock',
89 'woocommerce_product_on_backorder',
90 'woocommerce_order_status_pending_to_processing',
91 'woocommerce_order_status_pending_to_completed',
92 'woocommerce_order_status_processing_to_cancelled',
93 'woocommerce_order_status_pending_to_failed',
94 'woocommerce_order_status_pending_to_on-hold',
95 'woocommerce_order_status_failed_to_processing',
96 'woocommerce_order_status_failed_to_completed',
97 'woocommerce_order_status_failed_to_on-hold',
98 'woocommerce_order_status_cancelled_to_processing',
99 'woocommerce_order_status_cancelled_to_completed',
100 'woocommerce_order_status_cancelled_to_on-hold',
101 'woocommerce_order_status_on-hold_to_processing',
102 'woocommerce_order_status_on-hold_to_cancelled',
103 'woocommerce_order_status_on-hold_to_failed',
104 'woocommerce_order_status_completed',
105 'woocommerce_order_status_failed',
106 'woocommerce_order_fully_refunded',
107 'woocommerce_order_partially_refunded',
108 'woocommerce_new_customer_note',
109 'woocommerce_created_customer',
110 )
111 );
112
113 if ( apply_filters( 'woocommerce_defer_transactional_emails', false ) ) {
114 self::$background_emailer = new WC_Background_Emailer();
115
116 foreach ( $email_actions as $action ) {
117 add_action( $action, array( __CLASS__, 'queue_transactional_email' ), 10, 10 );
118 }
119 } else {
120 foreach ( $email_actions as $action ) {
121 add_action( $action, array( __CLASS__, 'send_transactional_email' ), 10, 10 );
122 }
123 }
124 }
125
126 /**
127 * Queues transactional email so it's not sent in current request if enabled,
128 * otherwise falls back to send now.
129 *
130 * @param mixed ...$args Optional arguments.
131 */
132 public static function queue_transactional_email( ...$args ) {
133 if ( is_a( self::$background_emailer, 'WC_Background_Emailer' ) ) {
134 self::$background_emailer->push_to_queue(
135 array(
136 'filter' => current_filter(),
137 'args' => func_get_args(),
138 )
139 );
140 } else {
141 self::send_transactional_email( ...$args );
142 }
143 }
144
145 /**
146 * Init the mailer instance and call the notifications for the current filter.
147 *
148 * @internal
149 *
150 * @param string $filter Filter name.
151 * @param array $args Email args (default: []).
152 */
153 public static function send_queued_transactional_email( $filter = '', $args = array() ) {
154 if ( apply_filters( 'woocommerce_allow_send_queued_transactional_email', true, $filter, $args ) ) {
155 self::instance(); // Init self so emails exist.
156
157 // Ensure gateways are loaded in case they need to insert data into the emails.
158 WC()->payment_gateways();
159 WC()->shipping();
160
161 // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
162 /** This action is documented in includes/class-wc-emails.php in the send_transactional_email method. */
163 do_action_ref_array( $filter . '_notification', $args );
164 }
165 }
166
167 /**
168 * Init the mailer instance and call the notifications for the current filter.
169 *
170 * @internal
171 *
172 * @param array $args Email args (default: []).
173 */
174 public static function send_transactional_email( $args = array() ) {
175 try {
176 $args = func_get_args();
177 self::instance(); // Init self so emails exist.
178
179 /**
180 * Action hook for email template classes to trigger the sending of an email.
181 *
182 * The name of the hook is based on the "parent" hook that is currently firing, that this is attached to.
183 * See the WC_Emails::init_transactional_emails method for a list of hooks.
184 *
185 * @since 3.1.0
186 *
187 * @param array $args Args from the parent hook, which may differ depending on the hook.
188 */
189 do_action_ref_array( current_filter() . '_notification', $args );
190 } catch ( Exception $e ) {
191 $error = 'Transactional email triggered fatal error for callback ' . current_filter();
192 $logger = wc_get_logger();
193 $logger->critical(
194 $error . PHP_EOL,
195 array(
196 'source' => 'transactional-emails',
197 )
198 );
199 if ( Constants::is_true( 'WP_DEBUG' ) ) {
200 trigger_error( $error, E_USER_WARNING ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped, WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
201 }
202 }
203 }
204
205 /**
206 * Constructor for the email class hooks in all emails that can be sent.
207 */
208 public function __construct() {
209 $this->init();
210
211 // Email Header, Footer and content hooks.
212 add_action( 'woocommerce_email_header', array( $this, 'email_header' ) );
213 add_action( 'woocommerce_email_footer', array( $this, 'email_footer' ) );
214 add_action( 'woocommerce_email_order_details', array( $this, 'order_downloads' ), 10, 4 );
215 add_action( 'woocommerce_email_order_details', array( $this, 'order_details' ), 10, 4 );
216 add_action( 'woocommerce_email_order_meta', array( $this, 'order_meta' ), 10, 3 );
217 add_action( 'woocommerce_email_customer_details', array( $this, 'customer_details' ), 10, 3 );
218 add_action( 'woocommerce_email_customer_details', array( $this, 'email_addresses' ), 20, 3 );
219 add_action( 'woocommerce_email_customer_details', array( $this, 'additional_checkout_fields' ), 30, 3 );
220 add_action( 'woocommerce_email_customer_address_section', array( $this, 'additional_address_fields' ), 30, 4 );
221
222 // Hooks for sending emails during store events.
223 add_action( 'woocommerce_low_stock_notification', array( $this, 'low_stock' ) );
224 add_action( 'woocommerce_no_stock_notification', array( $this, 'no_stock' ) );
225 add_action( 'woocommerce_product_on_backorder_notification', array( $this, 'backorder' ) );
226 add_action( 'woocommerce_created_customer_notification', array( $this, 'customer_new_account' ), 10, 3 );
227
228 // Hook for replacing {site_title} in email-footer.
229 add_filter( 'woocommerce_email_footer_text', array( $this, 'replace_placeholders' ) );
230
231 // Let 3rd parties unhook the above via this hook.
232 do_action( 'woocommerce_email', $this );
233 }
234
235 /**
236 * Init email classes.
237 */
238 public function init() {
239 // Include email classes.
240 include_once __DIR__ . '/emails/class-wc-email.php';
241
242 $this->emails['WC_Email_New_Order'] = include __DIR__ . '/emails/class-wc-email-new-order.php';
243 $this->emails['WC_Email_Cancelled_Order'] = include __DIR__ . '/emails/class-wc-email-cancelled-order.php';
244 $this->emails['WC_Email_Failed_Order'] = include __DIR__ . '/emails/class-wc-email-failed-order.php';
245 $this->emails['WC_Email_Customer_Failed_Order'] = include __DIR__ . '/emails/class-wc-email-customer-failed-order.php';
246 $this->emails['WC_Email_Customer_On_Hold_Order'] = include __DIR__ . '/emails/class-wc-email-customer-on-hold-order.php';
247 $this->emails['WC_Email_Customer_Processing_Order'] = include __DIR__ . '/emails/class-wc-email-customer-processing-order.php';
248 $this->emails['WC_Email_Customer_Completed_Order'] = include __DIR__ . '/emails/class-wc-email-customer-completed-order.php';
249 $this->emails['WC_Email_Customer_Refunded_Order'] = include __DIR__ . '/emails/class-wc-email-customer-refunded-order.php';
250 $this->emails['WC_Email_Customer_Invoice'] = include __DIR__ . '/emails/class-wc-email-customer-invoice.php';
251 $this->emails['WC_Email_Customer_Note'] = include __DIR__ . '/emails/class-wc-email-customer-note.php';
252 $this->emails['WC_Email_Customer_Reset_Password'] = include __DIR__ . '/emails/class-wc-email-customer-reset-password.php';
253 $this->emails['WC_Email_Customer_New_Account'] = include __DIR__ . '/emails/class-wc-email-customer-new-account.php';
254
255 if ( Features::is_enabled( 'point-of-sale' ) ) {
256 $this->emails['WC_Email_Customer_POS_Completed_Order'] = include __DIR__ . '/emails/class-wc-email-customer-pos-completed-order.php';
257 $this->emails['WC_Email_Customer_POS_Refunded_Order'] = include __DIR__ . '/emails/class-wc-email-customer-pos-refunded-order.php';
258 }
259
260 $this->emails = apply_filters( 'woocommerce_email_classes', $this->emails );
261 }
262
263 /**
264 * Return the email classes - used in admin to load settings.
265 *
266 * @return WC_Email[]
267 */
268 public function get_emails() {
269 return $this->emails;
270 }
271
272 /**
273 * Get from name for email.
274 *
275 * @return string
276 */
277 public function get_from_name() {
278 return wp_specialchars_decode( get_option( 'woocommerce_email_from_name' ), ENT_QUOTES );
279 }
280
281 /**
282 * Get from email address.
283 *
284 * @return string
285 */
286 public function get_from_address() {
287 return sanitize_email( get_option( 'woocommerce_email_from_address' ) );
288 }
289
290 /**
291 * Get the email header.
292 *
293 * @param mixed $email_heading Heading for the email.
294 */
295 public function email_header( $email_heading ) {
296 wc_get_template( 'emails/email-header.php', array( 'email_heading' => $email_heading ) );
297 }
298
299 /**
300 * Get the email footer.
301 */
302 public function email_footer() {
303 wc_get_template( 'emails/email-footer.php' );
304 }
305
306 /**
307 * Replace placeholder text in strings.
308 *
309 * @since 3.7.0
310 * @param string $string Email footer text.
311 * @return string Email footer text with any replacements done.
312 */
313 public function replace_placeholders( $string ) {
314 $domain = wp_parse_url( home_url(), PHP_URL_HOST );
315
316 return str_replace(
317 array(
318 '{site_title}',
319 '{site_address}',
320 '{site_url}',
321 '{woocommerce}',
322 '{WooCommerce}',
323 '{store_address}',
324 '{store_email}',
325 ),
326 array(
327 $this->get_blogname(),
328 $domain,
329 $domain,
330 '<a href="https://woocommerce.com">WooCommerce</a>',
331 '<a href="https://woocommerce.com">WooCommerce</a>',
332 $this->get_store_address(),
333 $this->get_from_address(),
334 ),
335 $string
336 );
337 }
338
339 /**
340 * Filter callback to replace {site_title} in email footer
341 *
342 * @since 3.3.0
343 * @deprecated 3.7.0
344 * @param string $string Email footer text.
345 * @return string Email footer text with any replacements done.
346 */
347 public function email_footer_replace_site_title( $string ) {
348 wc_deprecated_function( 'WC_Emails::email_footer_replace_site_title', '3.7.0', 'WC_Emails::replace_placeholders' );
349 return $this->replace_placeholders( $string );
350 }
351
352 /**
353 * Wraps a message in the woocommerce mail template.
354 *
355 * @param string $email_heading Heading text.
356 * @param string $message Email message.
357 * @param bool $plain_text Set true to send as plain text. Default to false.
358 *
359 * @return string
360 */
361 public function wrap_message( $email_heading, $message, $plain_text = false ) {
362 // Buffer.
363 ob_start();
364
365 do_action( 'woocommerce_email_header', $email_heading, null );
366
367 echo wpautop( wptexturize( $message ) ); // WPCS: XSS ok.
368
369 do_action( 'woocommerce_email_footer', null );
370
371 // Get contents.
372 $message = ob_get_clean();
373
374 return $message;
375 }
376
377 /**
378 * Send the email.
379 *
380 * @param mixed $to Receiver.
381 * @param mixed $subject Email subject.
382 * @param mixed $message Message.
383 * @param string $headers Email headers (default: "Content-Type: text/html\r\n").
384 * @param string $attachments Attachments (default: "").
385 * @return bool
386 */
387 public function send( $to, $subject, $message, $headers = "Content-Type: text/html\r\n", $attachments = '' ) {
388 // Send.
389 $email = new WC_Email();
390 return $email->send( $to, $subject, $message, $headers, $attachments );
391 }
392
393 /**
394 * Prepare and send the customer invoice email on demand.
395 *
396 * @param int|WC_Order $order Order instance or ID.
397 */
398 public function customer_invoice( $order ) {
399 $email = $this->emails['WC_Email_Customer_Invoice'];
400
401 if ( ! is_object( $order ) ) {
402 $order = wc_get_order( absint( $order ) );
403 }
404
405 $email->trigger( $order->get_id(), $order );
406 }
407
408 /**
409 * Customer new account welcome email.
410 *
411 * @param int $customer_id Customer ID.
412 * @param array $new_customer_data New customer data.
413 * @param bool $password_generated If password is generated.
414 */
415 public function customer_new_account( $customer_id, $new_customer_data = array(), $password_generated = false ) {
416 if ( ! $customer_id ) {
417 return;
418 }
419
420 $user_pass = ! empty( $new_customer_data['user_pass'] ) ? $new_customer_data['user_pass'] : '';
421
422 $email = $this->emails['WC_Email_Customer_New_Account'];
423 $email->trigger( $customer_id, $user_pass, $password_generated );
424 }
425
426 /**
427 * Show the order details table
428 *
429 * @param WC_Order $order Order instance.
430 * @param bool $sent_to_admin If should sent to admin.
431 * @param bool $plain_text If is plain text email.
432 * @param string $email Email address.
433 */
434 public function order_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
435 if ( $plain_text ) {
436 wc_get_template(
437 'emails/plain/email-order-details.php',
438 array(
439 'order' => $order,
440 'sent_to_admin' => $sent_to_admin,
441 'plain_text' => $plain_text,
442 'email' => $email,
443 )
444 );
445 } else {
446 wc_get_template(
447 'emails/email-order-details.php',
448 array(
449 'order' => $order,
450 'sent_to_admin' => $sent_to_admin,
451 'plain_text' => $plain_text,
452 'email' => $email,
453 )
454 );
455 }
456 }
457
458 /**
459 * Show order downloads in a table.
460 *
461 * @since 3.2.0
462 * @param WC_Order $order Order instance.
463 * @param bool $sent_to_admin If should sent to admin.
464 * @param bool $plain_text If is plain text email.
465 * @param string $email Email address.
466 */
467 public function order_downloads( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
468 $show_downloads = $order->has_downloadable_item() && $order->is_download_permitted() && ! $sent_to_admin && ! is_a( $email, 'WC_Email_Customer_Refunded_Order' );
469
470 if ( ! $show_downloads ) {
471 return;
472 }
473
474 $downloads = $order->get_downloadable_items();
475 $columns = apply_filters(
476 'woocommerce_email_downloads_columns',
477 array(
478 'download-product' => __( 'Product', 'woocommerce' ),
479 'download-expires' => __( 'Expires', 'woocommerce' ),
480 'download-file' => __( 'Download', 'woocommerce' ),
481 )
482 );
483
484 if ( $plain_text ) {
485 wc_get_template(
486 'emails/plain/email-downloads.php',
487 array(
488 'order' => $order,
489 'sent_to_admin' => $sent_to_admin,
490 'plain_text' => $plain_text,
491 'email' => $email,
492 'downloads' => $downloads,
493 'columns' => $columns,
494 )
495 );
496 } else {
497 wc_get_template(
498 'emails/email-downloads.php',
499 array(
500 'order' => $order,
501 'sent_to_admin' => $sent_to_admin,
502 'plain_text' => $plain_text,
503 'email' => $email,
504 'downloads' => $downloads,
505 'columns' => $columns,
506 )
507 );
508 }
509 }
510
511 /**
512 * Add order meta to email templates.
513 *
514 * @param WC_Order $order Order instance.
515 * @param bool $sent_to_admin If should sent to admin.
516 * @param bool $plain_text If is plain text email.
517 */
518 public function order_meta( $order, $sent_to_admin = false, $plain_text = false ) {
519 $fields = apply_filters( 'woocommerce_email_order_meta_fields', array(), $sent_to_admin, $order );
520
521 /**
522 * Deprecated woocommerce_email_order_meta_keys filter.
523 *
524 * @since 2.3.0
525 */
526 $_fields = apply_filters( 'woocommerce_email_order_meta_keys', array(), $sent_to_admin );
527
528 if ( $_fields ) {
529 foreach ( $_fields as $key => $field ) {
530 if ( is_numeric( $key ) ) {
531 $key = $field;
532 }
533
534 $fields[ $key ] = array(
535 'label' => wptexturize( $key ),
536 'value' => wptexturize( $order->get_meta( $field ) ),
537 );
538 }
539 }
540
541 if ( $fields ) {
542
543 if ( $plain_text ) {
544
545 foreach ( $fields as $field ) {
546 if ( isset( $field['label'] ) && isset( $field['value'] ) && $field['value'] ) {
547 echo $field['label'] . ': ' . $field['value'] . "\n"; // WPCS: XSS ok.
548 }
549 }
550 } else {
551
552 foreach ( $fields as $field ) {
553 if ( isset( $field['label'] ) && isset( $field['value'] ) && $field['value'] ) {
554 echo '<p><strong>' . $field['label'] . ':</strong> ' . $field['value'] . '</p>'; // WPCS: XSS ok.
555 }
556 }
557 }
558 }
559 }
560
561 /**
562 * Is customer detail field valid?
563 *
564 * @param array $field Field data to check if is valid.
565 * @return boolean
566 */
567 public function customer_detail_field_is_valid( $field ) {
568 return isset( $field['label'] ) && ! empty( $field['value'] );
569 }
570
571 /**
572 * Allows developers to add additional customer details to templates.
573 *
574 * In versions prior to 3.2 this was used for notes, phone and email but this data has moved.
575 *
576 * @param WC_Order $order Order instance.
577 * @param bool $sent_to_admin If should sent to admin.
578 * @param bool $plain_text If is plain text email.
579 */
580 public function customer_details( $order, $sent_to_admin = false, $plain_text = false ) {
581 if ( ! is_a( $order, 'WC_Order' ) ) {
582 return;
583 }
584
585 $fields = array_filter( apply_filters( 'woocommerce_email_customer_details_fields', array(), $sent_to_admin, $order ), array( $this, 'customer_detail_field_is_valid' ) );
586
587 if ( ! empty( $fields ) ) {
588 if ( $plain_text ) {
589 wc_get_template( 'emails/plain/email-customer-details.php', array( 'fields' => $fields ) );
590 } else {
591 wc_get_template( 'emails/email-customer-details.php', array( 'fields' => $fields ) );
592 }
593 }
594 }
595
596 /**
597 * Get the email addresses.
598 *
599 * @param WC_Order $order Order instance.
600 * @param bool $sent_to_admin If should sent to admin.
601 * @param bool $plain_text If is plain text email.
602 */
603 public function email_addresses( $order, $sent_to_admin = false, $plain_text = false ) {
604 if ( ! is_a( $order, 'WC_Order' ) ) {
605 return;
606 }
607 if ( $plain_text ) {
608 wc_get_template(
609 'emails/plain/email-addresses.php',
610 array(
611 'order' => $order,
612 'sent_to_admin' => $sent_to_admin,
613 )
614 );
615 } else {
616 wc_get_template(
617 'emails/email-addresses.php',
618 array(
619 'order' => $order,
620 'sent_to_admin' => $sent_to_admin,
621 )
622 );
623 }
624 }
625
626 /**
627 * Renders any additional fields captured during block-based checkout.
628 *
629 * @param WC_Order $order Order instance.
630 * @param bool $sent_to_admin If email is sent to admin.
631 * @param bool $plain_text If this is a plain text email.
632 */
633 public function additional_checkout_fields( $order, $sent_to_admin = false, $plain_text = false ) {
634 if ( ! is_a( $order, 'WC_Order' ) ) {
635 return;
636 }
637
638 $checkout_fields = Package::container()->get( CheckoutFields::class );
639 $fields = array_merge(
640 $checkout_fields->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
641 $checkout_fields->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
642 );
643
644 if ( ! $fields ) {
645 return;
646 }
647
648 if ( $plain_text ) {
649 echo "\n" . esc_html( wc_strtoupper( __( 'Additional information', 'woocommerce' ) ) ) . "\n\n";
650 foreach ( $fields as $field ) {
651 printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
652 }
653 } else {
654 echo '<h2>' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2>';
655 echo '<ul class="additional-fields" style="margin-bottom: 40px;">';
656 foreach ( $fields as $field ) {
657 printf( '<li><strong>%s</strong>: %s</li>', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
658 }
659 echo '</ul>';
660 }
661 }
662
663 /**
664 * Renders any additional address fields captured during block-based checkout.
665 *
666 * @param string $address_type Address type.
667 * @param WC_Order $order Order instance.
668 * @param bool $sent_to_admin If email is sent to admin.
669 * @param bool $plain_text If this is a plain text email.
670 */
671 public function additional_address_fields( $address_type, $order, $sent_to_admin = false, $plain_text = false ) {
672 if ( ! is_a( $order, 'WC_Order' ) ) {
673 return;
674 }
675
676 $checkout_fields = Package::container()->get( CheckoutFields::class );
677 $fields = $checkout_fields->get_order_additional_fields_with_values( $order, 'address', $address_type, 'view' );
678
679 if ( ! $fields ) {
680 return;
681 }
682
683 foreach ( $fields as $field ) {
684 if ( $plain_text ) {
685 printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
686 } else {
687 printf( '<br><strong>%s</strong>: %s', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
688 }
689 }
690 }
691
692 /**
693 * Get blog name formatted for emails.
694 *
695 * @return string
696 */
697 private function get_blogname() {
698 return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
699 }
700
701 /**
702 * Get store address formatted for emails.
703 *
704 * @return string
705 */
706 public function get_store_address() {
707 add_filter(
708 'woocommerce_formatted_address_force_country_display',
709 array( $this, 'get_store_address_force_country_display' ),
710 5
711 );
712 $result = wp_specialchars_decode(
713 WC()->countries->get_formatted_address(
714 array(
715 'address_1' => WC()->countries->get_base_address(),
716 'address_2' => WC()->countries->get_base_address_2(),
717 'city' => WC()->countries->get_base_city(),
718 'state' => WC()->countries->get_base_state(),
719 'country' => WC()->countries->get_base_country(),
720 'postcode' => WC()->countries->get_base_postcode(),
721 )
722 )
723 );
724 // Replace newlines by commas.
725 $result = preg_replace( '/<br\/?>/i', ', ', $result );
726 remove_filter(
727 'woocommerce_formatted_address_force_country_display',
728 array( $this, 'get_store_address_force_country_display' )
729 );
730 return $result;
731 }
732
733 /**
734 * Force country display, used by WC_Emails::get_store address() method
735 *
736 * @return bool
737 */
738 public function get_store_address_force_country_display() {
739 return true;
740 }
741
742 /**
743 * Low stock notification email.
744 *
745 * @param WC_Product $product Product instance.
746 */
747 public function low_stock( $product ) {
748 if ( 'no' === get_option( 'woocommerce_notify_low_stock', 'yes' ) ) {
749 return;
750 }
751
752 /**
753 * Determine if the current product should trigger a low stock notification
754 *
755 * @param int $product_id - The low stock product id
756 *
757 * @since 4.7.0
758 */
759 if ( false === apply_filters( 'woocommerce_should_send_low_stock_notification', true, $product->get_id() ) ) {
760 return;
761 }
762
763 // If this is a variation but stock is managed at the parent level, use the parent product for the notification.
764 if ( $product->is_type( 'variation' ) && 'parent' === $product->get_manage_stock() ) {
765 $parent_product = wc_get_product( $product->get_parent_id() );
766 if ( $parent_product ) {
767 $product = $parent_product;
768 }
769 }
770
771 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product low in stock', 'woocommerce' ) );
772 $message = sprintf(
773 /* translators: 1: product name 2: items in stock */
774 __( '%1$s is low in stock. There are %2$d left.', 'woocommerce' ),
775 html_entity_decode( wp_strip_all_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
776 html_entity_decode( wp_strip_all_tags( $product->get_stock_quantity() ) )
777 );
778
779 wp_mail(
780 apply_filters( 'woocommerce_email_recipient_low_stock', get_option( 'woocommerce_stock_email_recipient' ), $product, null ),
781 apply_filters( 'woocommerce_email_subject_low_stock', $subject, $product, null ),
782 apply_filters( 'woocommerce_email_content_low_stock', $message, $product ),
783 apply_filters( 'woocommerce_email_headers', '', 'low_stock', $product, null ),
784 apply_filters( 'woocommerce_email_attachments', array(), 'low_stock', $product, null )
785 );
786 }
787
788 /**
789 * No stock notification email.
790 *
791 * @param WC_Product $product Product instance.
792 */
793 public function no_stock( $product ) {
794 if ( 'no' === get_option( 'woocommerce_notify_no_stock', 'yes' ) ) {
795 return;
796 }
797
798 /**
799 * Determine if the current product should trigger a no stock notification
800 *
801 * @param int $product_id - The out of stock product id
802 *
803 * @since 4.6.0
804 */
805 if ( false === apply_filters( 'woocommerce_should_send_no_stock_notification', true, $product->get_id() ) ) {
806 return;
807 }
808
809 // If this is a variation but stock is managed at the parent level, use the parent product for the notification.
810 if ( $product->is_type( ProductType::VARIATION ) && 'parent' === $product->get_manage_stock() ) {
811 $parent_product = wc_get_product( $product->get_parent_id() );
812 if ( $parent_product ) {
813 $product = $parent_product;
814 }
815 }
816
817 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product out of stock', 'woocommerce' ) );
818 /* translators: %s: product name */
819 $message = sprintf( __( '%s is out of stock.', 'woocommerce' ), html_entity_decode( wp_strip_all_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
820
821 wp_mail(
822 apply_filters( 'woocommerce_email_recipient_no_stock', get_option( 'woocommerce_stock_email_recipient' ), $product, null ),
823 apply_filters( 'woocommerce_email_subject_no_stock', $subject, $product, null ),
824 apply_filters( 'woocommerce_email_content_no_stock', $message, $product ),
825 apply_filters( 'woocommerce_email_headers', '', 'no_stock', $product, null ),
826 apply_filters( 'woocommerce_email_attachments', array(), 'no_stock', $product, null )
827 );
828 }
829
830 /**
831 * Backorder notification email.
832 *
833 * @param array $args Arguments.
834 */
835 public function backorder( $args ) {
836 $args = wp_parse_args(
837 $args,
838 array(
839 'product' => '',
840 'quantity' => '',
841 'order_id' => '',
842 )
843 );
844
845 $order = wc_get_order( $args['order_id'] );
846 if (
847 ! $args['product'] ||
848 ! is_object( $args['product'] ) ||
849 ! $args['quantity'] ||
850 ! $order
851 ) {
852 return;
853 }
854
855 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product backorder', 'woocommerce' ) );
856 /* translators: 1: product quantity 2: product name 3: order number */
857 $message = sprintf( __( '%1$s units of %2$s have been backordered in order #%3$s.', 'woocommerce' ), $args['quantity'], html_entity_decode( wp_strip_all_tags( $args['product']->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ), $order->get_order_number() );
858
859 wp_mail(
860 apply_filters( 'woocommerce_email_recipient_backorder', get_option( 'woocommerce_stock_email_recipient' ), $args, null ),
861 apply_filters( 'woocommerce_email_subject_backorder', $subject, $args, null ),
862 apply_filters( 'woocommerce_email_content_backorder', $message, $args ),
863 apply_filters( 'woocommerce_email_headers', '', 'backorder', $args, null ),
864 apply_filters( 'woocommerce_email_attachments', array(), 'backorder', $args, null )
865 );
866 }
867
868 /**
869 * Adds Schema.org markup for order in JSON-LD format.
870 *
871 * @deprecated 3.0.0
872 * @see WC_Structured_Data::generate_order_data()
873 *
874 * @since 2.6.0
875 * @param WC_Order $order Order instance.
876 * @param bool $sent_to_admin If should sent to admin.
877 * @param bool $plain_text If is plain text email.
878 */
879 public function order_schema_markup( $order, $sent_to_admin = false, $plain_text = false ) {
880 wc_deprecated_function( 'WC_Emails::order_schema_markup', '3.0', 'WC_Structured_Data::generate_order_data' );
881
882 WC()->structured_data->generate_order_data( $order, $sent_to_admin, $plain_text );
883 WC()->structured_data->output_structured_data();
884 }
885 }
886