PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
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 1.3.20 All 50 releases
← All changes | app/Services/Email/EmailNotifications.php +450 -65 1.3.25 → 1.7.0 View file →
@@ -57,10 +57,13 @@
57 57 * Order Renewal -> fluent_cart/subscription_renewed (customer + admin)
58 58 * Order Refunded -> fluent_cart/order_fully_refunded (customer + admin)
59 59 * Order Shipping Status Changed => fluent_cart/shipping_status_changed (customer + admin)
60 60 * Reminder Events:
61 - * - fluent_cart/invoice_reminder_due (@todo uncomment when invoice feature is deployed)
62 - * - fluent_cart/invoice_reminder_overdue (used for payment reminders)
61 + * - fluent_cart/renewal_reminder_due
62 + * - fluent_cart/renewal_reminder_overdue (manual/on-demand payment reminder)
63 + * - fluent_cart/renewal_overdue_first (scheduled, first overdue stage)
64 + * - fluent_cart/renewal_overdue_followup (scheduled, intermediate overdue stages)
65 + * - fluent_cart/renewal_overdue_final (scheduled, last overdue stage)
63 66 * - fluent_cart/subscription_renewal_reminder
64 67 * - fluent_cart/subscription_trial_end_reminder
65 68 */
66 69
@@ -138,8 +141,43 @@
138 141 'email_body' => '',
139 142 'attach_pdf_template' => '',
140 143 ]
141 144 ],
145 + 'subscription_renewal_failed_customer' => [
146 + 'event' => 'subscription_renewal_failed',
147 + 'group' => 'subscription',
148 + 'group_label' => __('Subscription Actions', 'fluent-cart'),
149 + 'title' => __('Notify customer when an automatic renewal charge fails', 'fluent-cart'),
150 + 'description' => __('This email will be sent to the customer when their saved payment method could not be charged for a subscription renewal.', 'fluent-cart'),
151 + 'recipient' => 'customer',
152 + 'smartcode_groups' => [],
153 + 'template_path' => 'subscription.renewal_failed.customer',
154 + 'is_async' => false,
155 + 'settings' => [
156 + 'active' => 'yes',
157 + 'subject' => __('Automatic Payment Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
158 + 'is_default_body' => 'yes',
159 + 'email_body' => '',
160 + ]
161 + ],
162 + 'subscription_renewal_failed_admin' => [
163 + 'event' => 'subscription_renewal_failed',
164 + 'group' => 'subscription',
165 + 'group_label' => __('Subscription Actions', 'fluent-cart'),
166 + 'title' => __('Notify admin when an automatic renewal charge fails', 'fluent-cart'),
167 + 'description' => __('This email will be sent to the admin when a saved payment method could not be charged for a subscription renewal.', 'fluent-cart'),
168 + 'recipient' => 'admin',
169 + 'smartcode_groups' => [],
170 + 'template_path' => 'subscription.renewal_failed.admin',
171 + 'pre_header' => __('An automatic subscription renewal charge failed. Review the order in the FluentCart Dashboard.', 'fluent-cart'),
172 + 'is_async' => false,
173 + 'settings' => [
174 + 'active' => 'no',
175 + 'subject' => __('Automatic Charge Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
176 + 'is_default_body' => 'yes',
177 + 'email_body' => '',
178 + ]
179 + ],
142 180 'subscription_canceled_customer' => [
143 181 'event' => 'subscription_canceled',
144 182 'group' => 'subscription',
145 183 'group_label' => __('Subscription Actions', 'fluent-cart'),
@@ -173,8 +211,25 @@
173 211 'is_default_body' => 'yes',
174 212 'email_body' => '',
175 213 ]
176 214 ],
215 + 'subscription_period_skipped_customer' => [
216 + 'event' => 'subscription_period_skipped',
217 + 'group' => 'subscription',
218 + 'group_label' => __('Subscription Actions', 'fluent-cart'),
219 + 'title' => __('Notify customer when a billing period is skipped', 'fluent-cart'),
220 + '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'),
221 + 'recipient' => 'customer',
222 + 'smartcode_groups' => [],
223 + 'template_path' => 'subscription.period_skipped.customer',
224 + 'is_async' => false,
225 + 'settings' => [
226 + 'active' => 'no',
227 + 'subject' => __('Your next payment has been skipped on {{settings.store_name}}', 'fluent-cart'),
228 + 'is_default_body' => 'yes',
229 + 'email_body' => '',
230 + ]
231 + ],
177 232 'order_refunded_admin' => [
178 233 'event' => 'order_refunded',
179 234 'group' => 'order',
180 235 'group_label' => __('Order Actions', 'fluent-cart'),
@@ -284,42 +339,132 @@
284 339 'email_body' => '',
285 340 'attach_pdf_template' => '',
286 341 ]
287 342 ],
288 - // @todo uncomment when invoice feature is deployed
289 - // 'invoice_reminder_due_customer' => [
290 - // 'event' => 'invoice_reminder_due',
291 - // 'title' => __('Invoice due reminder to customer', 'fluent-cart'),
292 - // 'description' => __('This email will be sent before/at invoice due date when payment is pending.', 'fluent-cart'),
293 - // 'recipient' => 'customer',
294 - // 'smartcode_groups' => [],
295 - // 'template_path' => 'order.reminder.due.customer',
296 - // 'is_async' => false,
297 - // 'settings' => [
298 - // 'active' => 'yes',
299 - // 'subject' => __('Payment Reminder #{{order.invoice_no}}', 'fluent-cart'),
300 - // 'is_default_body' => 'yes',
301 - // 'email_body' => '',
302 - // ]
303 - // ],
304 - // 'invoice_reminder_due_admin' => [
305 - // 'event' => 'invoice_reminder_due',
306 - // 'title' => __('Invoice due reminder copy to admin', 'fluent-cart'),
307 - // 'description' => __('This email will be sent to admin when a due reminder is sent to customer.', 'fluent-cart'),
308 - // 'recipient' => 'admin',
309 - // 'smartcode_groups' => [],
310 - // 'template_path' => 'order.reminder.due.admin',
311 - // 'pre_header' => 'An invoice due reminder was sent to a customer. Review the order details in this email or visit FluentCart Dashboard.',
312 - // 'is_async' => false,
313 - // 'settings' => [
314 - // 'active' => 'no',
315 - // 'subject' => __('Invoice Reminder Sent #{{order.invoice_no}}', 'fluent-cart'),
316 - // 'is_default_body' => 'yes',
317 - // 'email_body' => '',
318 - // ]
319 - // ],
320 - 'invoice_reminder_overdue_customer' => [
321 - 'event' => 'invoice_reminder_overdue',
343 + 'renewal_created_admin' => [
344 + 'event' => 'renewal_created',
345 + 'group' => 'manual_subscription',
346 + 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
347 + 'title' => __('Notify admin when a renewal order is created', 'fluent-cart'),
348 + 'description' => __('This email will be sent to the admin when a renewal order is generated for a store-managed subscription.', 'fluent-cart'),
349 + 'recipient' => 'admin',
350 + 'smartcode_groups' => [],
351 + 'template_path' => 'renewal.created.admin',
352 + 'pre_header' => 'A new renewal order has been created for a store-managed subscription. Review it in the FluentCart Dashboard.',
353 + 'is_async' => false,
354 + 'settings' => [
355 + 'active' => 'yes',
356 + 'subject' => __('New Renewal Order Created on {{settings.store_name}}', 'fluent-cart'),
357 + 'is_default_body' => 'yes',
358 + 'email_body' => '',
359 + ]
360 + ],
361 + 'renewal_created_customer' => [
362 + 'event' => 'renewal_created',
363 + 'group' => 'manual_subscription',
364 + 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
365 + 'title' => __('Send the renewal order to the customer when a renewal order is created', 'fluent-cart'),
366 + 'description' => __('This email will be sent to the customer when a renewal order is generated for a store-managed subscription.', 'fluent-cart'),
367 + 'recipient' => 'customer',
368 + 'smartcode_groups' => [],
369 + 'template_path' => 'renewal.created.customer',
370 + 'is_async' => false,
371 + 'settings' => [
372 + 'active' => 'yes',
373 + 'subject' => __('Your Subscription Renewal #{{order.invoice_no}}', 'fluent-cart'),
374 + 'is_default_body' => 'yes',
375 + 'email_body' => '',
376 + ]
377 + ],
378 + 'system_upcoming_charge_customer' => [
379 + 'event' => 'system_upcoming_charge',
380 + 'group' => 'system_subscription',
381 + 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
382 + 'title' => __('Notify customer before an automatic renewal charge', 'fluent-cart'),
383 + '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'),
384 + 'recipient' => 'customer',
385 + 'smartcode_groups' => [],
386 + 'template_path' => 'subscription.upcoming_charge.customer',
387 + 'is_async' => false,
388 + 'settings' => [
389 + 'active' => 'yes',
390 + 'subject' => __('Upcoming automatic payment for your subscription', 'fluent-cart'),
391 + 'is_default_body' => 'yes',
392 + 'email_body' => '',
393 + ]
394 + ],
395 + 'system_charge_failed_customer' => [
396 + 'event' => 'system_charge_failed',
397 + 'group' => 'system_subscription',
398 + 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
399 + 'title' => __('Notify customer when an automatic renewal charge fails', 'fluent-cart'),
400 + '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'),
401 + 'recipient' => 'customer',
402 + 'smartcode_groups' => [],
403 + 'template_path' => 'subscription.charge_failed.customer',
404 + 'is_async' => false,
405 + 'settings' => [
406 + 'active' => 'yes',
407 + 'subject' => __('Automatic Payment Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
408 + 'is_default_body' => 'yes',
409 + 'email_body' => '',
410 + ]
411 + ],
412 + 'system_charge_failed_admin' => [
413 + 'event' => 'system_charge_failed',
414 + 'group' => 'system_subscription',
415 + 'group_label' => __('Store-Managed Renewals · Auto-Charge', 'fluent-cart'),
416 + 'title' => __('Notify admin when an automatic renewal charge fails', 'fluent-cart'),
417 + 'description' => __('This email will be sent to the admin when a saved payment method could not be charged for a renewal order.', 'fluent-cart'),
418 + 'recipient' => 'admin',
419 + 'smartcode_groups' => [],
420 + 'template_path' => 'subscription.charge_failed.admin',
421 + 'pre_header' => 'An automatic subscription renewal charge failed. Review the renewal order in the FluentCart Dashboard.',
422 + 'is_async' => false,
423 + 'settings' => [
424 + 'active' => 'no',
425 + 'subject' => __('Automatic Charge Failed for Renewal #{{order.invoice_no}}', 'fluent-cart'),
426 + 'is_default_body' => 'yes',
427 + 'email_body' => '',
428 + ]
429 + ],
430 + 'renewal_reminder_due_admin' => [
431 + 'event' => 'renewal_reminder_due',
432 + 'group' => 'manual_subscription',
433 + 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
434 + 'title' => __('Renewal due reminder copy to admin', 'fluent-cart'),
435 + 'description' => __('This email will be sent to admin when a due reminder is sent to customer.', 'fluent-cart'),
436 + 'recipient' => 'admin',
437 + 'smartcode_groups' => [],
438 + 'template_path' => 'order.reminder.due.admin',
439 + 'pre_header' => 'A renewal due reminder was sent to a customer. Review the order details in this email or visit FluentCart Dashboard.',
440 + 'is_async' => false,
441 + 'settings' => [
442 + 'active' => 'no',
443 + 'subject' => __('Renewal Reminder Sent #{{order.invoice_no}}', 'fluent-cart'),
444 + 'is_default_body' => 'yes',
445 + 'email_body' => '',
446 + ]
447 + ],
448 + 'renewal_reminder_due_customer' => [
449 + 'event' => 'renewal_reminder_due',
450 + 'group' => 'manual_subscription',
451 + 'group_label' => __('Store-Managed Renewals', 'fluent-cart'),
452 + 'title' => __('Renewal due reminder to customer', 'fluent-cart'),
453 + 'description' => __('This email will be sent before/at renewal due date when payment is pending.', 'fluent-cart'),
454 + 'recipient' => 'customer',
455 + 'smartcode_groups' => [],
456 + 'template_path' => 'order.reminder.due.customer',
457 + 'is_async' => false,
458 + 'settings' => [
459 + 'active' => 'yes',
460 + 'subject' => __('Payment Reminder #{{order.invoice_no}}', 'fluent-cart'),
461 + 'is_default_body' => 'yes',
462 + 'email_body' => '',
463 + ]
464 + ],
465 + 'renewal_reminder_overdue_customer' => [
466 + 'event' => 'renewal_reminder_overdue',
322 467 'group' => 'scheduler',
323 468 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
324 469 'title' => __('Payment reminder to customer', 'fluent-cart'),
325 470 'description' => __('This email will be sent to remind customer about pending payment.', 'fluent-cart'),
@@ -334,21 +479,39 @@
334 479 'is_default_body' => 'yes',
335 480 'email_body' => '',
336 481 ]
337 482 ],
338 - 'subscription_renewal_reminder_customer' => [
339 - 'event' => 'subscription_renewal_reminder',
340 - 'group' => 'scheduler',
341 - 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
342 - 'title' => __('Upcoming renewal reminder to customer', 'fluent-cart'),
343 - 'description' => __('This email will be sent before subscription auto-renewal date.', 'fluent-cart'),
483 + 'subscription_past_due_admin' => [
484 + 'event' => 'subscription_past_due',
485 + 'group' => 'subscription',
486 + 'group_label' => __('Subscription Actions', 'fluent-cart'),
487 + 'title' => __('Notify admin when a subscription is past due', 'fluent-cart'),
488 + 'description' => __('This email will be sent to the admin when a subscription is marked as past due.', 'fluent-cart'),
489 + 'recipient' => 'admin',
490 + 'smartcode_groups' => [],
491 + 'template_path' => 'subscription.past_due.admin',
492 + 'pre_header' => 'A subscription payment is past due. Review the subscription details in this email or visit FluentCart Dashboard.',
493 + 'is_async' => false,
494 + 'settings' => [
495 + 'active' => 'no',
496 + 'subject' => __('Subscription Past Due #{{order.invoice_no}}', 'fluent-cart'),
497 + 'is_default_body' => 'yes',
498 + 'email_body' => '',
499 + ]
500 + ],
501 + 'subscription_past_due_customer' => [
502 + 'event' => 'subscription_past_due',
503 + 'group' => 'subscription',
504 + 'group_label' => __('Subscription Actions', 'fluent-cart'),
505 + 'title' => __('Notify customer when subscription is past due', 'fluent-cart'),
506 + 'description' => __('This email will be sent to the customer when their subscription is marked as past due.', 'fluent-cart'),
344 507 'recipient' => 'customer',
345 508 'smartcode_groups' => [],
346 - 'template_path' => 'subscription.reminder.customer',
509 + 'template_path' => 'subscription.past_due.customer',
347 510 'is_async' => false,
348 511 'settings' => [
349 512 'active' => 'yes',
350 - 'subject' => __('Upcoming Renewal Reminder from {{settings.store_name}}', 'fluent-cart'),
513 + 'subject' => __('Your Subscription is Past Due on {{settings.store_name}}', 'fluent-cart'),
351 514 'is_default_body' => 'yes',
352 515 'email_body' => '',
353 516 ]
354 517 ],
@@ -369,26 +532,77 @@
369 532 'is_default_body' => 'yes',
370 533 'email_body' => '',
371 534 ]
372 535 ],
373 - 'subscription_trial_end_reminder_customer' => [
374 - 'event' => 'subscription_trial_end_reminder',
536 + 'subscription_renewal_reminder_customer' => [
537 + 'event' => 'subscription_renewal_reminder',
375 538 'group' => 'scheduler',
376 539 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
377 - 'title' => __('Trial ending soon reminder to customer', 'fluent-cart'),
378 - 'description' => __('This email will be sent before a trial period ends and converts to a paid subscription.', 'fluent-cart'),
540 + 'title' => __('Upcoming renewal reminder to customer', 'fluent-cart'),
541 + 'description' => __('This email will be sent before subscription auto-renewal date.', 'fluent-cart'),
379 542 'recipient' => 'customer',
380 543 'smartcode_groups' => [],
381 - 'template_path' => 'subscription.trial_end.customer',
544 + 'template_path' => 'subscription.reminder.customer',
382 545 'is_async' => false,
383 546 'settings' => [
384 547 'active' => 'yes',
385 - 'subject' => __('Your Trial is Ending Soon - {{settings.store_name}}', 'fluent-cart'),
548 + 'subject' => __('Upcoming Renewal Reminder from {{settings.store_name}}', 'fluent-cart'),
386 549 'is_default_body' => 'yes',
387 550 'email_body' => '',
388 551 ]
389 552 ],
390 - 'subscription_trial_end_reminder_admin' => [
553 + 'renewal_overdue_first_customer' => [
554 + 'event' => 'renewal_overdue_first',
555 + 'group' => 'scheduler',
556 + 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
557 + 'title' => __('First overdue renewal reminder to customer', 'fluent-cart'),
558 + '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'),
559 + 'recipient' => 'customer',
560 + 'smartcode_groups' => [],
561 + 'template_path' => 'order.reminder.renewal_overdue.first.customer',
562 + 'is_async' => false,
563 + 'settings' => [
564 + 'active' => 'yes',
565 + 'subject' => __('Payment Reminder for Renewal #{{order.order_ref}}', 'fluent-cart'),
566 + 'is_default_body' => 'yes',
567 + 'email_body' => '',
568 + ]
569 + ],
570 + 'renewal_overdue_followup_customer' => [
571 + 'event' => 'renewal_overdue_followup',
572 + 'group' => 'scheduler',
573 + 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
574 + 'title' => __('Follow-up overdue renewal reminder to customer', 'fluent-cart'),
575 + 'description' => __('This email will be sent at the intermediate overdue reminder days while the renewal payment remains pending.', 'fluent-cart'),
576 + 'recipient' => 'customer',
577 + 'smartcode_groups' => [],
578 + 'template_path' => 'order.reminder.renewal_overdue.followup.customer',
579 + 'is_async' => false,
580 + 'settings' => [
581 + 'active' => 'yes',
582 + 'subject' => __('Payment Overdue for Renewal #{{order.order_ref}}', 'fluent-cart'),
583 + 'is_default_body' => 'yes',
584 + 'email_body' => '',
585 + ]
586 + ],
587 + 'renewal_overdue_final_customer' => [
588 + 'event' => 'renewal_overdue_final',
589 + 'group' => 'scheduler',
590 + 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
591 + 'title' => __('Final overdue renewal notice to customer', 'fluent-cart'),
592 + 'description' => __('This email will be sent at the last configured overdue reminder day while the renewal payment is still pending.', 'fluent-cart'),
593 + 'recipient' => 'customer',
594 + 'smartcode_groups' => [],
595 + 'template_path' => 'order.reminder.renewal_overdue.final.customer',
596 + 'is_async' => false,
597 + 'settings' => [
598 + 'active' => 'yes',
599 + 'subject' => __('Final Notice: Payment Overdue for Renewal #{{order.order_ref}}', 'fluent-cart'),
600 + 'is_default_body' => 'yes',
601 + 'email_body' => '',
602 + ]
603 + ],
604 + 'subscription_trial_end_reminder_admin' => [
391 605 'event' => 'subscription_trial_end_reminder',
392 606 'group' => 'scheduler',
393 607 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
394 608 'title' => __('Trial ending soon reminder copy to admin', 'fluent-cart'),
@@ -404,12 +618,142 @@
404 618 'is_default_body' => 'yes',
405 619 'email_body' => '',
406 620 ]
407 621 ],
622 + 'subscription_trial_end_reminder_customer' => [
623 + 'event' => 'subscription_trial_end_reminder',
624 + 'group' => 'scheduler',
625 + 'group_label' => __('Scheduler / Reminder Actions', 'fluent-cart'),
626 + 'title' => __('Trial ending soon reminder to customer', 'fluent-cart'),
627 + 'description' => __('This email will be sent before a trial period ends and converts to a paid subscription.', 'fluent-cart'),
628 + 'recipient' => 'customer',
629 + 'smartcode_groups' => [],
630 + 'template_path' => 'subscription.trial_end.customer',
631 + 'is_async' => false,
632 + 'settings' => [
633 + 'active' => 'yes',
634 + 'subject' => __('Your Trial is Ending Soon - {{settings.store_name}}', 'fluent-cart'),
635 + 'is_default_body' => 'yes',
636 + 'email_body' => '',
637 + ]
638 + ],
639 + 'review_submitted_admin' => [
640 + 'event' => 'review_created',
641 + 'group' => 'review',
642 + 'group_label' => __('Review Actions', 'fluent-cart'),
643 + 'title' => __('Send mail to admin when a new review is submitted', 'fluent-cart'),
644 + 'description' => __('Email admin when a customer submits a new review.', 'fluent-cart'),
645 + 'recipient' => 'admin',
646 + 'smartcode_groups' => [],
647 + 'template_path' => 'review.submitted.admin',
648 + 'is_async' => false,
649 + 'pre_header' => __('A new product review has been submitted.', 'fluent-cart'),
650 + 'settings' => [
651 + 'active' => 'yes',
652 + 'subject' => __('New Review Submitted', 'fluent-cart'),
653 + 'is_default_body' => 'yes',
654 + 'email_body' => '',
655 + ]
656 + ],
657 + 'review_approved_customer' => [
658 + // The settled hook, not the transition: fired from a queued
659 + // job once the review has been re-read and is still approved,
660 + // the way order_paid_done is to order_paid. A bulk approve is
661 + // fifty of these, and none of them is sent inside that request.
662 + 'event' => 'review_approved_done',
663 + 'group' => 'review',
664 + 'group_label' => __('Review Actions', 'fluent-cart'),
665 + 'title' => __('Send mail to the reviewer when their review is approved', 'fluent-cart'),
666 + 'description' => __('Email the customer who wrote a review once it is approved and visible on the product page.', 'fluent-cart'),
667 + 'recipient' => 'customer',
668 + // Declared, because the customer recipient resolves through an
669 + // order and a review has none. The resolver behind this code is
670 + // the one every review notification uses.
671 + 'to' => '{{review.recipient_email}}',
672 + 'smartcode_groups' => [],
673 + 'template_path' => 'review.approved.customer',
674 + 'is_async' => false,
675 + 'pre_header' => __('Your review is now live.', 'fluent-cart'),
676 + 'settings' => [
677 + 'active' => 'yes',
678 + 'subject' => __('Your review of {{review.product_title}} is now live', 'fluent-cart'),
679 + 'is_default_body' => 'yes',
680 + 'email_body' => '',
681 + ]
682 + ],
683 + 'review_replied_customer' => [
684 + // Fired from a queued job once the reply and the review it
685 + // answers have both been re-read and are still published.
686 + 'event' => 'review_replied_done',
687 + 'group' => 'review',
688 + 'group_label' => __('Review Actions', 'fluent-cart'),
689 + 'title' => __('Send mail to the reviewer when the store replies to their review', 'fluent-cart'),
690 + 'description' => __('Email the customer who wrote a review when the store posts a reply to it.', 'fluent-cart'),
691 + 'recipient' => 'customer',
692 + // The review's author, resolved the way every review
693 + // notification resolves it — {{review.*}} is the review being
694 + // answered, not the reply.
695 + 'to' => '{{review.recipient_email}}',
696 + 'smartcode_groups' => [],
697 + 'template_path' => 'review.replied.customer',
698 + 'is_async' => false,
699 + 'pre_header' => __('The store has replied to your review.', 'fluent-cart'),
700 + 'settings' => [
701 + 'active' => 'yes',
702 + 'subject' => __('{{settings.store_name}} replied to your review of {{review.product_title}}', 'fluent-cart'),
703 + 'is_default_body' => 'yes',
704 + 'email_body' => '',
705 + ]
706 + ],
408 707 ];
409 708
410 709 }
411 710
711 + /**
712 + * The names of the notifications that would send for an event — the same
713 + * gate getNotificationsOfEvent() applies, without rendering a body.
714 + *
715 + * For a caller that only needs to know whether, and which, before it
716 + * commits to something — a once-only lease, say — and will render at
717 + * send time through mailByEmailName(). Rendering here as well would draw
718 + * every template twice per send.
719 + *
720 + * Kept in step with getNotificationsOfEvent(): the four conditions below
721 + * are that method's, in the same order.
722 + *
723 + * @param string $event
724 + * @return string[] notification names, in registry order
725 + */
726 + public static function activeNotificationNamesOfEvent($event): array
727 + {
728 + $mailingSettings = static::getSettings();
729 + $names = [];
730 +
731 + foreach (static::getNotifications() as $key => $notification) {
732 + if (Arr::get($notification, 'event') !== $event) {
733 + continue;
734 + }
735 +
736 + $settings = Arr::get($notification, 'settings');
737 + if (!in_array($notification['event'], ['order_placed_offline', 'renewal_reminder_overdue']) && Arr::get($settings, 'active') !== 'yes') {
738 + continue;
739 + }
740 +
741 + $recipient = Arr::get($notification, 'recipient');
742 + if (!Arr::get($notification, 'to') && !in_array($recipient, ['admin', 'customer', 'user', 'subscriber'])) {
743 + continue;
744 + }
745 +
746 + if (empty(static::resolveRecipientTemplate($notification, $mailingSettings))) {
747 + continue;
748 + }
749 +
750 + $names[] = (string) $key;
751 + }
752 +
753 + return $names;
754 + }
755 +
412 756 public static function getNotificationsOfEvent($event, $viewData): array
413 757 {
414 758 $notifications = static::getNotifications();
415 759 $notifications = array_filter($notifications, function ($notification) use ($event) {
@@ -427,9 +771,9 @@
427 771
428 772 $settings = Arr::get($notification, 'settings');
429 773
430 774 // Skip active check for on-demand notifications (offline payments, payment reminders)
431 - if (in_array($notification['event'], ['order_placed_offline', 'invoice_reminder_overdue']) || Arr::get($settings, 'active') === 'yes') {
775 + if (in_array($notification['event'], ['order_placed_offline', 'renewal_reminder_overdue']) || Arr::get($settings, 'active') === 'yes') {
432 776 // Continue with normal flow
433 777 } else {
434 778 continue;
435 779 }
@@ -434,17 +778,16 @@
434 778 continue;
435 779 }
436 780 $recipient = Arr::get($notification, 'recipient');
437 781
438 - if (!in_array($recipient, ['admin', 'customer', 'user', 'subscriber'])) {
782 + // A notification that declares its own `to` carries its recipient
783 + // with it, so the whitelist — which only describes the recipients
784 + // core knows how to resolve — does not apply to it.
785 + if (!Arr::get($notification, 'to') && !in_array($recipient, ['admin', 'customer', 'user', 'subscriber'])) {
439 786 continue;
440 787 }
441 788
442 - if ($recipient === 'admin') {
443 - $toEmail = Arr::get($mailingSettings, 'admin_email', '');
444 - } else {
445 - $toEmail = self::EMAIL_RECIPIENT_MAP[$recipient];
446 - }
789 + $toEmail = static::resolveRecipientTemplate($notification, $mailingSettings);
447 790
448 791 if (empty($toEmail)) {
449 792 continue;
450 793 }
@@ -459,15 +802,10 @@
459 802 {
460 803 $settings = Arr::get($notification, 'settings');
461 804 $mailingSettings = static::getSettings();
462 805
463 -
464 806 $recipient = Arr::get($notification, 'recipient');
465 - if ($recipient === 'admin') {
466 - $toEmail = Arr::get($mailingSettings, 'admin_email', '');
467 - } else {
468 - $toEmail = self::EMAIL_RECIPIENT_MAP[$recipient];
469 - }
807 + $toEmail = static::resolveRecipientTemplate($notification, $mailingSettings);
470 808
471 809
472 810 $isDefaultEmailBody = Arr::get($settings, 'is_default_body', 'yes') === 'yes' || empty(Arr::get($settings, 'email_body'));
473 811
@@ -482,11 +820,58 @@
482 820 'is_async' => $notification['is_async'],
483 821 'subject' => Arr::get($settings, 'subject'),
484 822 'is_custom' => !$isDefaultEmailBody,
485 823 'settings' => $settings,
824 + // Carried through so send-time consumers can tell one notification
825 + // from another, and admin-bound mail from customer-bound — see the
826 + // admin recipient filter in parseEmailContent().
827 + 'name' => Arr::get($notification, 'name', ''),
828 + 'event' => Arr::get($notification, 'event', ''),
829 + 'recipient' => $recipient,
486 830 ];
487 831 }
488 832
833 + /**
834 + * Resolve the recipient template for a notification.
835 + *
836 + * A notification may declare its own `to` — a smartcode template, or a
837 + * literal address — which wins outright. Add-ons need this: they register
838 + * notifications that are not order-bound, and the built-in customer
839 + * recipient resolves to {{order.customer.email}}, which has nothing to read
840 + * from without an order.
841 + *
842 + * Falling back to the recipient map keeps every core notification, and any
843 + * add-on that does not declare `to`, on exactly the path it used before.
844 + *
845 + * The returned value is a template — parseEmailContent() runs it through
846 + * the shortcode parser before use. A declared `to` is returned as given:
847 + * wp_mail() accepts an array of recipients as well as a comma-separated
848 + * string, so an array is passed through rather than flattened.
849 + *
850 + * @param array $notification
851 + * @param array|null $mailingSettings resolved settings, passed in to avoid a repeat lookup in loops
852 + * @return string|array
853 + */
854 + public static function resolveRecipientTemplate($notification, $mailingSettings = null)
855 + {
856 + $declaredTo = Arr::get($notification, 'to', '');
857 + if (!empty($declaredTo)) {
858 + return $declaredTo;
859 + }
860 +
861 + if ($mailingSettings === null) {
862 + $mailingSettings = static::getSettings();
863 + }
864 +
865 + $recipient = Arr::get($notification, 'recipient');
866 +
867 + if ($recipient === 'admin') {
868 + return (string)Arr::get($mailingSettings, 'admin_email', '');
869 + }
870 +
871 + return (string)Arr::get(self::EMAIL_RECIPIENT_MAP, $recipient, '');
872 + }
873 +
489 874 public static function getNotification($name)
490 875 {
491 876 $notifications = static::getNotifications();
492 877 return Arr::get($notifications, $name);
@@ -508,9 +893,9 @@
508 893 'active',
509 894 'subject',
510 895 'email_body',
511 896 'is_default_body',
512 - 'attach_pdf_template'
897 + 'attach_pdf_template',
513 898 ];
514 899 $allConfig = static::getSettings();
515 900 $config = static::getNotificationConfig($name);
516 901