PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.23
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.23
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 1.3.19 trunk All 48 releases
fluent-cart / app / Services / Email / EmailNotificationMailer.php

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

369 lines 13.1 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\App\App;
6 use FluentCart\App\Models\Model;
7 use FluentCart\App\Models\Order;
8 use FluentCart\App\Models\Subscription;
9 use FluentCart\App\Services\OrderService;
10 use FluentCart\App\Services\ShortCodeParser\ShortcodeTemplateBuilder;
11 use FluentCart\Framework\Support\Arr;
12
13 class EmailNotificationMailer
14 {
15 public function register()
16 {
17 // $this->registerAsyncMails();
18 add_action('fluent_cart/order_placed_offline', function ($data) {
19 $this->mailEmailsOfEvent(
20 'order_placed_offline',
21 $data
22 );
23
24 }, 999, 1);
25 // To Customer
26 add_action('fluent_cart/order_paid', function ($data) {
27 $this->mailEmailsOfEvent(
28 'order_paid',
29 $data
30 );
31
32 }, 999, 1);
33 // To Admin
34 add_action('fluent_cart/order_paid_done', function ($data) {
35 $this->mailEmailsOfEvent(
36 'order_paid_done',
37 $data
38 );
39 }, 10, 1);
40
41 // to customer and admin
42 add_action('fluent_cart/subscription_renewed', function ($data) {
43 $this->mailEmailsOfEvent(
44 'subscription_renewed',
45 $data
46 );
47 }, 999, 1);
48
49 // to customer and admin
50 add_action('fluent_cart/subscription_canceled', function ($data) {
51 $this->mailEmailsOfEvent(
52 'subscription_canceled',
53 $data
54 );
55 }, 999, 1);
56
57 add_action('fluent_cart/order_refunded', function ($data) {
58 $this->mailEmailsOfEvent('order_refunded', $data);
59 }, 999, 1);
60
61 add_action('fluent_cart/shipping_status_changed_to_shipped', function ($data) {
62 $this->mailEmailsOfEvent('shipping_status_changed_to_shipped', $data);
63 }, 999, 1);
64
65 add_action('fluent_cart/shipping_status_changed_to_delivered', function ($data) {
66 $this->mailEmailsOfEvent('shipping_status_changed_to_delivered', $data);
67 }, 999, 1);
68
69 // @todo uncomment when invoice feature is deployed
70 // add_action('fluent_cart/invoice_reminder_due', function ($data) {
71 // $this->mailEmailsOfEvent('invoice_reminder_due', $data);
72 // }, 999, 1);
73
74 add_action('fluent_cart/invoice_reminder_overdue', function ($data) {
75 $this->mailEmailsOfEvent('invoice_reminder_overdue', $data);
76 }, 999, 1);
77
78 add_action('fluent_cart/subscription_renewal_reminder', function ($data) {
79 $this->mailEmailsOfEvent('subscription_renewal_reminder', $data);
80 }, 999, 1);
81
82 add_action('fluent_cart/subscription_trial_end_reminder', function ($data) {
83 $this->mailEmailsOfEvent('subscription_trial_end_reminder', $data);
84 }, 999, 1);
85
86 }
87
88 public function registerAsyncMails()
89 {
90 //For Async Actions
91 add_action('fluent_cart/async_mail/order_created', function ($orderId, $mailName = '') {
92 (new static())->sendAsyncOrderMail($mailName, $orderId);
93 }, 10, 2);
94
95 add_action('fluent_cart/async_mail/order_placed_offline', function ($orderId, $mailName = '') {
96 (new static())->sendAsyncOrderMail($mailName, $orderId);
97 }, 10, 2);
98
99 add_action('fluent_cart/async_mail/order_paid', function ($orderId, $mailName = '') {
100 (new static())->sendAsyncOrderMail($mailName, $orderId);
101 }, 10, 2);
102
103 add_action('fluent_cart/async_mail/order_updated', function ($orderId, $mailName = '') {
104 (new static())->sendAsyncOrderMail($mailName, $orderId);
105 }, 10, 2);
106
107 add_action('fluent_cart/async_mail/order_refunded', function ($orderId, $mailName = '') {
108 (new static())->sendAsyncOrderMail($mailName, $orderId);
109 }, 10, 2);
110
111 add_action('fluent_cart/async_mail/subscription_activated', function ($subscriptionId, $mailName = '') {
112 (new static())->sendAsyncSubscriptionMail($mailName, $subscriptionId);
113 }, 10, 2);
114
115 add_action('fluent_cart/async_mail/subscription_renewed', function ($subscriptionId, $mailName = '') {
116 (new static())->sendAsyncSubscriptionMail($mailName, $subscriptionId);
117 }, 10, 2);
118
119 add_action('fluent_cart/async_mail/subscription_eot', function ($subscriptionId, $mailName = '') {
120 (new static())->sendAsyncSubscriptionMail($mailName, $subscriptionId);
121 }, 10, 2);
122
123 add_action('fluent_cart/async_mail/subscription_canceled', function ($subscriptionId, $mailName = '') {
124 (new static())->sendAsyncSubscriptionMail($mailName, $subscriptionId);
125 }, 10, 2);
126
127 add_action('fluent_cart/async_mail/subscription_expired', function ($subscriptionId, $mailName = '') {
128 (new static())->sendAsyncSubscriptionMail($mailName, $subscriptionId);
129 }, 10, 2);
130
131 }
132
133
134 public function formatParsable($parsable)
135 {
136 foreach ($parsable as &$item) {
137 if ($item instanceof Model) {
138 $item = $item->toArray();
139 }
140 }
141
142 if (!Arr::has($parsable, 'order.customer')) {
143 $parsable['order']['customer'] = Arr::get(
144 $parsable, 'customer', []
145 );
146 }
147
148 return $parsable;
149 }
150
151 public function mailEmailsOfEvent($event, $data, $asyncHook = '', $asyncData = [])
152 {
153 $parsedData = $this->formatParsable($data);
154
155 $order = Arr::get($data, 'order');
156
157 // Pass original Model data for template rendering (templates use $order->method())
158 $notifications = EmailNotifications::getNotificationsOfEvent($event, $data);
159
160 foreach ($notifications as $mailName => $notification) {
161 if ($order instanceof Order && !apply_filters('fluent_cart/should_send_email_notification', true, [
162 'event' => $event,
163 'mail_name' => $mailName,
164 'order' => $order,
165 ])) {
166 continue;
167 }
168
169 $isAsync = Arr::get($notification, 'is_async', false);
170 if ($isAsync && !empty($asyncHook)) {
171 $asyncData['mailName'] = $mailName;
172 as_enqueue_async_action($asyncHook, $asyncData);
173 } else {
174 list($body, $subject, $to) = $this->parseEmailContent($notification, $data);
175
176 $mailer = Mailer::make()->to($to)->subject($subject)->body($body);
177
178 $pdfPath = null;
179 $templateId = Arr::get($notification, 'settings.attach_pdf_template', '');
180 // Backward compat: old attach_pdf='yes' maps to order_receipt
181 if (empty($templateId) && Arr::get($notification, 'settings.attach_pdf', 'no') === 'yes') {
182 $templateId = 'order_receipt';
183 }
184 if (!empty($templateId) && defined('FLUENT_PDF')) {
185 $pdfPath = $this->generateOrderPdf($data, $templateId);
186 if ($pdfPath) {
187 $mailer->addAttachment($pdfPath);
188 }
189 }
190
191 $mailer->send(true);
192
193 // Clean up temp PDF file after sending
194 if ($pdfPath && file_exists($pdfPath)) {
195 @unlink($pdfPath);
196 }
197 }
198 }
199
200 }
201
202 public function mailByEmailName($emailName, $data)
203 {
204 // Extract Order model before formatParsable converts it to array
205 $orderModel = Arr::get($data, 'order');
206
207 $data = $this->formatParsable($data);
208 $notification = EmailNotifications::getNotification($emailName);
209 $notification = EmailNotifications::formatNotification($notification, $data);
210 list($body, $subject, $to) = $this->parseEmailContent($notification, $data);
211
212 $mailer = Mailer::make()->to($to)->subject($subject)->body($body);
213
214 $pdfPath = null;
215 $templateId = Arr::get($notification, 'settings.attach_pdf_template', '');
216 if (empty($templateId) && Arr::get($notification, 'settings.attach_pdf', 'no') === 'yes') {
217 $templateId = 'order_receipt';
218 }
219 if (!empty($templateId) && defined('FLUENT_PDF')) {
220 $pdfPath = $this->generateOrderPdf(['order' => $orderModel], $templateId);
221 if ($pdfPath) {
222 $mailer->addAttachment($pdfPath);
223 }
224 }
225
226 $mailer->send(true);
227
228 if ($pdfPath && file_exists($pdfPath)) {
229 @unlink($pdfPath);
230 }
231 }
232
233 public function getEmailFooter(): string
234 {
235
236 $footer = "";
237 $settings = EmailNotifications::getSettings();
238 $emailFooter = Arr::get($settings, 'email_footer', '');
239 if (!empty($emailFooter)) {
240 $footer .= ShortcodeTemplateBuilder::make($emailFooter, []);
241 }
242 $isEmailFooter = EmailNotifications::getSettings('show_email_footer');
243 if (!App::isProActive() || $isEmailFooter === 'yes') {
244 $cartFooter = "<div style='padding: 15px; text-align: center; font-size: 16px; color: #2F3448;'>Powered by <a href='https://fluentcart.com' style='color: #017EF3; text-decoration: none;'>FluentCart</a></div>";
245 $footer .= $cartFooter;
246 }
247 return $footer;
248
249 }
250
251 public function parseEmailContent($notification, $data, $templateData = null): array
252 {
253 $templateData = $templateData ?: $data;
254
255 $rawBody = Arr::get($notification, 'body', '');
256 $isCustom = (bool) Arr::get($notification, 'is_custom', false);
257
258 // Let pro parse block content; returns empty string if no pro
259 $parsedBody = apply_filters('fluent_cart/parse_email_block_content', '', $rawBody, $data);
260
261 $body = '';
262
263 // Custom block emails use block_editor_template (pro);
264 // default templates use general_template with legacy order_header.
265 if ($isCustom && $parsedBody) {
266 $body = apply_filters('fluent_cart/render_block_email_template', '', [
267 'emailBody' => $parsedBody,
268 'preheader' => Arr::get($notification, 'pre_header', ''),
269 'emailFooter' => $this->getEmailFooter(),
270 ]);
271 }
272
273 if (empty($body)) {
274 $header = App::make('view')->make('emails.parts.order_header', $data);
275 $body = (string)App::make('view')->make('emails.general_template', [
276 'emailBody' => $rawBody,
277 'preheader' => Arr::get($notification, 'pre_header', ''),
278 'header' => $header,
279 'emailFooter' => $this->getEmailFooter(),
280 ]);
281 }
282
283 $body = ShortcodeTemplateBuilder::make($body, $data);
284
285 $subject = ShortcodeTemplateBuilder::make(Arr::get($notification, 'subject', ''), $data);
286 $to = Arr::get($notification, 'to', '');
287 $to = ShortcodeTemplateBuilder::make($to, $data);
288
289 return [
290 0 => $body,
291 1 => $subject,
292 2 => $to
293 ];
294 }
295
296 public function sendAsyncOrderMail($emailName, $orderId)
297 {
298 $order = Order::query()->with(['customer', 'shipping_address', 'billing_address', 'transactions'])->find($orderId);
299
300 if ($order) {
301 $transaction = [];
302 if (!empty($order->transactions)) {
303 $transaction = $order->transactions->first();
304 }
305 $this->mailByEmailName($emailName, [
306 'order' => $order,
307 'customer' => $order->customer !== null ? $order->customer : [],
308 'transaction' => $transaction
309 ]);
310 }
311 }
312
313 public function sendAsyncSubscriptionMail($emailName, $subscriptionId)
314 {
315 $subscription = Subscription::query()->with([
316 'customer',
317 'transactions',
318 'order'
319 ])->find($subscriptionId);
320
321 if ($subscription) {
322 $order = $subscription->order;
323 if (!$order) {
324 return;
325 }
326 $this->mailByEmailName($emailName, [
327 'subscription' => $subscription,
328 'order' => $order,
329 'customer' => $subscription->customer !== null ? $subscription->customer : [],
330 'transactions' => $subscription->transactions
331 ]);
332 }
333
334 }
335
336 /**
337 * Generate a PDF receipt for the order in the given data array.
338 *
339 * @param array $data Event data containing 'order' key
340 * @return string|null Path to generated PDF or null
341 */
342 private function generateOrderPdf(array $data, string $templateId = 'order_receipt'): ?string
343 {
344 if (!OrderService::canGenerateReceiptPdf()) {
345 return null;
346 }
347
348 $order = Arr::get($data, 'order');
349 if (!$order || !($order instanceof Order)) {
350 return null;
351 }
352
353 try {
354 return apply_filters('fluent_cart/pdf/generate_receipt', null, [
355 'order' => $order,
356 'template_id' => $templateId,
357 ]);
358 } catch (\Throwable $e) {
359 fluent_cart_add_log(
360 'PDF Generation Failed',
361 $e->getMessage(),
362 'error'
363 );
364 return null;
365 }
366 }
367
368 }
369