PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 All 51 releases
fluent-cart / app / Services / Email / EmailNotifications.php

EmailNotifications.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.7.1, at app/Services/Email/EmailNotifications.php

1,008 lines 53.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Email;
4
5 use FluentCart\Api\ModuleSettings;
6 use FluentCart\App\Models\Meta;
7 use FluentCart\App\Services\Cache;
8 use FluentCart\App\Services\TemplateService;
9 use FluentCart\Framework\Support\Arr;
10
11 class EmailNotifications
12 {
13
14 const EMAIL_RECIPIENT_MAP = [
15 'customer' => '{{order.customer.email}}',
16 'user' => '{{user.user_email}}',
17 'subscriber' => '{{order.customer.email}}'
18 ];
19
20 const META_KEY = 'email_notifications_config';
21
22 /** The events the reviews module raises. */
23 const REVIEW_EVENTS = ['review_created', 'review_approved_done', 'review_replied_done'];
24
25 public static function getNotifications(): array
26 {
27 $settings = static::getDefaultNotifications();
28 $settings = apply_filters('fluent_cart/email_notifications', $settings);
29
30 // Review notifications are neither offered nor sent while the module
31 // is off; their stored settings are kept by name.
32 if (!ModuleSettings::isActive('reviews')) {
33 $settings = array_filter($settings, function ($setting) {
34 return !in_array(Arr::get($setting, 'event'), static::REVIEW_EVENTS, true);
35 });
36 }
37
38 $config = Arr::get(static::cachedSettings(), 'notification_config', []);
39
40 foreach ($settings as $key => &$setting) {
41 $setting['name'] = $key;
42
43 if (empty($setting['group_label'])) {
44 $setting['group_label'] = __('Other Actions', 'fluent-cart');
45 }
46
47 $keyConfig = Arr::get($config, $key, []);
48 if (!$keyConfig) {
49 continue;
50 }
51
52 $setting['settings'] = wp_parse_args($keyConfig, $setting['settings']);
53
54 // Backward compat: migrate old attach_pdf to attach_pdf_template
55 if (empty($setting['settings']['attach_pdf_template']) && !empty($setting['settings']['attach_pdf']) && $setting['settings']['attach_pdf'] === 'yes') {
56 $setting['settings']['attach_pdf_template'] = 'order_receipt';
57 }
58 unset($setting['settings']['attach_pdf']);
59 }
60
61 return $settings;
62 }
63
64 public static function getDefaultNotifications(): array
65 {
66
67 /**
68 * Order Placed (Offline Payment) -> fluent_cart/order_placed_offline (customer + admin)
69 * Order Paid -> fluent_cart/order_paid (customer + admin)
70 * Order Renewal -> fluent_cart/subscription_renewed (customer + admin)
71 * Order Refunded -> fluent_cart/order_fully_refunded (customer + admin)
72 * Order Shipping Status Changed => fluent_cart/shipping_status_changed (customer + admin)
73 * Reminder Events:
74 * - fluent_cart/renewal_reminder_due
75 * - fluent_cart/renewal_reminder_overdue (manual/on-demand payment reminder)
76 * - fluent_cart/renewal_overdue_first (scheduled, first overdue stage)
77 * - fluent_cart/renewal_overdue_followup (scheduled, intermediate overdue stages)
78 * - fluent_cart/renewal_overdue_final (scheduled, last overdue stage)
79 * - fluent_cart/subscription_renewal_reminder
80 * - fluent_cart/subscription_trial_end_reminder
81 */
82
83 return [
84 'order_paid_admin' => [
85 'event' => 'order_paid_done',
86 'group' => 'order',
87 'group_label' => __('Order Actions', 'fluent-cart'),
88 'title' => __('Send mail to admin after New Order Paid', 'fluent-cart'),
89 'description' => __('This email will be sent to the admin after an order is placed.', 'fluent-cart'),
90 'recipient' => 'admin',
91 'smartcode_groups' => [],
92 'template_path' => 'order.paid.admin',
93 'is_async' => false,
94 'pre_header' => __('You got a new order on your shop. Congratulations! Checkout all the details in this email. You can also go to FluentCart Dashboard to view the order details and manage it. Thank you for using FluentCart.', 'fluent-cart'),
95 'settings' => [
96 'active' => 'yes',
97 'subject' => __('New Sales On {{settings.store_name}}', 'fluent-cart'),
98 'is_default_body' => 'yes',
99 'email_body' => '',
100 'attach_pdf_template' => '',
101 ]
102 ],
103 'order_paid_customer' => [
104 'event' => 'order_paid',
105 'group' => 'order',
106 'group_label' => __('Order Actions', 'fluent-cart'),
107 'title' => __('Purchase receipt to customer', 'fluent-cart'),
108 'description' => __('This email will be sent to the customer after an order is placed.', 'fluent-cart'),
109 'recipient' => 'customer',
110 'smartcode_groups' => [],
111 'template_path' => 'order.paid.customer',
112 'is_async' => false,
113 'settings' => [
114 'active' => 'yes',
115 'subject' => __('Purchase Receipt #{{order.invoice_no}}', 'fluent-cart'),
116 'is_default_body' => 'yes',
117 'email_body' => '',
118 'attach_pdf_template' => '',
119 ]
120 ],
121 'subscription_renewal_customer' => [
122 'event' => 'subscription_renewed',
123 'group' => 'subscription',
124 'group_label' => __('Subscription Actions', 'fluent-cart'),
125 'title' => __('Send mail to customer after a subscription renewed', 'fluent-cart'),
126 'description' => __('This email will be sent to the customer after a renewal payment made', 'fluent-cart'),
127 'recipient' => 'customer',
128 'smartcode_groups' => [],
129 'template_path' => 'subscription.renewal.customer',
130 'is_async' => false,
131 'settings' => [
132 'active' => 'yes',
133 'subject' => __('Renewal Confirmation on {{settings.store_name}}', 'fluent-cart'),
134 'is_default_body' => 'yes',
135 'email_body' => '',
136 'attach_pdf_template' => '',
137 ]
138 ],
139 'subscription_renewal_admin' => [
140 'event' => 'subscription_renewed',
141 'group' => 'subscription',
142 'group_label' => __('Subscription Actions', 'fluent-cart'),
143 'title' => __('Send mail to admin after a subscription renewed', 'fluent-cart'),
144 'description' => __('This email will be sent to the admin after a renewal payment made', 'fluent-cart'),
145 'recipient' => 'admin',
146 'smartcode_groups' => [],
147 'template_path' => 'subscription.renewal.admin',
148 'pre_header' => __('You got a new Renewal on your shop. Congratulations! Checkout all the details in this email. You can also go to FluentCart Dashboard to view the order details and manage it. Thank you for using FluentCart.', 'fluent-cart'),
149 'is_async' => false,
150 'settings' => [
151 'active' => 'yes',
152 'subject' => __('New Renewal On {{settings.store_name}}', 'fluent-cart'),
153 'is_default_body' => 'yes',
154 'email_body' => '',
155 'attach_pdf_template' => '',
156 ]
157 ],
158 'subscription_renewal_failed_customer' => [
159 'event' => 'subscription_renewal_failed',
160 'group' => 'subscription',
161 'group_label' => __('Subscription Actions', 'fluent-cart'),
162 'title' => __('Notify customer when an automatic renewal charge fails', 'fluent-cart'),
163 'description' => __('This email will be sent to the customer when their saved payment method could not be charged for a subscription renewal.', 'fluent-cart'),
164 'recipient' => 'customer',
165 'smartcode_groups' => [],
166 'template_path' => 'subscription.renewal_failed.customer',
167 'is_async' => false,
168 'settings' => [
169 'active' => 'yes',
170 'subject' => __('Automatic Payment Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
171 'is_default_body' => 'yes',
172 'email_body' => '',
173 ]
174 ],
175 'subscription_renewal_failed_admin' => [
176 'event' => 'subscription_renewal_failed',
177 'group' => 'subscription',
178 'group_label' => __('Subscription Actions', 'fluent-cart'),
179 'title' => __('Notify admin when an automatic renewal charge fails', 'fluent-cart'),
180 'description' => __('This email will be sent to the admin when a saved payment method could not be charged for a subscription renewal.', 'fluent-cart'),
181 'recipient' => 'admin',
182 'smartcode_groups' => [],
183 'template_path' => 'subscription.renewal_failed.admin',
184 'pre_header' => __('An automatic subscription renewal charge failed. Review the order in the FluentCart Dashboard.', 'fluent-cart'),
185 'is_async' => false,
186 'settings' => [
187 'active' => 'no',
188 'subject' => __('Automatic Charge Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
189 'is_default_body' => 'yes',
190 'email_body' => '',
191 ]
192 ],
193 'subscription_canceled_customer' => [
194 'event' => 'subscription_canceled',
195 'group' => 'subscription',
196 'group_label' => __('Subscription Actions', 'fluent-cart'),
197 'title' => __('Send mail to customer when a subscription is canceled', 'fluent-cart'),
198 'description' => __('This email will be sent to the customer when their subscription is canceled.', 'fluent-cart'),
199 'recipient' => 'customer',
200 'smartcode_groups' => [],
201 'template_path' => 'subscription.canceled.customer',
202 'is_async' => false,
203 'settings' => [
204 'active' => 'yes',
205 'subject' => __('Subscription Canceled on {{settings.store_name}}', 'fluent-cart'),
206 'is_default_body' => 'yes',
207 'email_body' => '',
208 ]
209 ],
210 'subscription_canceled_admin' => [
211 'event' => 'subscription_canceled',
212 'group' => 'subscription',
213 'group_label' => __('Subscription Actions', 'fluent-cart'),
214 'title' => __('Send mail to admin when a subscription is canceled', 'fluent-cart'),
215 'description' => __('This email will be sent to the admin when a subscription is canceled.', 'fluent-cart'),
216 'recipient' => 'admin',
217 'smartcode_groups' => [],
218 'template_path' => 'subscription.canceled.admin',
219 'pre_header' => __('A subscription has been canceled. Review the details in this email or visit FluentCart Dashboard to manage the subscription.', 'fluent-cart'),
220 'is_async' => false,
221 'settings' => [
222 'active' => 'yes',
223 'subject' => __('Subscription Canceled - {{order.customer.full_name}}', 'fluent-cart'),
224 'is_default_body' => 'yes',
225 'email_body' => '',
226 ]
227 ],
228 'subscription_period_skipped_customer' => [
229 'event' => 'subscription_period_skipped',
230 'group' => 'subscription',
231 'group_label' => __('Subscription Actions', 'fluent-cart'),
232 'title' => __('Notify customer when a billing period is skipped', 'fluent-cart'),
233 'description' => __('This email will be sent to the customer when an admin skips their next billing period. The internal reason and actor are never included. Off by default.', 'fluent-cart'),
234 'recipient' => 'customer',
235 'smartcode_groups' => [],
236 'template_path' => 'subscription.period_skipped.customer',
237 'is_async' => false,
238 'settings' => [
239 'active' => 'no',
240 'subject' => __('Your next payment has been skipped on {{settings.store_name}}', 'fluent-cart'),
241 'is_default_body' => 'yes',
242 'email_body' => '',
243 ]
244 ],
245 'order_refunded_admin' => [
246 'event' => 'order_refunded',
247 'group' => 'order',
248 'group_label' => __('Order Actions', 'fluent-cart'),
249 'title' => __('Send mail to admin after a refund.', 'fluent-cart'),
250 'description' => __('This email will be sent to the admin after an order is refunded (partial / full).', 'fluent-cart'),
251 'recipient' => 'admin',
252 'smartcode_groups' => [],
253 'template_path' => 'order.refunded.admin',
254 'is_async' => false,
255 'settings' => [
256 'active' => 'no',
257 'subject' => __('Refund sent to {{order.customer.full_name}}', 'fluent-cart'),
258 'is_default_body' => 'yes',
259 'email_body' => '',
260 'attach_pdf_template' => '',
261 ]
262 ],
263 'order_refunded_customer' => [
264 'event' => 'order_refunded',
265 'group' => 'order',
266 'group_label' => __('Order Actions', 'fluent-cart'),
267 'title' => __('Send mail to customer after a refund.', 'fluent-cart'),
268 'description' => __('This email will be sent to the customer after an order is refunded (partial / full).', 'fluent-cart'),
269 'recipient' => 'customer',
270 'smartcode_groups' => [],
271 'template_path' => 'order.refunded.customer',
272 'is_async' => false,
273 'settings' => [
274 'active' => 'yes',
275 'subject' => __('Refund Confirmation from {{settings.store_name}}', 'fluent-cart'),
276 'is_default_body' => 'yes',
277 'email_body' => '',
278 'attach_pdf_template' => '',
279 ]
280 ],
281 'order_shipped_customer' => [
282 'event' => 'shipping_status_changed_to_shipped',
283 'group' => 'order',
284 'group_label' => __('Order Actions', 'fluent-cart'),
285 'title' => __('Send mail to customer when shipping status changed to shipped.', 'fluent-cart'),
286 'description' => __('This email will be sent to the customer after an order is marked as shipped', 'fluent-cart'),
287 'recipient' => 'customer',
288 'smartcode_groups' => [],
289 'template_path' => 'order.shipped.customer',
290 'is_async' => false,
291 'settings' => [
292 'active' => 'yes',
293 'subject' => __('Order has been shipped #{{order.invoice_no}} 📦', 'fluent-cart'),
294 'is_default_body' => 'yes',
295 'email_body' => '',
296 'attach_pdf_template' => '',
297 ]
298 ],
299 'order_delivered_customer' => [
300 'event' => 'shipping_status_changed_to_delivered',
301 'group' => 'order',
302 'group_label' => __('Order Actions', 'fluent-cart'),
303 'title' => __('Send mail to customer when shipping status changed to delivered.', 'fluent-cart'),
304 'description' => __('This email will be sent to the customer after an order is marked as delivered', 'fluent-cart'),
305 'recipient' => 'customer',
306 'smartcode_groups' => [],
307 'template_path' => 'order.delivered.customer',
308 'is_async' => false,
309 'settings' => [
310 'active' => 'yes',
311 'subject' => __('Order has been delivered #{{order.invoice_no}}', 'fluent-cart'),
312 'is_default_body' => 'yes',
313 'email_body' => '',
314 'attach_pdf_template' => '',
315 ]
316 ],
317 'order_placed_admin' => [
318 'event' => 'order_placed_offline',
319 'group' => 'order',
320 'group_label' => __('Order Actions', 'fluent-cart'),
321 'title' => __('Send mail to admin after New Order Placed (Offline Payment)', 'fluent-cart'),
322 'description' => __('This email will be sent to the admin when an order is placed using offline payment method.', 'fluent-cart'),
323 'recipient' => 'admin',
324 'smartcode_groups' => [],
325 'template_path' => 'order.placed.admin',
326 'is_async' => false,
327 'manage_toggle' => 'no',
328 'toggle_label' => __('Auto-enabled for offline payments', 'fluent-cart'),
329 'pre_header' => __('You have a new order on your shop placed with offline payment. Please review the order details in this email. You can also go to FluentCart Dashboard to view the order details and manage it. Thank you for using FluentCart.', 'fluent-cart'),
330 'settings' => [
331 'subject' => __('New Order on {{settings.store_name}} (Offline Payment)', 'fluent-cart'),
332 'is_default_body' => 'yes',
333 'email_body' => '',
334 'attach_pdf_template' => '',
335 ]
336 ],
337 'order_placed_customer' => [
338 'event' => 'order_placed_offline',
339 'group' => 'order',
340 'group_label' => __('Order Actions', 'fluent-cart'),
341 'title' => __('Order confirmation to customer (Offline Payment)', 'fluent-cart'),
342 'description' => __('This email will be sent to the customer when an order is placed using offline payment method.', 'fluent-cart'),
343 'recipient' => 'customer',
344 'smartcode_groups' => [],
345 'template_path' => 'order.placed.customer',
346 'is_async' => false,
347 'manage_toggle' => 'no',
348 'toggle_label' => __('Auto-enabled for offline payments', 'fluent-cart'),
349 'settings' => [
350 'subject' => __('Order Confirmation #{{order.invoice_no}} (Offline Payment)', 'fluent-cart'),
351 'is_default_body' => 'yes',
352 'email_body' => '',
353 'attach_pdf_template' => '',
354 ]
355 ],
356 'renewal_created_admin' => [
357 'event' => 'renewal_created',
358 'group' => 'manual_subscription',
359 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
360 'title' => __('Notify admin when a renewal order is created', 'fluent-cart'),
361 'description' => __('This email will be sent to the admin when a renewal order is generated for a store-managed subscription.', 'fluent-cart'),
362 'recipient' => 'admin',
363 'smartcode_groups' => [],
364 'template_path' => 'renewal.created.admin',
365 'pre_header' => 'A new renewal order has been created for a store-managed subscription. Review it in the FluentCart Dashboard.',
366 'is_async' => false,
367 'settings' => [
368 'active' => 'yes',
369 'subject' => __('New Renewal Order Created on {{settings.store_name}}', 'fluent-cart'),
370 'is_default_body' => 'yes',
371 'email_body' => '',
372 ]
373 ],
374 'renewal_created_customer' => [
375 'event' => 'renewal_created',
376 'group' => 'manual_subscription',
377 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
378 'title' => __('Send the renewal order to the customer when a renewal order is created', 'fluent-cart'),
379 'description' => __('This email will be sent to the customer when a renewal order is generated for a store-managed subscription.', 'fluent-cart'),
380 'recipient' => 'customer',
381 'smartcode_groups' => [],
382 'template_path' => 'renewal.created.customer',
383 'is_async' => false,
384 'settings' => [
385 'active' => 'yes',
386 'subject' => __('Your Subscription Renewal #{{order.invoice_no}}', 'fluent-cart'),
387 'is_default_body' => 'yes',
388 'email_body' => '',
389 ]
390 ],
391 'system_upcoming_charge_customer' => [
392 'event' => 'system_upcoming_charge',
393 'group' => 'system_subscription',
394 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
395 'title' => __('Notify customer before an automatic renewal charge', 'fluent-cart'),
396 'description' => __('This email tells the customer the amount and date of the upcoming automatic charge to their saved payment method, when the renewal order is created ahead of the due date.', 'fluent-cart'),
397 'recipient' => 'customer',
398 'smartcode_groups' => [],
399 'template_path' => 'subscription.upcoming_charge.customer',
400 'is_async' => false,
401 'settings' => [
402 'active' => 'yes',
403 'subject' => __('Upcoming automatic payment for your subscription', 'fluent-cart'),
404 'is_default_body' => 'yes',
405 'email_body' => '',
406 ]
407 ],
408 'system_charge_failed_customer' => [
409 'event' => 'system_charge_failed',
410 'group' => 'system_subscription',
411 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
412 'title' => __('Notify customer when an automatic renewal charge fails', 'fluent-cart'),
413 'description' => __('This email will be sent to the customer when the saved payment method could not be charged for a renewal order. It includes the failure reason and a Pay Now link.', 'fluent-cart'),
414 'recipient' => 'customer',
415 'smartcode_groups' => [],
416 'template_path' => 'subscription.charge_failed.customer',
417 'is_async' => false,
418 'settings' => [
419 'active' => 'yes',
420 'subject' => __('Automatic Payment Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
421 'is_default_body' => 'yes',
422 'email_body' => '',
423 ]
424 ],
425 'system_charge_failed_admin' => [
426 'event' => 'system_charge_failed',
427 'group' => 'system_subscription',
428 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
429 'title' => __('Notify admin when an automatic renewal charge fails', 'fluent-cart'),
430 'description' => __('This email will be sent to the admin when a saved payment method could not be charged for a renewal order.', 'fluent-cart'),
431 'recipient' => 'admin',
432 'smartcode_groups' => [],
433 'template_path' => 'subscription.charge_failed.admin',
434 'pre_header' => 'An automatic subscription renewal charge failed. Review the renewal order in the FluentCart Dashboard.',
435 'is_async' => false,
436 'settings' => [
437 'active' => 'no',
438 'subject' => __('Automatic Charge Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
439 'is_default_body' => 'yes',
440 'email_body' => '',
441 ]
442 ],
443 'renewal_reminder_due_admin' => [
444 'event' => 'renewal_reminder_due',
445 'group' => 'manual_subscription',
446 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
447 'title' => __('Renewal due reminder copy to admin', 'fluent-cart'),
448 'description' => __('This email will be sent to admin when a due reminder is sent to customer.', 'fluent-cart'),
449 'recipient' => 'admin',
450 'smartcode_groups' => [],
451 'template_path' => 'order.reminder.due.admin',
452 'pre_header' => 'A renewal due reminder was sent to a customer. Review the order details in this email or visit FluentCart Dashboard.',
453 'is_async' => false,
454 'settings' => [
455 'active' => 'no',
456 'subject' => __('Renewal Reminder Sent #{{order.invoice_no}}', 'fluent-cart'),
457 'is_default_body' => 'yes',
458 'email_body' => '',
459 ]
460 ],
461 'renewal_reminder_due_customer' => [
462 'event' => 'renewal_reminder_due',
463 'group' => 'manual_subscription',
464 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
465 'title' => __('Renewal due reminder to customer', 'fluent-cart'),
466 'description' => __('This email will be sent before/at renewal due date when payment is pending.', 'fluent-cart'),
467 'recipient' => 'customer',
468 'smartcode_groups' => [],
469 'template_path' => 'order.reminder.due.customer',
470 'is_async' => false,
471 'settings' => [
472 'active' => 'yes',
473 'subject' => __('Payment Reminder #{{order.invoice_no}}', 'fluent-cart'),
474 'is_default_body' => 'yes',
475 'email_body' => '',
476 ]
477 ],
478 'renewal_reminder_overdue_customer' => [
479 'event' => 'renewal_reminder_overdue',
480 'group' => 'scheduler',
481 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
482 'title' => __('Payment reminder to customer', 'fluent-cart'),
483 'description' => __('This email will be sent to remind customer about pending payment.', 'fluent-cart'),
484 'recipient' => 'customer',
485 'smartcode_groups' => [],
486 'template_path' => 'order.reminder.overdue.customer',
487 'is_async' => false,
488 'manage_toggle' => 'no',
489 'toggle_label' => __('On demand', 'fluent-cart'),
490 'settings' => [
491 'subject' => __('Payment Reminder - Order #{{order.id}}', 'fluent-cart'),
492 'is_default_body' => 'yes',
493 'email_body' => '',
494 ]
495 ],
496 'subscription_past_due_admin' => [
497 'event' => 'subscription_past_due',
498 'group' => 'subscription',
499 'group_label' => __('Subscription Actions', 'fluent-cart'),
500 'title' => __('Notify admin when a subscription is past due', 'fluent-cart'),
501 'description' => __('This email will be sent to the admin when a subscription is marked as past due.', 'fluent-cart'),
502 'recipient' => 'admin',
503 'smartcode_groups' => [],
504 'template_path' => 'subscription.past_due.admin',
505 'pre_header' => 'A subscription payment is past due. Review the subscription details in this email or visit FluentCart Dashboard.',
506 'is_async' => false,
507 'settings' => [
508 'active' => 'no',
509 'subject' => __('Subscription Past Due #{{order.invoice_no}}', 'fluent-cart'),
510 'is_default_body' => 'yes',
511 'email_body' => '',
512 ]
513 ],
514 'subscription_past_due_customer' => [
515 'event' => 'subscription_past_due',
516 'group' => 'subscription',
517 'group_label' => __('Subscription Actions', 'fluent-cart'),
518 'title' => __('Notify customer when subscription is past due', 'fluent-cart'),
519 'description' => __('This email will be sent to the customer when their subscription is marked as past due.', 'fluent-cart'),
520 'recipient' => 'customer',
521 'smartcode_groups' => [],
522 'template_path' => 'subscription.past_due.customer',
523 'is_async' => false,
524 'settings' => [
525 'active' => 'yes',
526 'subject' => __('Your Subscription is Past Due on {{settings.store_name}}', 'fluent-cart'),
527 'is_default_body' => 'yes',
528 'email_body' => '',
529 ]
530 ],
531 'subscription_renewal_reminder_admin' => [
532 'event' => 'subscription_renewal_reminder',
533 'group' => 'scheduler',
534 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
535 'title' => __('Upcoming renewal reminder copy to admin', 'fluent-cart'),
536 'description' => __('This email will be sent to admin when an upcoming renewal reminder is sent.', 'fluent-cart'),
537 'recipient' => 'admin',
538 'smartcode_groups' => [],
539 'template_path' => 'subscription.reminder.admin',
540 'pre_header' => __('A subscription renewal reminder was sent to a customer. Review subscription details from FluentCart Dashboard.', 'fluent-cart'),
541 'is_async' => false,
542 'settings' => [
543 'active' => 'no',
544 'subject' => __('Renewal Reminder Sent for {{order.customer.full_name}}', 'fluent-cart'),
545 'is_default_body' => 'yes',
546 'email_body' => '',
547 ]
548 ],
549 'subscription_renewal_reminder_customer' => [
550 'event' => 'subscription_renewal_reminder',
551 'group' => 'scheduler',
552 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
553 'title' => __('Upcoming renewal reminder to customer', 'fluent-cart'),
554 'description' => __('This email will be sent before subscription auto-renewal date.', 'fluent-cart'),
555 'recipient' => 'customer',
556 'smartcode_groups' => [],
557 'template_path' => 'subscription.reminder.customer',
558 'is_async' => false,
559 'settings' => [
560 'active' => 'yes',
561 'subject' => __('Upcoming Renewal Reminder from {{settings.store_name}}', 'fluent-cart'),
562 'is_default_body' => 'yes',
563 'email_body' => '',
564 ]
565 ],
566 'renewal_overdue_first_customer' => [
567 'event' => 'renewal_overdue_first',
568 'group' => 'scheduler',
569 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
570 'title' => __('First overdue renewal reminder to customer', 'fluent-cart'),
571 'description' => __('This email will be sent at the first overdue reminder day after a renewal due date while the payment is still pending.', 'fluent-cart'),
572 'recipient' => 'customer',
573 'smartcode_groups' => [],
574 'template_path' => 'order.reminder.renewal_overdue.first.customer',
575 'is_async' => false,
576 'settings' => [
577 'active' => 'yes',
578 'subject' => __('Payment Reminder for Renewal #{{order.order_ref}}', 'fluent-cart'),
579 'is_default_body' => 'yes',
580 'email_body' => '',
581 ]
582 ],
583 'renewal_overdue_followup_customer' => [
584 'event' => 'renewal_overdue_followup',
585 'group' => 'scheduler',
586 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
587 'title' => __('Follow-up overdue renewal reminder to customer', 'fluent-cart'),
588 'description' => __('This email will be sent at the intermediate overdue reminder days while the renewal payment remains pending.', 'fluent-cart'),
589 'recipient' => 'customer',
590 'smartcode_groups' => [],
591 'template_path' => 'order.reminder.renewal_overdue.followup.customer',
592 'is_async' => false,
593 'settings' => [
594 'active' => 'yes',
595 'subject' => __('Payment Overdue for Renewal #{{order.order_ref}}', 'fluent-cart'),
596 'is_default_body' => 'yes',
597 'email_body' => '',
598 ]
599 ],
600 'renewal_overdue_final_customer' => [
601 'event' => 'renewal_overdue_final',
602 'group' => 'scheduler',
603 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
604 'title' => __('Final overdue renewal notice to customer', 'fluent-cart'),
605 'description' => __('This email will be sent at the last configured overdue reminder day while the renewal payment is still pending.', 'fluent-cart'),
606 'recipient' => 'customer',
607 'smartcode_groups' => [],
608 'template_path' => 'order.reminder.renewal_overdue.final.customer',
609 'is_async' => false,
610 'settings' => [
611 'active' => 'yes',
612 'subject' => __('Final Notice: Payment Overdue for Renewal #{{order.order_ref}}', 'fluent-cart'),
613 'is_default_body' => 'yes',
614 'email_body' => '',
615 ]
616 ],
617 'subscription_trial_end_reminder_admin' => [
618 'event' => 'subscription_trial_end_reminder',
619 'group' => 'scheduler',
620 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
621 'title' => __('Trial ending soon reminder copy to admin', 'fluent-cart'),
622 'description' => __('This email will be sent to admin when a trial ending reminder is sent to a customer.', 'fluent-cart'),
623 'recipient' => 'admin',
624 'smartcode_groups' => [],
625 'template_path' => 'subscription.trial_end.admin',
626 'pre_header' => __('A trial ending soon reminder was sent to a customer. Review subscription details from FluentCart Dashboard.', 'fluent-cart'),
627 'is_async' => false,
628 'settings' => [
629 'active' => 'no',
630 'subject' => __('Trial Ending Soon - {{order.customer.full_name}}', 'fluent-cart'),
631 'is_default_body' => 'yes',
632 'email_body' => '',
633 ]
634 ],
635 'subscription_trial_end_reminder_customer' => [
636 'event' => 'subscription_trial_end_reminder',
637 'group' => 'scheduler',
638 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
639 'title' => __('Trial ending soon reminder to customer', 'fluent-cart'),
640 'description' => __('This email will be sent before a trial period ends and converts to a paid subscription.', 'fluent-cart'),
641 'recipient' => 'customer',
642 'smartcode_groups' => [],
643 'template_path' => 'subscription.trial_end.customer',
644 'is_async' => false,
645 'settings' => [
646 'active' => 'yes',
647 'subject' => __('Your Trial is Ending Soon - {{settings.store_name}}', 'fluent-cart'),
648 'is_default_body' => 'yes',
649 'email_body' => '',
650 ]
651 ],
652 'review_submitted_admin' => [
653 'event' => 'review_created',
654 'group' => 'review',
655 'group_label' => __('Review Actions', 'fluent-cart'),
656 'title' => __('Send mail to admin when a new review is submitted', 'fluent-cart'),
657 'description' => __('Email admin when a customer submits a new review.', 'fluent-cart'),
658 'recipient' => 'admin',
659 'smartcode_groups' => [],
660 'template_path' => 'review.submitted.admin',
661 'is_async' => false,
662 'pre_header' => __('A new product review has been submitted.', 'fluent-cart'),
663 'settings' => [
664 'active' => 'yes',
665 'subject' => __('New Review Submitted', 'fluent-cart'),
666 'is_default_body' => 'yes',
667 'email_body' => '',
668 ]
669 ],
670 'review_approved_customer' => [
671 // The settled hook, not the transition: fired from a queued
672 // job once the review has been re-read and is still approved,
673 // the way order_paid_done is to order_paid. A bulk approve is
674 // fifty of these, and none of them is sent inside that request.
675 'event' => 'review_approved_done',
676 'group' => 'review',
677 'group_label' => __('Review Actions', 'fluent-cart'),
678 'title' => __('Send mail to the reviewer when their review is approved', 'fluent-cart'),
679 'description' => __('Email the customer who wrote a review once it is approved and visible on the product page.', 'fluent-cart'),
680 'recipient' => 'customer',
681 // Declared, because the customer recipient resolves through an
682 // order and a review has none. The resolver behind this code is
683 // the one every review notification uses.
684 'to' => '{{review.recipient_email}}',
685 'smartcode_groups' => [],
686 'template_path' => 'review.approved.customer',
687 'is_async' => false,
688 'pre_header' => __('Your review is now live.', 'fluent-cart'),
689 'settings' => [
690 'active' => 'yes',
691 'subject' => __('Your review of {{review.product_title}} is now live', 'fluent-cart'),
692 'is_default_body' => 'yes',
693 'email_body' => '',
694 ]
695 ],
696 'review_replied_customer' => [
697 // Fired from a queued job once the reply and the review it
698 // answers have both been re-read and are still published.
699 'event' => 'review_replied_done',
700 'group' => 'review',
701 'group_label' => __('Review Actions', 'fluent-cart'),
702 'title' => __('Send mail to the reviewer when the store replies to their review', 'fluent-cart'),
703 'description' => __('Email the customer who wrote a review when the store posts a reply to it.', 'fluent-cart'),
704 'recipient' => 'customer',
705 // The review's author, resolved the way every review
706 // notification resolves it — {{review.*}} is the review being
707 // answered, not the reply.
708 'to' => '{{review.recipient_email}}',
709 'smartcode_groups' => [],
710 'template_path' => 'review.replied.customer',
711 'is_async' => false,
712 'pre_header' => __('The store has replied to your review.', 'fluent-cart'),
713 'settings' => [
714 'active' => 'yes',
715 'subject' => __('{{settings.store_name}} replied to your review of {{review.product_title}}', 'fluent-cart'),
716 'is_default_body' => 'yes',
717 'email_body' => '',
718 ]
719 ],
720 ];
721
722 }
723
724 /**
725 * The names of the notifications that would send for an event — the same
726 * gate getNotificationsOfEvent() applies, without rendering a body.
727 *
728 * For a caller that only needs to know whether, and which, before it
729 * commits to something — a once-only lease, say — and will render at
730 * send time through mailByEmailName(). Rendering here as well would draw
731 * every template twice per send.
732 *
733 * Kept in step with getNotificationsOfEvent(): the four conditions below
734 * are that method's, in the same order.
735 *
736 * @param string $event
737 * @return string[] notification names, in registry order
738 */
739 public static function activeNotificationNamesOfEvent($event): array
740 {
741 $mailingSettings = static::getSettings();
742 $names = [];
743
744 foreach (static::getNotifications() as $key => $notification) {
745 if (Arr::get($notification, 'event') !== $event) {
746 continue;
747 }
748
749 $settings = Arr::get($notification, 'settings');
750 if (!in_array($notification['event'], ['order_placed_offline', 'renewal_reminder_overdue']) && Arr::get($settings, 'active') !== 'yes') {
751 continue;
752 }
753
754 $recipient = Arr::get($notification, 'recipient');
755 if (!Arr::get($notification, 'to') && !in_array($recipient, ['admin', 'customer', 'user', 'subscriber'])) {
756 continue;
757 }
758
759 if (empty(static::resolveRecipientTemplate($notification, $mailingSettings))) {
760 continue;
761 }
762
763 $names[] = (string) $key;
764 }
765
766 return $names;
767 }
768
769 public static function getNotificationsOfEvent($event, $viewData): array
770 {
771 $notifications = static::getNotifications();
772 $notifications = array_filter($notifications, function ($notification) use ($event) {
773 return $notification['event'] === $event;
774 });
775
776 if (empty($notifications)) {
777 return [];
778 }
779
780 $emails = [];
781 $mailingSettings = static::getSettings();
782
783 foreach ($notifications as $key => $notification) {
784
785 $settings = Arr::get($notification, 'settings');
786
787 // Skip active check for on-demand notifications (offline payments, payment reminders)
788 if (in_array($notification['event'], ['order_placed_offline', 'renewal_reminder_overdue']) || Arr::get($settings, 'active') === 'yes') {
789 // Continue with normal flow
790 } else {
791 continue;
792 }
793 $recipient = Arr::get($notification, 'recipient');
794
795 // A notification that declares its own `to` carries its recipient
796 // with it, so the whitelist — which only describes the recipients
797 // core knows how to resolve — does not apply to it.
798 if (!Arr::get($notification, 'to') && !in_array($recipient, ['admin', 'customer', 'user', 'subscriber'])) {
799 continue;
800 }
801
802 $toEmail = static::resolveRecipientTemplate($notification, $mailingSettings);
803
804 if (empty($toEmail)) {
805 continue;
806 }
807
808 $emails[$key] = static::formatNotification($notification, $viewData);
809 }
810
811 return $emails;
812 }
813
814 public static function formatNotification($notification, $viewData): array
815 {
816 $settings = Arr::get($notification, 'settings');
817 $mailingSettings = static::getSettings();
818
819 $recipient = Arr::get($notification, 'recipient');
820 $toEmail = static::resolveRecipientTemplate($notification, $mailingSettings);
821
822
823 $isDefaultEmailBody = Arr::get($settings, 'is_default_body', 'yes') === 'yes' || empty(Arr::get($settings, 'email_body'));
824
825 $emailBody = $isDefaultEmailBody ?
826 TemplateService::getTemplateByPathName(Arr::get($notification, 'template_path'), $viewData) :
827 Arr::get($settings, 'email_body');
828
829 return [
830 'to' => $toEmail,
831 'body' => $emailBody,
832 'pre_header' => Arr::get($notification, 'pre_header', ''),
833 'is_async' => $notification['is_async'],
834 'subject' => Arr::get($settings, 'subject'),
835 'is_custom' => !$isDefaultEmailBody,
836 'settings' => $settings,
837 // Carried through so send-time consumers can tell one notification
838 // from another, and admin-bound mail from customer-bound — see the
839 // admin recipient filter in parseEmailContent().
840 'name' => Arr::get($notification, 'name', ''),
841 'event' => Arr::get($notification, 'event', ''),
842 'recipient' => $recipient,
843 ];
844 }
845
846 /**
847 * Resolve the recipient template for a notification.
848 *
849 * A notification may declare its own `to` — a smartcode template, or a
850 * literal address — which wins outright. Add-ons need this: they register
851 * notifications that are not order-bound, and the built-in customer
852 * recipient resolves to {{order.customer.email}}, which has nothing to read
853 * from without an order.
854 *
855 * Falling back to the recipient map keeps every core notification, and any
856 * add-on that does not declare `to`, on exactly the path it used before.
857 *
858 * The returned value is a template — parseEmailContent() runs it through
859 * the shortcode parser before use. A declared `to` is returned as given:
860 * wp_mail() accepts an array of recipients as well as a comma-separated
861 * string, so an array is passed through rather than flattened.
862 *
863 * @param array $notification
864 * @param array|null $mailingSettings resolved settings, passed in to avoid a repeat lookup in loops
865 * @return string|array
866 */
867 public static function resolveRecipientTemplate($notification, $mailingSettings = null)
868 {
869 $declaredTo = Arr::get($notification, 'to', '');
870 if (!empty($declaredTo)) {
871 return $declaredTo;
872 }
873
874 if ($mailingSettings === null) {
875 $mailingSettings = static::getSettings();
876 }
877
878 $recipient = Arr::get($notification, 'recipient');
879
880 if ($recipient === 'admin') {
881 return (string)Arr::get($mailingSettings, 'admin_email', '');
882 }
883
884 return (string)Arr::get(self::EMAIL_RECIPIENT_MAP, $recipient, '');
885 }
886
887 public static function getNotification($name)
888 {
889 $notifications = static::getNotifications();
890 return Arr::get($notifications, $name);
891 }
892
893 //returns only the notification settings
894 public static function getNotificationConfig($notificationName = null)
895 {
896 $configs = self::getNotifications();
897 if ($notificationName) {
898 return Arr::get($configs, $notificationName . '.settings', []);
899 }
900 return Arr::pluck($configs, 'settings', 'name');
901 }
902
903 public static function updateNotification($name, $data)
904 {
905 $updateableKeys = [
906 'active',
907 'subject',
908 'email_body',
909 'is_default_body',
910 'attach_pdf_template',
911 ];
912 $allConfig = static::getSettings();
913 $config = static::getNotificationConfig($name);
914
915 foreach ($updateableKeys as $key) {
916 $defaultValue = Arr::get($config, $key, '');
917 $config[$key] = Arr::get($data, $key, $defaultValue);
918 }
919
920 // When switching back to default body, clear the custom email body unless draft mode is enabled
921 if (Arr::get($config, 'is_default_body') === 'yes' && !apply_filters('fluent_cart/keep_email_body_draft', false, ['notification_name' => $name])) {
922 $config['email_body'] = '';
923 }
924
925 Arr::set($allConfig, 'notification_config.' . $name, $config);
926
927 $isUpdate = Meta::query()->updateOrCreate(
928 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
929 ['meta_key' => static::META_KEY, 'object_type' => 'email_notification'],
930 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
931 ['meta_value' => $allConfig]
932 );
933
934 if ($isUpdate) {
935 static::updateCache();
936 }
937
938 return static::getNotification($name);
939 }
940
941 public static function defaultSettings(): array
942 {
943 return [
944 'from_name' => '',
945 'from_email' => '',
946 'reply_to_name' => '',
947 'reply_to_email' => '',
948 'email_footer' => '',
949 'show_email_footer' => 'yes',
950 'admin_email' => '{{wp.admin_email}}',
951 ];
952 }
953
954 //returns all the settings
955 public static function getSettings($key = null)
956 {
957
958 $defaultSettings = static::defaultSettings();
959 $cachedSettings = static::cachedSettings();
960 $settings = wp_parse_args($cachedSettings, $defaultSettings);
961
962 $settings['notification_config'] = wp_parse_args(
963 Arr::get($cachedSettings, 'notification_config', []),
964 Arr::get($defaultSettings, 'notification_config', [])
965 );
966
967 if (!empty($key)) {
968 return Arr::get($settings, $key);
969 }
970
971 return $settings;
972 }
973
974 public static function cachedSettings()
975 {
976 return Cache::get(static::META_KEY, function () {
977 $config = Meta::query()->where('object_type', 'email_notification')
978 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
979 ->where('meta_key', static::META_KEY)->first();
980 return $config ? $config->meta_value : [];
981 });
982 }
983
984 public static function updateSettings($data)
985 {
986 $allConfig = static::getSettings();
987
988 foreach ($data as $key => $value) {
989 Arr::set($allConfig, $key, $value);
990 }
991
992 $config = Meta::query()->updateOrCreate(
993 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
994 ['meta_key' => static::META_KEY, 'object_type' => 'email_notification'],
995 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
996 ['meta_value' => $allConfig]
997 );
998
999 static::updateCache();
1000 return $config;
1001 }
1002
1003 private static function updateCache(): void
1004 {
1005 Cache::forget(static::META_KEY);
1006 }
1007 }
1008