| @@ -30,14 +30,21 @@ | ||
| 30 | 30 | ); |
| 31 | 31 | |
| 32 | 32 | }, 999, 1); |
| 33 | 33 | // To Admin |
| 34 | + // 999 like the rest of this file: custom smartcodes in a customised admin | |
| 35 | + // body resolve against the payload as it stands when the mail is built, so | |
| 36 | + // the mail has to run after everything else bound here writes its data — | |
| 37 | + // integration feeds (11), FluentCRM (20), affiliate referral status (99). | |
| 38 | + // Cost of running last: this hook forces every feed to run realtime, so the | |
| 39 | + // mail waits on their outbound HTTP, and IntegrationEventListener catches | |
| 40 | + // only \Exception — a \Error in a feed loses the mail. | |
| 34 | 41 | add_action('fluent_cart/order_paid_done', function ($data) { |
| 35 | 42 | $this->mailEmailsOfEvent( |
| 36 | 43 | 'order_paid_done', |
| 37 | 44 | $data |
| 38 | 45 | ); |
| 39 | - }, 10, 1); | |
| 46 | + }, 999, 1); | |
| 40 | 47 | |
| 41 | 48 | // to customer and admin |
| 42 | 49 | add_action('fluent_cart/subscription_renewed', function ($data) { |
| 43 | 50 | $this->mailEmailsOfEvent( |
| @@ -46,8 +53,16 @@ | ||
| 46 | 53 | ); |
| 47 | 54 | }, 999, 1); |
| 48 | 55 | |
| 49 | 56 | // to customer and admin |
| 57 | + add_action('fluent_cart/subscription_renewal_failed', function ($data) { | |
| 58 | + $this->mailEmailsOfEvent( | |
| 59 | + 'subscription_renewal_failed', | |
| 60 | + $data | |
| 61 | + ); | |
| 62 | + }, 999, 1); | |
| 63 | + | |
| 64 | + // to customer and admin | |
| 50 | 65 | add_action('fluent_cart/subscription_canceled', function ($data) { |
| 51 | 66 | $this->mailEmailsOfEvent( |
| 52 | 67 | 'subscription_canceled', |
| 53 | 68 | $data |
| @@ -102,11 +117,30 @@ | ||
| 102 | 117 | $this->mailEmailsOfEvent('renewal_reminder_due', $data); |
| 103 | 118 | }, 999, 1); |
| 104 | 119 | |
| 105 | 120 | add_action('fluent_cart/renewal_reminder_overdue', function ($data) { |
| 121 | + // Set only by the deprecated bridge in RenewalReminderService::send(): | |
| 122 | + // the staged email (renewal_overdue_first/followup/final) already went | |
| 123 | + // out for this payload, so mailing here would duplicate it. Direct | |
| 124 | + // dispatches of this hook never carry the flag and still deliver. | |
| 125 | + if (!empty(Arr::get($data, 'staged_email_dispatched'))) { | |
| 126 | + return; | |
| 127 | + } | |
| 106 | 128 | $this->mailEmailsOfEvent('renewal_reminder_overdue', $data); |
| 107 | 129 | }, 999, 1); |
| 108 | 130 | |
| 131 | + add_action('fluent_cart/renewal_overdue_first', function ($data) { | |
| 132 | + $this->mailEmailsOfEvent('renewal_overdue_first', $data); | |
| 133 | + }, 999, 1); | |
| 134 | + | |
| 135 | + add_action('fluent_cart/renewal_overdue_followup', function ($data) { | |
| 136 | + $this->mailEmailsOfEvent('renewal_overdue_followup', $data); | |
| 137 | + }, 999, 1); | |
| 138 | + | |
| 139 | + add_action('fluent_cart/renewal_overdue_final', function ($data) { | |
| 140 | + $this->mailEmailsOfEvent('renewal_overdue_final', $data); | |
| 141 | + }, 999, 1); | |
| 142 | + | |
| 109 | 143 | add_action('fluent_cart/subscription_renewal_reminder', function ($data) { |
| 110 | 144 | $this->mailEmailsOfEvent('subscription_renewal_reminder', $data); |
| 111 | 145 | }, 999, 1); |
| 112 | 146 | |
| @@ -222,8 +256,16 @@ | ||
| 222 | 256 | $mailer->addAttachment($pdfPath); |
| 223 | 257 | } |
| 224 | 258 | } |
| 225 | 259 | |
| 260 | + $mailer = $this->applyMailerFilter($mailer, [ | |
| 261 | + 'event' => $event, | |
| 262 | + 'mail_name' => $mailName, | |
| 263 | + 'recipient' => Arr::get($notification, 'recipient'), | |
| 264 | + 'notification' => $notification, | |
| 265 | + 'data' => $data, | |
| 266 | + ]); | |
| 267 | + | |
| 226 | 268 | $mailer->send(true); |
| 227 | 269 | |
| 228 | 270 | // Clean up temp PDF file after sending |
| 229 | 271 | if ($pdfPath && file_exists($pdfPath)) { |
| @@ -258,13 +300,36 @@ | ||
| 258 | 300 | $mailer->addAttachment($pdfPath); |
| 259 | 301 | } |
| 260 | 302 | } |
| 261 | 303 | |
| 304 | + $mailer = $this->applyMailerFilter($mailer, [ | |
| 305 | + 'event' => Arr::get($notification, 'event', ''), | |
| 306 | + 'mail_name' => $emailName, | |
| 307 | + 'recipient' => Arr::get($notification, 'recipient'), | |
| 308 | + 'notification' => $notification, | |
| 309 | + 'data' => $data, | |
| 310 | + ]); | |
| 311 | + | |
| 262 | 312 | $mailer->send(true); |
| 263 | 313 | |
| 264 | 314 | if ($pdfPath && file_exists($pdfPath)) { |
| 265 | 315 | @unlink($pdfPath); |
| 266 | 316 | } |
| 317 | + } | |
| 318 | + | |
| 319 | + /** | |
| 320 | + * Let third-party code adjust the fully prepared Mailer (recipients, | |
| 321 | + * subject, body, attachments already set) immediately before it sends. | |
| 322 | + * | |
| 323 | + * @param Mailer $mailer | |
| 324 | + * @param array $context event, mail_name, recipient, notification, data | |
| 325 | + * @return Mailer | |
| 326 | + */ | |
| 327 | + private function applyMailerFilter(Mailer $mailer, array $context): Mailer | |
| 328 | + { | |
| 329 | + $filtered = apply_filters('fluent_cart/email_notification/mailer', $mailer, $context); | |
| 330 | + | |
| 331 | + return $filtered instanceof Mailer ? $filtered : $mailer; | |
| 267 | 332 | } |
| 268 | 333 | |
| 269 | 334 | public function getEmailFooter(): string |
| 270 | 335 | { |