PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.6.2
WooCommerce v10.6.2
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 2 months ago admin 2 months ago blocks 2 months ago cli 2 months ago customizer 2 months ago data-stores 2 months ago emails 2 months ago export 2 months ago gateways 2 months ago import 2 months ago integrations 2 months ago interfaces 2 months ago legacy 2 months ago libraries 2 months ago log-handlers 2 months ago payment-tokens 2 months ago product-usage 2 months ago queue 2 months ago react-admin 2 months ago rest-api 2 months ago shipping 2 months ago shortcodes 2 months ago theme-support 2 months ago tracks 2 months ago traits 2 months ago walkers 2 months ago wccom-site 2 months ago widgets 2 months ago class-wc-ajax.php 2 months ago class-wc-auth.php 2 months ago class-wc-autoloader.php 2 months ago class-wc-background-emailer.php 2 months ago class-wc-background-updater.php 2 months ago class-wc-brands-brand-settings-manager.php 2 months ago class-wc-brands-coupons.php 2 months ago class-wc-brands.php 2 months ago class-wc-breadcrumb.php 2 months ago class-wc-cache-helper.php 2 months ago class-wc-cart-fees.php 2 months ago class-wc-cart-session.php 2 months ago class-wc-cart-totals.php 2 months ago class-wc-cart.php 2 months ago class-wc-checkout.php 2 months ago class-wc-cli.php 2 months ago class-wc-comments.php 2 months ago class-wc-countries.php 2 months ago class-wc-coupon.php 2 months ago class-wc-customer-download-log.php 2 months ago class-wc-customer-download.php 2 months ago class-wc-customer.php 2 months ago class-wc-data-exception.php 2 months ago class-wc-data-store.php 2 months ago class-wc-datetime.php 2 months ago class-wc-deprecated-action-hooks.php 2 months ago class-wc-deprecated-filter-hooks.php 2 months ago class-wc-discounts.php 2 months ago class-wc-download-handler.php 2 months ago class-wc-emails.php 2 months ago class-wc-embed.php 2 months ago class-wc-form-handler.php 2 months ago class-wc-frontend-scripts.php 2 months ago class-wc-geo-ip.php 2 months ago class-wc-geolite-integration.php 2 months ago class-wc-geolocation.php 2 months ago class-wc-https.php 2 months ago class-wc-install.php 2 months ago class-wc-integrations.php 2 months ago class-wc-log-levels.php 2 months ago class-wc-logger.php 2 months ago class-wc-meta-data.php 2 months ago class-wc-order-factory.php 2 months ago class-wc-order-item-coupon.php 2 months ago class-wc-order-item-fee.php 2 months ago class-wc-order-item-meta.php 2 months ago class-wc-order-item-product.php 2 months ago class-wc-order-item-shipping.php 2 months ago class-wc-order-item-tax.php 2 months ago class-wc-order-item.php 2 months ago class-wc-order-query.php 2 months ago class-wc-order-refund.php 2 months ago class-wc-order.php 2 months ago class-wc-payment-gateways.php 2 months ago class-wc-payment-tokens.php 2 months ago class-wc-post-data.php 2 months ago class-wc-post-types.php 2 months ago class-wc-privacy-background-process.php 2 months ago class-wc-privacy-erasers.php 2 months ago class-wc-privacy-exporters.php 2 months ago class-wc-privacy.php 2 months ago class-wc-product-attribute.php 2 months ago class-wc-product-download.php 2 months ago class-wc-product-external.php 2 months ago class-wc-product-factory.php 2 months ago class-wc-product-grouped.php 2 months ago class-wc-product-query.php 2 months ago class-wc-product-simple.php 2 months ago class-wc-product-variable.php 2 months ago class-wc-product-variation.php 2 months ago class-wc-query.php 2 months ago class-wc-rate-limiter.php 2 months ago class-wc-regenerate-images-request.php 2 months ago class-wc-regenerate-images.php 2 months ago class-wc-register-wp-admin-settings.php 2 months ago class-wc-rest-authentication.php 2 months ago class-wc-rest-exception.php 2 months ago class-wc-session-handler.php 2 months ago class-wc-shipping-rate.php 2 months ago class-wc-shipping-zone.php 2 months ago class-wc-shipping-zones.php 2 months ago class-wc-shipping.php 2 months ago class-wc-shortcodes.php 2 months ago class-wc-structured-data.php 2 months ago class-wc-tax.php 2 months ago class-wc-template-loader.php 2 months ago class-wc-tracker.php 2 months ago class-wc-validation.php 2 months ago class-wc-webhook.php 2 months ago class-woocommerce.php 2 months ago wc-account-functions.php 2 months ago wc-attribute-functions.php 2 months ago wc-brands-functions.php 2 months ago wc-cart-functions.php 2 months ago wc-conditional-functions.php 2 months ago wc-core-functions.php 2 months ago wc-coupon-functions.php 2 months ago wc-deprecated-functions.php 2 months ago wc-formatting-functions.php 2 months ago wc-interactivity-api-functions.php 2 months ago wc-notice-functions.php 2 months ago wc-order-functions.php 2 months ago wc-order-item-functions.php 2 months ago wc-order-step-logger-functions.php 2 months ago wc-page-functions.php 2 months ago wc-product-functions.php 2 months ago wc-rest-functions.php 2 months ago wc-stock-functions.php 2 months ago wc-template-functions.php 2 months ago wc-template-hooks.php 2 months ago wc-term-functions.php 2 months ago wc-update-functions.php 2 months ago wc-user-functions.php 2 months ago wc-webhook-functions.php 2 months ago wc-widget-functions.php 2 months ago
class-wc-emails.php
1275 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 declare( strict_types = 1 );
12
13 use Automattic\Jetpack\Constants;
14 use Automattic\WooCommerce\Blocks\Package;
15 use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
16 use Automattic\WooCommerce\Enums\ProductType;
17 use Automattic\WooCommerce\Internal\Fulfillments\Fulfillment;
18 use Automattic\WooCommerce\Utilities\FeaturesUtil;
19
20 defined( 'ABSPATH' ) || exit;
21
22 /**
23 * Emails class.
24 */
25 class WC_Emails {
26
27 /**
28 * Array of email notification classes
29 *
30 * @var WC_Email[]
31 */
32 public $emails = array();
33
34 /**
35 * The single instance of the class
36 *
37 * @var WC_Emails
38 */
39 protected static $instance = null;
40
41 /**
42 * Background emailer class.
43 *
44 * @var WC_Background_Emailer
45 */
46 protected static $background_emailer = null;
47
48 /**
49 * Main WC_Emails Instance.
50 *
51 * Ensures only one instance of WC_Emails is loaded or can be loaded.
52 *
53 * @since 2.1
54 * @static
55 * @return WC_Emails Main instance
56 */
57 public static function instance() {
58 if ( is_null( self::$instance ) ) {
59 self::$instance = new self();
60 }
61 return self::$instance;
62 }
63
64 /**
65 * Cloning is forbidden.
66 *
67 * @since 2.1
68 * @return void
69 */
70 public function __clone() {
71 wc_doing_it_wrong( __FUNCTION__, __( 'Cloning is forbidden.', 'woocommerce' ), '2.1' );
72 }
73
74 /**
75 * Unserializing instances of this class is forbidden.
76 *
77 * @since 2.1
78 * @return void
79 */
80 public function __wakeup() {
81 wc_doing_it_wrong( __FUNCTION__, __( 'Unserializing instances of this class is forbidden.', 'woocommerce' ), '2.1' );
82 }
83
84 /**
85 * Hook in all transactional emails.
86 *
87 * @return void
88 */
89 public static function init_transactional_emails() {
90 /**
91 * Filter the actions that trigger transactional emails.
92 *
93 * @since 3.0.0
94 * @param array $email_actions Array of actions that trigger transactional emails.
95 */
96 $email_actions = apply_filters(
97 'woocommerce_email_actions',
98 array(
99 'woocommerce_low_stock',
100 'woocommerce_no_stock',
101 'woocommerce_product_on_backorder',
102 'woocommerce_order_status_pending_to_processing',
103 'woocommerce_order_status_pending_to_completed',
104 'woocommerce_order_status_processing_to_cancelled',
105 'woocommerce_order_status_pending_to_failed',
106 'woocommerce_order_status_pending_to_on-hold',
107 'woocommerce_order_status_failed_to_processing',
108 'woocommerce_order_status_failed_to_completed',
109 'woocommerce_order_status_failed_to_on-hold',
110 'woocommerce_order_status_cancelled_to_processing',
111 'woocommerce_order_status_cancelled_to_completed',
112 'woocommerce_order_status_cancelled_to_on-hold',
113 'woocommerce_order_status_on-hold_to_processing',
114 'woocommerce_order_status_on-hold_to_cancelled',
115 'woocommerce_order_status_on-hold_to_failed',
116 'woocommerce_order_status_completed',
117 'woocommerce_order_status_failed',
118 'woocommerce_order_fully_refunded',
119 'woocommerce_order_partially_refunded',
120 'woocommerce_new_customer_note',
121 'woocommerce_created_customer',
122 )
123 );
124
125 /**
126 * Filter whether to defer transactional emails.
127 *
128 * @since 3.0.0
129 * @param bool $defer Whether to defer transactional emails.
130 */
131 if ( apply_filters( 'woocommerce_defer_transactional_emails', false ) ) {
132 self::$background_emailer = new WC_Background_Emailer();
133
134 foreach ( $email_actions as $action ) {
135 add_action( $action, array( __CLASS__, 'queue_transactional_email' ), 10, 10 );
136 }
137 } else {
138 foreach ( $email_actions as $action ) {
139 add_action( $action, array( __CLASS__, 'send_transactional_email' ), 10, 10 );
140 }
141 }
142 }
143
144 /**
145 * Queues transactional email so it's not sent in current request if enabled,
146 * otherwise falls back to send now.
147 *
148 * @param mixed ...$args Optional arguments.
149 * @return void
150 */
151 public static function queue_transactional_email( ...$args ) {
152 if ( is_a( self::$background_emailer, 'WC_Background_Emailer' ) ) {
153 self::$background_emailer->push_to_queue(
154 array(
155 'filter' => current_filter(),
156 'args' => func_get_args(),
157 )
158 );
159 } else {
160 self::send_transactional_email( ...$args );
161 }
162 }
163
164 /**
165 * Init the mailer instance and call the notifications for the current filter.
166 *
167 * @internal
168 *
169 * @param string $filter Filter name.
170 * @param array $args Email args (default: []).
171 * @return void
172 */
173 public static function send_queued_transactional_email( $filter = '', $args = array() ) {
174 /**
175 * Filter whether to allow sending queued transactional emails.
176 *
177 * @since 3.0.0
178 * @param bool $allow Whether to allow sending queued transactional emails.
179 * @param string $filter Filter name.
180 * @param array $args Email args.
181 */
182 if ( apply_filters( 'woocommerce_allow_send_queued_transactional_email', true, $filter, $args ) ) {
183 self::instance(); // Init self so emails exist.
184
185 // Ensure gateways are loaded in case they need to insert data into the emails.
186 WC()->payment_gateways();
187 WC()->shipping();
188
189 // phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
190 /** This action is documented in includes/class-wc-emails.php in the send_transactional_email method. */
191 do_action_ref_array( $filter . '_notification', $args );
192 }
193 }
194
195 /**
196 * Init the mailer instance and call the notifications for the current filter.
197 *
198 * @internal
199 *
200 * @param array $args Email args (default: []).
201 * @return void
202 */
203 public static function send_transactional_email( $args = array() ) {
204 try {
205 $args = func_get_args();
206 self::instance(); // Init self so emails exist.
207
208 /**
209 * Action hook for email template classes to trigger the sending of an email.
210 *
211 * The name of the hook is based on the "parent" hook that is currently firing, that this is attached to.
212 * See the WC_Emails::init_transactional_emails method for a list of hooks.
213 *
214 * @since 3.1.0
215 *
216 * @param array $args Args from the parent hook, which may differ depending on the hook.
217 */
218 do_action_ref_array( current_filter() . '_notification', $args );
219 } catch ( Exception $e ) {
220 $error = 'Transactional email triggered fatal error for callback ' . current_filter();
221 $logger = wc_get_logger();
222 $logger->critical(
223 $error . PHP_EOL,
224 array(
225 'source' => 'transactional-emails',
226 )
227 );
228 if ( Constants::is_true( 'WP_DEBUG' ) ) {
229 trigger_error( esc_html( $error ), E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
230 }
231 }
232 }
233
234 /**
235 * Constructor for the email class hooks in all emails that can be sent.
236 */
237 public function __construct() {
238 $this->init();
239
240 // Email Header, Footer and content hooks.
241 add_action( 'woocommerce_email_header', array( $this, 'email_header' ) );
242 add_action( 'woocommerce_email_footer', array( $this, 'email_footer' ) );
243 add_action( 'woocommerce_email_order_details', array( $this, 'order_downloads' ), 10, 4 );
244 add_action( 'woocommerce_email_order_details', array( $this, 'order_details' ), 10, 4 );
245 add_action( 'woocommerce_email_order_meta', array( $this, 'order_meta' ), 10, 3 );
246 add_action( 'woocommerce_email_customer_details', array( $this, 'customer_details' ), 10, 3 );
247 add_action( 'woocommerce_email_customer_details', array( $this, 'email_addresses' ), 20, 3 );
248 add_action( 'woocommerce_email_customer_details', array( $this, 'additional_checkout_fields' ), 30, 3 );
249 add_action( 'woocommerce_email_customer_address_section', array( $this, 'additional_address_fields' ), 30, 4 );
250
251 if ( FeaturesUtil::feature_is_enabled( 'fulfillments' ) ) {
252 // Fulfillment details and meta.
253 add_action( 'woocommerce_email_fulfillment_details', array( $this, 'fulfillment_details' ), 10, 5 );
254 add_action( 'woocommerce_email_fulfillment_meta', array( $this, 'fulfillment_meta' ), 30, 4 );
255 }
256
257 // Hooks for sending emails during store events.
258 add_action( 'woocommerce_low_stock_notification', array( $this, 'low_stock' ) );
259 add_action( 'woocommerce_no_stock_notification', array( $this, 'no_stock' ) );
260 add_action( 'woocommerce_product_on_backorder_notification', array( $this, 'backorder' ) );
261 add_action( 'woocommerce_created_customer_notification', array( $this, 'customer_new_account' ), 10, 3 );
262
263 // Hook for replacing {site_title} in email-footer.
264 add_filter( 'woocommerce_email_footer_text', array( $this, 'replace_placeholders' ) );
265
266 /**
267 * Action hook for email classes to hook into.
268 *
269 * @since 3.0.0
270 * @param WC_Emails $this The WC_Emails instance.
271 */
272 do_action( 'woocommerce_email', $this );
273 }
274
275 /**
276 * Init email classes.
277 *
278 * @return void
279 */
280 public function init() {
281 // Include email classes.
282 include_once __DIR__ . '/emails/class-wc-email.php';
283
284 $emails = array(
285 'WC_Email_New_Order' => __DIR__ . '/emails/class-wc-email-new-order.php',
286 'WC_Email_Cancelled_Order' => __DIR__ . '/emails/class-wc-email-cancelled-order.php',
287 'WC_Email_Customer_Cancelled_Order' => __DIR__ . '/emails/class-wc-email-customer-cancelled-order.php',
288 'WC_Email_Failed_Order' => __DIR__ . '/emails/class-wc-email-failed-order.php',
289 'WC_Email_Customer_Failed_Order' => __DIR__ . '/emails/class-wc-email-customer-failed-order.php',
290 'WC_Email_Customer_On_Hold_Order' => __DIR__ . '/emails/class-wc-email-customer-on-hold-order.php',
291 'WC_Email_Customer_Processing_Order' => __DIR__ . '/emails/class-wc-email-customer-processing-order.php',
292 'WC_Email_Customer_Completed_Order' => __DIR__ . '/emails/class-wc-email-customer-completed-order.php',
293 'WC_Email_Customer_Refunded_Order' => __DIR__ . '/emails/class-wc-email-customer-refunded-order.php',
294 'WC_Email_Customer_Invoice' => __DIR__ . '/emails/class-wc-email-customer-invoice.php',
295 'WC_Email_Customer_Note' => __DIR__ . '/emails/class-wc-email-customer-note.php',
296 'WC_Email_Customer_Reset_Password' => __DIR__ . '/emails/class-wc-email-customer-reset-password.php',
297 'WC_Email_Customer_New_Account' => __DIR__ . '/emails/class-wc-email-customer-new-account.php',
298 );
299 if ( FeaturesUtil::feature_is_enabled( 'point_of_sale' ) ) {
300 $emails['WC_Email_Customer_POS_Completed_Order'] = __DIR__ . '/emails/class-wc-email-customer-pos-completed-order.php';
301 $emails['WC_Email_Customer_POS_Refunded_Order'] = __DIR__ . '/emails/class-wc-email-customer-pos-refunded-order.php';
302 }
303 if ( FeaturesUtil::feature_is_enabled( 'fulfillments' ) ) {
304 $emails['WC_Email_Customer_Fulfillment_Created'] = __DIR__ . '/emails/class-wc-email-customer-fulfillment-created.php';
305 $emails['WC_Email_Customer_Fulfillment_Updated'] = __DIR__ . '/emails/class-wc-email-customer-fulfillment-updated.php';
306 $emails['WC_Email_Customer_Fulfillment_Deleted'] = __DIR__ . '/emails/class-wc-email-customer-fulfillment-deleted.php';
307 }
308
309 // Preload the options which will be used when emails are getting initialized in the loop below (reduces the number of SQL-queries).
310 wp_prime_option_caches(
311 array_map(
312 fn( string $class_name ) => sprintf( 'woocommerce_%s_settings', strtolower( str_replace( 'WC_Email_', '', $class_name ) ) ),
313 array_keys( $emails )
314 )
315 );
316 foreach ( $emails as $class => $path ) {
317 $this->emails[ $class ] = include $path;
318 }
319
320 // Enable custom partially refunded order email for the block email editor.
321 if ( FeaturesUtil::feature_is_enabled( 'block_email_editor' ) ) {
322 $this->emails['WC_Email_Customer_Partially_Refunded_Order'] = include __DIR__ . '/emails/class-wc-email-customer-partially-refunded-order.php';
323 }
324
325 /**
326 * Filter the email classes.
327 *
328 * @since 3.0.0
329 * @param array $emails Email classes.
330 */
331 $this->emails = apply_filters( 'woocommerce_email_classes', $this->emails );
332 }
333
334 /**
335 * Return the email classes - used in admin to load settings.
336 *
337 * @return array<string, WC_Email> Email classes.
338 */
339 public function get_emails() {
340 return $this->emails;
341 }
342
343 /**
344 * Get from name for email.
345 *
346 * @return string
347 */
348 public function get_from_name() {
349 $default = get_bloginfo( 'name', 'display' );
350 return wp_specialchars_decode( get_option( 'woocommerce_email_from_name', $default ), ENT_QUOTES );
351 }
352
353 /**
354 * Get from email address.
355 *
356 * @return string
357 */
358 public function get_from_address() {
359 return sanitize_email( get_option( 'woocommerce_email_from_address' ) );
360 }
361
362 /**
363 * Get the email header.
364 *
365 * @param mixed $email_heading Heading for the email.
366 * @return void
367 */
368 public function email_header( $email_heading ) {
369 wc_get_template(
370 'emails/email-header.php',
371 array(
372 'email_heading' => $email_heading,
373 'store_name' => get_bloginfo( 'name', 'display' ),
374 )
375 );
376 }
377
378 /**
379 * Get the email footer.
380 *
381 * @return void
382 */
383 public function email_footer() {
384 wc_get_template( 'emails/email-footer.php' );
385 }
386
387 /**
388 * Replace placeholder text in strings.
389 *
390 * @since 3.7.0
391 * @param string $text Email footer text.
392 * @return string Email footer text with any replacements done.
393 */
394 public function replace_placeholders( $text ) {
395 $domain = wp_parse_url( home_url(), PHP_URL_HOST );
396
397 return str_replace(
398 array(
399 '{site_title}',
400 '{site_address}',
401 '{site_url}',
402 '{woocommerce}',
403 '{WooCommerce}',
404 '{store_address}',
405 '{store_email}',
406 ),
407 array(
408 $this->get_blogname(),
409 $domain,
410 $domain,
411 '<a href="https://woocommerce.com">WooCommerce</a>',
412 '<a href="https://woocommerce.com">WooCommerce</a>',
413 $this->get_store_address(),
414 $this->get_from_address(),
415 ),
416 $text
417 );
418 }
419
420 /**
421 * Filter callback to replace {site_title} in email footer
422 *
423 * @since 3.3.0
424 * @deprecated 3.7.0
425 * @param string $text Email footer text.
426 * @return string Email footer text with any replacements done.
427 */
428 public function email_footer_replace_site_title( $text ) {
429 wc_deprecated_function( 'WC_Emails::email_footer_replace_site_title', '3.7.0', 'WC_Emails::replace_placeholders' );
430 return $this->replace_placeholders( $text );
431 }
432
433 /**
434 * Wraps a message in the woocommerce mail template.
435 *
436 * @param string $email_heading Heading text.
437 * @param string $message Email message.
438 * @param bool $deprecated Deprecated.
439 *
440 * @return string
441 */
442 public function wrap_message( $email_heading, $message, $deprecated = false ) {
443 if ( $deprecated ) {
444 wc_deprecated_argument( 'WC_Emails::wrap_message', '9.9.0' );
445 }
446
447 ob_start();
448
449 /**
450 * Action hook for email header.
451 *
452 * @since 3.0.0
453 * @param string $email_heading Heading text.
454 * @param null $null Unused.
455 */
456 do_action( 'woocommerce_email_header', $email_heading, null );
457
458 echo wp_kses_post( wpautop( wptexturize( $message ) ) );
459
460 /**
461 * Action hook for email footer.
462 *
463 * @since 3.0.0
464 * @param null $null Unused.
465 */
466 do_action( 'woocommerce_email_footer', null );
467
468 return ob_get_clean();
469 }
470
471 /**
472 * Send the email.
473 *
474 * @param mixed $to Receiver.
475 * @param mixed $subject Email subject.
476 * @param mixed $message Message.
477 * @param string $headers Email headers (default: "Content-Type: text/html\r\n").
478 * @param string $attachments Attachments (default: "").
479 * @return bool
480 */
481 public function send( $to, $subject, $message, $headers = "Content-Type: text/html\r\n", $attachments = '' ) {
482 $email = new WC_Email();
483 return $email->send( $to, $subject, $message, $headers, $attachments );
484 }
485
486 /**
487 * Prepare and send the customer invoice email on demand.
488 *
489 * @param int|WC_Order $order Order instance or ID.
490 * @return void
491 */
492 public function customer_invoice( $order ) {
493 $email = $this->emails['WC_Email_Customer_Invoice'];
494
495 if ( ! is_object( $order ) ) {
496 $order = wc_get_order( absint( $order ) );
497 }
498
499 $email->trigger( $order->get_id(), $order );
500 }
501
502 /**
503 * Customer new account welcome email.
504 *
505 * @param int $customer_id Customer ID.
506 * @param array $new_customer_data New customer data.
507 * @param bool $password_generated If password is generated.
508 * @return void
509 */
510 public function customer_new_account( $customer_id, $new_customer_data = array(), $password_generated = false ) {
511 if ( ! $customer_id ) {
512 return;
513 }
514 $email = $this->emails['WC_Email_Customer_New_Account'];
515 $email->trigger( $customer_id, $new_customer_data['user_pass'] ?? '', $password_generated );
516 }
517
518 /**
519 * Show the order details table
520 *
521 * @param WC_Order $order Order instance.
522 * @param bool $sent_to_admin If should sent to admin.
523 * @param bool $plain_text If is plain text email.
524 * @param string $email Email address.
525 * @return void
526 */
527 public function order_details( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
528 if ( $plain_text ) {
529 wc_get_template(
530 'emails/plain/email-order-details.php',
531 array(
532 'order' => $order,
533 'sent_to_admin' => $sent_to_admin,
534 'plain_text' => $plain_text,
535 'email' => $email,
536 )
537 );
538 } else {
539 wc_get_template(
540 'emails/email-order-details.php',
541 array(
542 'order' => $order,
543 'sent_to_admin' => $sent_to_admin,
544 'plain_text' => $plain_text,
545 'email' => $email,
546 )
547 );
548 }
549 }
550
551 /**
552 * Show order downloads in a table.
553 *
554 * @since 3.2.0
555 * @param WC_Order $order Order instance.
556 * @param bool $sent_to_admin If should sent to admin.
557 * @param bool $plain_text If is plain text email.
558 * @param string $email Email address.
559 * @return void
560 */
561 public function order_downloads( $order, $sent_to_admin = false, $plain_text = false, $email = '' ) {
562 $show_downloads = $order->has_downloadable_item() && $order->is_download_permitted() && ! $sent_to_admin && ! is_a( $email, 'WC_Email_Customer_Refunded_Order' );
563
564 if ( ! $show_downloads ) {
565 return;
566 }
567
568 $downloads = $order->get_downloadable_items();
569
570 /**
571 * Filter the columns of the order downloads table.
572 *
573 * @since 3.2.0
574 * @since 10.0.0 Added $order parameter.
575 * @param array $columns Array of columns.
576 * @param WC_Order $order Order object.
577 */
578 $columns = apply_filters(
579 'woocommerce_email_downloads_columns',
580 array(
581 'download-product' => __( 'Product', 'woocommerce' ),
582 'download-expires' => __( 'Expires', 'woocommerce' ),
583 'download-file' => __( 'Download', 'woocommerce' ),
584 ),
585 $order
586 );
587
588 if ( $plain_text ) {
589 wc_get_template(
590 'emails/plain/email-downloads.php',
591 array(
592 'order' => $order,
593 'sent_to_admin' => $sent_to_admin,
594 'plain_text' => $plain_text,
595 'email' => $email,
596 'downloads' => $downloads,
597 'columns' => $columns,
598 )
599 );
600 } else {
601 wc_get_template(
602 'emails/email-downloads.php',
603 array(
604 'order' => $order,
605 'sent_to_admin' => $sent_to_admin,
606 'plain_text' => $plain_text,
607 'email' => $email,
608 'downloads' => $downloads,
609 'columns' => $columns,
610 )
611 );
612 }
613 }
614
615 /**
616 * Add order meta to email templates.
617 *
618 * @param WC_Order $order Order instance.
619 * @param bool $sent_to_admin If should sent to admin.
620 * @param bool $plain_text If is plain text email.
621 * @return void
622 */
623 public function order_meta( $order, $sent_to_admin = false, $plain_text = false ) {
624 /**
625 * Filter the order meta fields.
626 *
627 * @since 3.0.0
628 * @param array $fields Array of meta fields.
629 * @param bool $sent_to_admin If sent to admin.
630 * @param WC_Order $order Order instance.
631 */
632 $fields = apply_filters( 'woocommerce_email_order_meta_fields', array(), $sent_to_admin, $order );
633
634 /**
635 * Deprecated woocommerce_email_order_meta_keys filter.
636 *
637 * @since 2.3.0
638 * @param array $fields Array of meta fields.
639 * @param bool $sent_to_admin If sent to admin.
640 */
641 $_fields = apply_filters( 'woocommerce_email_order_meta_keys', array(), $sent_to_admin );
642
643 if ( $_fields ) {
644 foreach ( $_fields as $key => $field ) {
645 if ( is_numeric( $key ) ) {
646 $key = $field;
647 }
648
649 $fields[ $key ] = array(
650 'label' => wptexturize( $key ),
651 'value' => wptexturize( $order->get_meta( $field ) ),
652 );
653 }
654 }
655
656 if ( $fields ) {
657
658 if ( $plain_text ) {
659
660 foreach ( $fields as $field ) {
661 if ( isset( $field['label'], $field['value'] ) && $field['value'] ) {
662 echo wp_kses_post( $field['label'] . ': ' . $field['value'] ) . "\n"; // WPCS: XSS ok.
663 }
664 }
665 } else {
666
667 foreach ( $fields as $field ) {
668 if ( isset( $field['label'], $field['value'] ) && $field['value'] ) {
669 echo '<p><strong>' . wp_kses_post( $field['label'] ) . ':</strong> ' . wp_kses_post( $field['value'] ) . '</p>'; // WPCS: XSS ok.
670 }
671 }
672 }
673 }
674 }
675
676 /**
677 * Show the fulfillment details
678 *
679 * @param WC_Order $order Order instance.
680 * @param Fulfillment $fulfillment Fulfillment instance.
681 * @param bool $sent_to_admin If should sent to admin.
682 * @param bool $plain_text If is plain text email.
683 * @param string $email Email address.
684 * @return void
685 */
686 public function fulfillment_details( $order, $fulfillment, $sent_to_admin = false, $plain_text = false, $email = '' ) {
687 if ( $plain_text ) {
688 wc_get_template(
689 'emails/plain/email-fulfillment-details.php',
690 array(
691 'order' => $order,
692 'fulfillment' => $fulfillment,
693 'sent_to_admin' => $sent_to_admin,
694 'plain_text' => $plain_text,
695 'email' => $email,
696 )
697 );
698 } else {
699 wc_get_template(
700 'emails/email-fulfillment-details.php',
701 array(
702 'order' => $order,
703 'fulfillment' => $fulfillment,
704 'sent_to_admin' => $sent_to_admin,
705 'plain_text' => $plain_text,
706 'email' => $email,
707 )
708 );
709 }
710 }
711
712 /**
713 * Add fulfillment meta to email templates.
714 *
715 * @param WC_Order $order Order instance.
716 * @param Fulfillment $fulfillment Fulfillment instance.
717 * @param bool $sent_to_admin If should sent to admin.
718 * @param bool $plain_text If is plain text email.
719 * @return void
720 */
721 public function fulfillment_meta( $order, $fulfillment, $sent_to_admin = false, $plain_text = false ) {
722 $fields = $fulfillment->get_meta_data();
723 $public_fields = array_filter(
724 $fields,
725 function ( $field ) {
726 return ! str_starts_with( $field->key, '_' );
727 }
728 );
729
730 if ( 0 < count( $public_fields ) ) {
731
732 foreach ( $public_fields as $field ) {
733 if ( isset( $field->key ) && isset( $field->value ) && $field->value ) {
734 /**
735 * Allows developers to translate the fulfillment meta key for display in emails.
736 *
737 * @since 10.1.0
738 */
739 $meta_key_translation = apply_filters( 'woocommerce_fulfillment_translate_meta_key', $field->key );
740 if ( $plain_text ) {
741 echo esc_attr( $meta_key_translation ) . ': ' . esc_attr( $field->value ) . PHP_EOL;
742 } else {
743 echo '<p><strong>' . esc_attr( $meta_key_translation ) . ':</strong> ' . esc_attr( $field->value ) . '</p>';
744 }
745 }
746 }
747 }
748 }
749
750 /**
751 * Is customer detail field valid?
752 *
753 * @param array $field Field data to check if is valid.
754 * @return boolean
755 */
756 public function customer_detail_field_is_valid( $field ) {
757 return isset( $field['label'] ) && ! empty( $field['value'] );
758 }
759
760 /**
761 * Allows developers to add additional customer details to templates.
762 *
763 * In versions prior to 3.2 this was used for notes, phone and email but this data has moved.
764 *
765 * @param WC_Order $order Order instance.
766 * @param bool $sent_to_admin If should sent to admin.
767 * @param bool $plain_text If is plain text email.
768 * @return void
769 */
770 public function customer_details( $order, $sent_to_admin = false, $plain_text = false ) {
771 if ( ! is_a( $order, 'WC_Order' ) ) {
772 return;
773 }
774
775 /**
776 * Filter the customer details fields.
777 *
778 * @since 3.2.0
779 * @param array $fields Array of customer details fields.
780 * @param bool $sent_to_admin If sent to admin.
781 * @param WC_Order $order Order instance.
782 */
783 $fields = array_filter( apply_filters( 'woocommerce_email_customer_details_fields', array(), $sent_to_admin, $order ), array( $this, 'customer_detail_field_is_valid' ) );
784
785 if ( ! empty( $fields ) ) {
786 if ( $plain_text ) {
787 wc_get_template( 'emails/plain/email-customer-details.php', array( 'fields' => $fields ) );
788 } else {
789 wc_get_template( 'emails/email-customer-details.php', array( 'fields' => $fields ) );
790 }
791 }
792 }
793
794 /**
795 * Get the email addresses.
796 *
797 * @param WC_Order $order Order instance.
798 * @param bool $sent_to_admin If should sent to admin.
799 * @param bool $plain_text If is plain text email.
800 * @return void
801 */
802 public function email_addresses( $order, $sent_to_admin = false, $plain_text = false ) {
803 if ( ! is_a( $order, 'WC_Order' ) ) {
804 return;
805 }
806 if ( $plain_text ) {
807 wc_get_template(
808 'emails/plain/email-addresses.php',
809 array(
810 'order' => $order,
811 'sent_to_admin' => $sent_to_admin,
812 )
813 );
814 } else {
815 wc_get_template(
816 'emails/email-addresses.php',
817 array(
818 'order' => $order,
819 'sent_to_admin' => $sent_to_admin,
820 )
821 );
822 }
823 }
824
825 /**
826 * Renders any additional fields captured during block-based checkout.
827 *
828 * @param WC_Order $order Order instance.
829 * @param bool $sent_to_admin If email is sent to admin.
830 * @param bool $plain_text If this is a plain text email.
831 * @return void
832 */
833 public function additional_checkout_fields( $order, $sent_to_admin = false, $plain_text = false ) {
834 if ( ! is_a( $order, 'WC_Order' ) ) {
835 return;
836 }
837
838 /**
839 * Service class managing checkout fields and its related extensibility points.
840 *
841 * @var CheckoutFields $checkout_fields
842 */
843 $checkout_fields = Package::container()->get( CheckoutFields::class );
844 $fields = array_merge(
845 $checkout_fields->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
846 $checkout_fields->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
847 );
848
849 $context = array(
850 'caller' => 'WC_Email::additional_checkout_fields',
851 'order' => $order,
852 'sent_to_admin' => $sent_to_admin,
853 'plain_text' => $plain_text,
854 );
855
856 $fields = $checkout_fields->filter_fields_for_order_confirmation( $fields, $context );
857
858 if ( ! $fields ) {
859 return;
860 }
861
862 if ( $plain_text ) {
863 echo "\n" . esc_html( wc_strtoupper( __( 'Additional information', 'woocommerce' ) ) ) . "\n\n";
864 foreach ( $fields as $field ) {
865 printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
866 }
867 } else {
868 echo '<h2>' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2>';
869 echo '<ul class="additional-fields" style="margin-bottom: 40px;">';
870 foreach ( $fields as $field ) {
871 printf( '<li><strong>%s</strong>: %s</li>', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
872 }
873 echo '</ul>';
874 }
875 }
876
877 /**
878 * Renders any additional address fields captured during block-based checkout.
879 *
880 * @param string $address_type Address type.
881 * @param WC_Order $order Order instance.
882 * @param bool $sent_to_admin If email is sent to admin.
883 * @param bool $plain_text If this is a plain text email.
884 * @return void
885 */
886 public function additional_address_fields( $address_type, $order, $sent_to_admin = false, $plain_text = false ) {
887 if ( ! is_a( $order, 'WC_Order' ) ) {
888 return;
889 }
890
891 /**
892 * Service class managing checkout fields and its related extensibility points.
893 *
894 * @var CheckoutFields $checkout_fields
895 */
896 $checkout_fields = Package::container()->get( CheckoutFields::class );
897 $fields = $checkout_fields->get_order_additional_fields_with_values( $order, 'address', $address_type, 'view' );
898
899 $context = array(
900 'caller' => 'WC_Email::additional_address_fields',
901 'address_type' => $address_type,
902 'order' => $order,
903 'sent_to_admin' => $sent_to_admin,
904 'plain_text' => $plain_text,
905 );
906
907 $fields = $checkout_fields->filter_fields_for_order_confirmation( $fields, $context );
908
909 if ( ! $fields ) {
910 return;
911 }
912
913 foreach ( $fields as $field ) {
914 if ( $plain_text ) {
915 printf( "%s: %s\n", wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
916 } else {
917 printf( '<br><strong>%s</strong>: %s', wp_kses_post( $field['label'] ), wp_kses_post( $field['value'] ) );
918 }
919 }
920 }
921
922 /**
923 * Get blog name formatted for emails.
924 *
925 * @return string
926 */
927 private function get_blogname() {
928 return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
929 }
930
931 /**
932 * Get store address formatted for emails.
933 *
934 * @return string
935 */
936 public function get_store_address() {
937 add_filter(
938 'woocommerce_formatted_address_force_country_display',
939 array( $this, 'get_store_address_force_country_display' ),
940 5
941 );
942 $result = wp_specialchars_decode(
943 WC()->countries->get_formatted_address(
944 array(
945 'address_1' => WC()->countries->get_base_address(),
946 'address_2' => WC()->countries->get_base_address_2(),
947 'city' => WC()->countries->get_base_city(),
948 'state' => WC()->countries->get_base_state(),
949 'country' => WC()->countries->get_base_country(),
950 'postcode' => WC()->countries->get_base_postcode(),
951 )
952 )
953 );
954 // Replace newlines by commas.
955 $result = preg_replace( '/<br\/?>/i', ', ', $result );
956 remove_filter(
957 'woocommerce_formatted_address_force_country_display',
958 array( $this, 'get_store_address_force_country_display' )
959 );
960 return $result;
961 }
962
963 /**
964 * Force country display, used by WC_Emails::get_store address() method
965 *
966 * @return bool
967 */
968 public function get_store_address_force_country_display() {
969 return true;
970 }
971
972 /**
973 * Add email sender filters.
974 *
975 * @return void
976 */
977 private function add_email_sender_filters() {
978 add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
979 add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
980 }
981
982 /**
983 * Remove email sender filters.
984 *
985 * @return void
986 */
987 private function remove_email_sender_filters() {
988 remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
989 remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
990 }
991
992 /**
993 * Low stock notification email.
994 *
995 * @param WC_Product $product Product instance.
996 * @return void
997 */
998 public function low_stock( $product ) {
999 if ( 'no' === get_option( 'woocommerce_notify_low_stock', 'yes' ) ) {
1000 return;
1001 }
1002
1003 /**
1004 * Determine if the current product should trigger a low stock notification
1005 *
1006 * @param int $product_id - The low stock product id
1007 *
1008 * @since 4.7.0
1009 */
1010 if ( false === apply_filters( 'woocommerce_should_send_low_stock_notification', true, $product->get_id() ) ) {
1011 return;
1012 }
1013
1014 // If this is a variation but stock is managed at the parent level, use the parent product for the notification.
1015 if ( $product->is_type( 'variation' ) && 'parent' === $product->get_manage_stock() ) {
1016 $parent_product = wc_get_product( $product->get_parent_id() );
1017 if ( $parent_product ) {
1018 $product = $parent_product;
1019 }
1020 }
1021
1022 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product low in stock', 'woocommerce' ) );
1023 $message = sprintf(
1024 /* translators: 1: product name 2: items in stock */
1025 __( '%1$s is low in stock. There are %2$d left.', 'woocommerce' ),
1026 html_entity_decode( wp_strip_all_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ),
1027 html_entity_decode( wp_strip_all_tags( $product->get_stock_quantity() ) )
1028 );
1029
1030 $this->add_email_sender_filters();
1031
1032 wp_mail(
1033 /**
1034 * Filter the recipient of the low stock notification email.
1035 *
1036 * @since 3.0.0
1037 * @param string $recipient The recipient email address.
1038 * @param WC_Product $product Product instance.
1039 * @param null $null Unused.
1040 */
1041 apply_filters( 'woocommerce_email_recipient_low_stock', get_option( 'woocommerce_stock_email_recipient' ), $product, null ),
1042 /**
1043 * Filter the subject of the low stock notification email.
1044 *
1045 * @since 3.0.0
1046 * @param string $subject The email subject.
1047 * @param WC_Product $product Product instance.
1048 * @param null $null Unused.
1049 */
1050 apply_filters( 'woocommerce_email_subject_low_stock', $subject, $product, null ),
1051 /**
1052 * Filter the content of the low stock notification email.
1053 *
1054 * @since 3.0.0
1055 * @param string $message The email content.
1056 * @param WC_Product $product Product instance.
1057 * @param null $null Unused.
1058 */
1059 apply_filters( 'woocommerce_email_content_low_stock', $message, $product ),
1060 /**
1061 * Filter the headers of the low stock notification email.
1062 *
1063 * @since 3.0.0
1064 * @param string $headers The email headers.
1065 * @param WC_Product $product Product instance.
1066 * @param null $null Unused.
1067 */
1068 apply_filters( 'woocommerce_email_headers', '', 'low_stock', $product, null ),
1069 /**
1070 * Filter the attachments of the low stock notification email.
1071 *
1072 * @since 3.0.0
1073 * @param array $attachments The email attachments.
1074 * @param WC_Product $product Product instance.
1075 * @param null $null Unused.
1076 */
1077 apply_filters( 'woocommerce_email_attachments', array(), 'low_stock', $product, null )
1078 );
1079
1080 $this->remove_email_sender_filters();
1081 }
1082
1083 /**
1084 * No stock notification email.
1085 *
1086 * @param WC_Product $product Product instance.
1087 * @return void
1088 */
1089 public function no_stock( $product ) {
1090 if ( 'no' === get_option( 'woocommerce_notify_no_stock', 'yes' ) ) {
1091 return;
1092 }
1093
1094 /**
1095 * Determine if the current product should trigger a no stock notification
1096 *
1097 * @param int $product_id - The out of stock product id
1098 *
1099 * @since 4.6.0
1100 */
1101 if ( false === apply_filters( 'woocommerce_should_send_no_stock_notification', true, $product->get_id() ) ) {
1102 return;
1103 }
1104
1105 // If this is a variation but stock is managed at the parent level, use the parent product for the notification.
1106 if ( $product->is_type( ProductType::VARIATION ) && 'parent' === $product->get_manage_stock() ) {
1107 $parent_product = wc_get_product( $product->get_parent_id() );
1108 if ( $parent_product ) {
1109 $product = $parent_product;
1110 }
1111 }
1112
1113 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product out of stock', 'woocommerce' ) );
1114 /* translators: %s: product name */
1115 $message = sprintf( __( '%s is out of stock.', 'woocommerce' ), html_entity_decode( wp_strip_all_tags( $product->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ) );
1116
1117 $this->add_email_sender_filters();
1118
1119 wp_mail(
1120 /**
1121 * Filter the recipient of the no stock notification email.
1122 *
1123 * @since 3.0.0
1124 * @param string $recipient The recipient email address.
1125 * @param WC_Product $product Product instance.
1126 * @param null $null Unused.
1127 */
1128 apply_filters( 'woocommerce_email_recipient_no_stock', get_option( 'woocommerce_stock_email_recipient' ), $product, null ),
1129 /**
1130 * Filter the subject of the no stock notification email.
1131 *
1132 * @since 3.0.0
1133 * @param string $subject The email subject.
1134 * @param WC_Product $product Product instance.
1135 * @param null $null Unused.
1136 */
1137 apply_filters( 'woocommerce_email_subject_no_stock', $subject, $product, null ),
1138 /**
1139 * Filter the content of the no stock notification email.
1140 *
1141 * @since 3.0.0
1142 * @param string $message The email content.
1143 * @param WC_Product $product Product instance.
1144 * @param null $null Unused.
1145 */
1146 apply_filters( 'woocommerce_email_content_no_stock', $message, $product ),
1147 /**
1148 * Filter the headers of the no stock notification email.
1149 *
1150 * @since 3.0.0
1151 * @param string $headers The email headers.
1152 * @param WC_Product $product Product instance.
1153 * @param null $null Unused.
1154 */
1155 apply_filters( 'woocommerce_email_headers', '', 'no_stock', $product, null ),
1156 /**
1157 * Filter the attachments of the no stock notification email.
1158 *
1159 * @since 3.0.0
1160 * @param array $attachments The email attachments.
1161 * @param WC_Product $product Product instance.
1162 * @param null $null Unused.
1163 */
1164 apply_filters( 'woocommerce_email_attachments', array(), 'no_stock', $product, null )
1165 );
1166
1167 $this->remove_email_sender_filters();
1168 }
1169
1170 /**
1171 * Backorder notification email.
1172 *
1173 * @param array $args Arguments.
1174 * @return void
1175 */
1176 public function backorder( $args ) {
1177 $args = wp_parse_args(
1178 $args,
1179 array(
1180 'product' => '',
1181 'quantity' => '',
1182 'order_id' => '',
1183 )
1184 );
1185
1186 $order = wc_get_order( $args['order_id'] );
1187 if (
1188 ! $args['product'] ||
1189 ! is_object( $args['product'] ) ||
1190 ! $args['quantity'] ||
1191 ! $order
1192 ) {
1193 return;
1194 }
1195
1196 $stock_before = $args['quantity'] + $args['product']->get_stock_quantity();
1197 $backordered_quantity = $args['quantity'] - max( 0, $stock_before );
1198
1199 $subject = sprintf( '[%s] %s', $this->get_blogname(), __( 'Product backorder', 'woocommerce' ) );
1200 /* translators: 1: backordered quantity 2: product name 3: order number */
1201 $message = sprintf( __( '%1$s units of %2$s have been backordered in order #%3$s.', 'woocommerce' ), $backordered_quantity, html_entity_decode( wp_strip_all_tags( $args['product']->get_formatted_name() ), ENT_QUOTES, get_bloginfo( 'charset' ) ), $order->get_order_number() );
1202
1203 $this->add_email_sender_filters();
1204
1205 wp_mail(
1206 /**
1207 * Filter the recipient of the backorder notification email.
1208 *
1209 * @since 3.0.0
1210 * @param string $recipient The recipient email address.
1211 * @param array $args Arguments.
1212 * @param null $null Unused.
1213 */
1214 apply_filters( 'woocommerce_email_recipient_backorder', get_option( 'woocommerce_stock_email_recipient' ), $args, null ),
1215 /**
1216 * Filter the subject of the backorder notification email.
1217 *
1218 * @since 3.0.0
1219 * @param string $subject The email subject.
1220 * @param array $args Arguments.
1221 * @param null $null Unused.
1222 */
1223 apply_filters( 'woocommerce_email_subject_backorder', $subject, $args, null ),
1224 /**
1225 * Filter the content of the backorder notification email.
1226 *
1227 * @since 3.0.0
1228 * @param string $message The email content.
1229 * @param array $args Arguments.
1230 * @param null $null Unused.
1231 */
1232 apply_filters( 'woocommerce_email_content_backorder', $message, $args ),
1233 /**
1234 * Filter the headers of the backorder notification email.
1235 *
1236 * @since 3.0.0
1237 * @param string $headers The email headers.
1238 * @param array $args Arguments.
1239 * @param null $null Unused.
1240 */
1241 apply_filters( 'woocommerce_email_headers', '', 'backorder', $args, null ),
1242 /**
1243 * Filter the attachments of the backorder notification email.
1244 *
1245 * @since 3.0.0
1246 * @param array $attachments The email attachments.
1247 * @param array $args Arguments.
1248 * @param null $null Unused.
1249 */
1250 apply_filters( 'woocommerce_email_attachments', array(), 'backorder', $args, null )
1251 );
1252
1253 $this->remove_email_sender_filters();
1254 }
1255
1256 /**
1257 * Adds Schema.org markup for order in JSON-LD format.
1258 *
1259 * @deprecated 3.0.0
1260 * @see WC_Structured_Data::generate_order_data()
1261 *
1262 * @since 2.6.0
1263 * @param WC_Order $order Order instance.
1264 * @param bool $sent_to_admin If should sent to admin.
1265 * @param bool $plain_text If is plain text email.
1266 * @return void
1267 */
1268 public function order_schema_markup( $order, $sent_to_admin = false, $plain_text = false ) {
1269 wc_deprecated_function( 'WC_Emails::order_schema_markup', '3.0', 'WC_Structured_Data::generate_order_data' );
1270
1271 WC()->structured_data->generate_order_data( $order, $sent_to_admin, $plain_text );
1272 WC()->structured_data->output_structured_data();
1273 }
1274 }
1275