| @@ -1,8 +1,9 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace FluentCart\App\Services\Email; |
| 4 | 4 | |
| 5 | +use FluentCart\Api\ModuleSettings; | |
| 5 | 6 | use FluentCart\App\Models\Meta; |
| 6 | 7 | use FluentCart\App\Services\Cache; |
| 7 | 8 | use FluentCart\App\Services\TemplateService; |
| 8 | 9 | use FluentCart\Framework\Support\Arr; |
| @@ -17,12 +18,24 @@ | ||
| 17 | 18 | ]; |
| 18 | 19 | |
| 19 | 20 | const META_KEY = 'email_notifications_config'; |
| 20 | 21 | |
| 22 | + /** The events the reviews module raises. */ | |
| 23 | + const REVIEW_EVENTS = ['review_created', 'review_approved_done', 'review_replied_done']; | |
| 24 | + | |
| 21 | 25 | public static function getNotifications(): array |
| 22 | 26 | { |
| 23 | 27 | $settings = static::getDefaultNotifications(); |
| 24 | 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 | + | |
| 25 | 38 | $config = Arr::get(static::cachedSettings(), 'notification_config', []); |
| 26 | 39 | |
| 27 | 40 | foreach ($settings as $key => &$setting) { |
| 28 | 41 | $setting['name'] = $key; |
| @@ -635,10 +648,123 @@ | ||
| 635 | 648 | 'is_default_body' => 'yes', |
| 636 | 649 | 'email_body' => '', |
| 637 | 650 | ] |
| 638 | 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 | + ], | |
| 639 | 720 | ]; |
| 640 | 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; | |
| 641 | 767 | } |
| 642 | 768 | |
| 643 | 769 | public static function getNotificationsOfEvent($event, $viewData): array |
| 644 | 770 | { |