PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / Email / DeferredEmailQueue.php
woocommerce / src / Internal / Email Last commit date
DeferredEmailQueue.php 1 month ago EmailColors.php 11 months ago EmailFont.php 1 year ago EmailLogger.php 1 month ago EmailStyleSync.php 11 months ago OrderPriceFormatter.php 1 year ago
DeferredEmailQueue.php
291 lines
1 <?php
2
3 declare( strict_types = 1 );
4
5 namespace Automattic\WooCommerce\Internal\Email;
6
7 use Automattic\WooCommerce\Internal\StockNotifications\Factory as StockNotificationFactory;
8 use Automattic\WooCommerce\Internal\StockNotifications\Notification as StockNotification;
9
10 /**
11 * Handles deferred transactional email sending via Action Scheduler.
12 *
13 * Collects email callbacks during a request and dispatches each one as an
14 * individual Action Scheduler action on shutdown, replacing the legacy
15 * WC_Background_Emailer approach.
16 *
17 * @since 10.8.0
18 */
19 final class DeferredEmailQueue {
20
21 /**
22 * Action Scheduler hook for processing a queued email.
23 */
24 private const AS_HOOK = 'woocommerce_send_queued_transactional_email';
25
26 /**
27 * Action Scheduler group for email actions.
28 */
29 private const AS_GROUP = 'woocommerce-emails';
30
31 /**
32 * Key for object references stored in queued email args.
33 */
34 private const QUEUED_OBJECT_KEY = '__woocommerce_deferred_email_object';
35
36 /**
37 * Queue of email callbacks collected during the current request.
38 *
39 * @var array<int, array{filter: string, args: array}>
40 */
41 private array $queue = array();
42
43 /**
44 * Whether the shutdown hook has been registered.
45 *
46 * @var bool
47 */
48 private bool $shutdown_registered = false;
49
50 /**
51 * Initialize hooks.
52 *
53 * @internal
54 */
55 final public function init(): void { // phpcs:ignore Generic.CodeAnalysis.UnnecessaryFinalModifier.Found
56 // Registered unconditionally so previously-scheduled AS jobs can still
57 // be processed even if the feature is later disabled.
58 add_action( self::AS_HOOK, array( $this, 'send_queued_transactional_email' ), 10, 2 );
59 }
60
61 /**
62 * Add an email callback to the queue.
63 *
64 * Returns false when any argument cannot be represented in Action Scheduler
65 * storage, allowing callers to fall back to sending the email synchronously.
66 *
67 * @param string $filter The action hook name that triggered the email.
68 * @param array $args The arguments passed to the action hook.
69 * @return bool True if the email was queued.
70 */
71 public function push( string $filter, array $args ): bool {
72 try {
73 $args = $this->prepare_arg_for_queue( $args );
74 } catch ( \UnexpectedValueException $e ) {
75 return false;
76 }
77
78 $this->queue[] = array(
79 'filter' => $filter,
80 'args' => $args,
81 );
82
83 if ( ! $this->shutdown_registered ) {
84 add_action( 'shutdown', array( $this, 'dispatch' ), 100 );
85 $this->shutdown_registered = true;
86 }
87
88 return true;
89 }
90
91 /**
92 * Dispatch queued emails via Action Scheduler on shutdown.
93 *
94 * Each email is scheduled as an individual AS action for atomic
95 * processing and per-email failure isolation.
96 *
97 * @internal
98 */
99 public function dispatch(): void {
100 if ( empty( $this->queue ) ) {
101 return;
102 }
103
104 foreach ( $this->queue as $item ) {
105 \WC()->queue()->add(
106 self::AS_HOOK,
107 array( $item['filter'], $item['args'] ),
108 self::AS_GROUP
109 );
110 }
111
112 $this->queue = array();
113 $this->shutdown_registered = false;
114 }
115
116 /**
117 * Process a single queued transactional email from Action Scheduler.
118 *
119 * @internal
120 *
121 * @param mixed $filter The action hook name.
122 * @param mixed $args The arguments for the email callback.
123 */
124 public function send_queued_transactional_email( $filter, $args ): void {
125 if ( ! is_string( $filter ) || ! is_array( $args ) ) {
126 return;
127 }
128
129 $args = $this->restore_args_from_queue( $args );
130 if ( null === $args ) {
131 return;
132 }
133
134 \WC_Emails::send_queued_transactional_email( $filter, $args );
135 }
136
137 /**
138 * Convert a queued argument to a JSON-safe value.
139 *
140 * @param mixed $arg The argument to convert.
141 * @return mixed
142 * @throws \UnexpectedValueException When a queued object argument cannot be prepared.
143 */
144 private function prepare_arg_for_queue( $arg ) {
145 if ( is_array( $arg ) ) {
146 foreach ( $arg as $key => $value ) {
147 $arg[ $key ] = $this->prepare_arg_for_queue( $value );
148 }
149
150 return $arg;
151 }
152
153 if ( is_object( $arg ) ) {
154 foreach ( $this->get_supported_object_types() as $type => $object_type ) {
155 if ( ! $arg instanceof $object_type['class'] ) {
156 continue;
157 }
158
159 $id = $object_type['get_id']( $arg );
160
161 if ( empty( $id ) || ( ! is_int( $id ) && ! is_string( $id ) ) ) {
162 throw new \UnexpectedValueException( 'Queued email object argument cannot be prepared.' );
163 }
164
165 return array(
166 self::QUEUED_OBJECT_KEY => array(
167 'type' => $type,
168 'id' => $id,
169 ),
170 );
171 }
172
173 throw new \UnexpectedValueException( 'Queued email object argument cannot be prepared.' );
174 }
175
176 return $arg;
177 }
178
179 /**
180 * Restore queued arguments after Action Scheduler storage.
181 *
182 * @param array $args The arguments for the email callback.
183 * @return array|null
184 */
185 private function restore_args_from_queue( array $args ): ?array {
186 try {
187 foreach ( $args as $key => $arg ) {
188 $args[ $key ] = $this->restore_arg_from_queue( $arg );
189 }
190
191 return $args;
192 } catch ( \UnexpectedValueException $e ) {
193 return null;
194 }
195 }
196
197 /**
198 * Restore a queued argument after Action Scheduler storage.
199 *
200 * @param mixed $arg The argument to restore.
201 * @return mixed
202 * @throws \UnexpectedValueException When a queued object reference cannot be restored.
203 */
204 private function restore_arg_from_queue( $arg ) {
205 if ( ! is_array( $arg ) ) {
206 return $arg;
207 }
208
209 if ( ! array_key_exists( self::QUEUED_OBJECT_KEY, $arg ) ) {
210 foreach ( $arg as $key => $value ) {
211 $arg[ $key ] = $this->restore_arg_from_queue( $value );
212 }
213
214 return $arg;
215 }
216
217 $reference = $arg[ self::QUEUED_OBJECT_KEY ];
218
219 if ( ! is_array( $reference ) || ! isset( $reference['type'], $reference['id'] ) ) {
220 throw new \UnexpectedValueException( 'Queued email object reference is invalid.' );
221 }
222
223 $id = $reference['id'];
224
225 if ( ! is_int( $id ) && ! is_string( $id ) ) {
226 throw new \UnexpectedValueException( 'Queued email object reference is invalid.' );
227 }
228
229 $object_type = $this->get_supported_object_types()[ (string) $reference['type'] ] ?? null;
230
231 if ( ! is_array( $object_type ) ) {
232 throw new \UnexpectedValueException( 'Queued email object reference is invalid.' );
233 }
234
235 $object = $object_type['fetch']( $id );
236
237 if ( ! is_object( $object ) ) {
238 throw new \UnexpectedValueException( 'Queued email object reference cannot be restored.' );
239 }
240
241 return $object;
242 }
243
244 /**
245 * Get supported queued object types.
246 *
247 * @return array<string, array{class: class-string, get_id: callable, fetch: callable}>
248 */
249 private function get_supported_object_types(): array {
250 return array(
251 'product' => array(
252 'class' => \WC_Product::class,
253 'get_id' => static function ( $queued_object ) {
254 return $queued_object instanceof \WC_Product ? $queued_object->get_id() : null;
255 },
256 'fetch' => static function ( $id ) {
257 return \WC()->call_function( 'wc_get_product', $id );
258 },
259 ),
260 'order' => array(
261 'class' => \WC_Order::class,
262 'get_id' => static function ( $queued_object ) {
263 return $queued_object instanceof \WC_Order ? $queued_object->get_id() : null;
264 },
265 'fetch' => static function ( $id ) {
266 return \WC()->call_function( 'wc_get_order', $id );
267 },
268 ),
269 'payment_gateway' => array(
270 'class' => \WC_Payment_Gateway::class,
271 'get_id' => static function ( $queued_object ) {
272 return $queued_object instanceof \WC_Payment_Gateway ? $queued_object->id : null;
273 },
274 'fetch' => static function ( $id ) {
275 $gateways = \WC()->payment_gateways()->payment_gateways();
276 return $gateways[ $id ] ?? null;
277 },
278 ),
279 'stock_notification' => array(
280 'class' => StockNotification::class,
281 'get_id' => static function ( $queued_object ) {
282 return $queued_object instanceof StockNotification ? $queued_object->get_id() : null;
283 },
284 'fetch' => static function ( $id ) {
285 return StockNotificationFactory::get_notification( (int) $id );
286 },
287 ),
288 );
289 }
290 }
291