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/EmailPreviewService.php +153 -8 1.3.21 → 1.7.0 View file →
@@ -1,8 +1,11 @@
1 1 <?php
2 2
3 3 namespace FluentCart\App\Services\Email;
4 4
5 +use FluentCart\App\Helpers\Status;
6 +use FluentCart\App\Models\Product;
7 +use FluentCart\App\Models\ProductReview;
5 8 use FluentCart\App\Services\DateTime\DateTime;
6 9 use FluentCart\Framework\Support\Collection;
7 10 use FluentCart\Framework\Support\Str;
8 11
@@ -30,8 +33,34 @@
30 33 'trial_end_date' => DateTime::gmtNow()->addDays(3)->format('Y-m-d H:i:s'),
31 34 ],
32 35 ];
33 36
37 + if (Str::startsWith($template, 'order.reminder.renewal_overdue.')) {
38 + if (Str::contains($template, '.final.')) {
39 + $previewData['reminder']['stage'] = 'overdue_7';
40 + } elseif (Str::contains($template, '.followup.')) {
41 + $previewData['reminder']['stage'] = 'overdue_3';
42 + } else {
43 + $previewData['reminder']['stage'] = 'overdue_1';
44 + }
45 + }
46 +
47 + // The review templates read a review, not an order. Without one the
48 + // preview of any of them fatals on the first property it touches.
49 + if (Str::startsWith($template, 'review.')) {
50 + $previewData['product'] = $this->getPreviewProduct();
51 + $previewData['review'] = $this->getDummyReview((int) $previewData['product']->ID);
52 +
53 + if (Str::startsWith($template, 'review.replied.')) {
54 + $previewData['reply'] = $this->getDummyReply((int) $previewData['product']->ID);
55 + }
56 + // A review is not about an order, and a real send carries none —
57 + // the order header (date, number) renders only when one is there.
58 + // Left in, the preview would show a header the email never has.
59 + $previewData['order'] = null;
60 + $previewData['transaction'] = null;
61 + }
62 +
34 63 if (Str::startsWith($template, 'subscription.')) {
35 64 $previewData['transaction'] = $subscription->getLatestTransaction();
36 65
37 66 if (Str::contains($template, 'trial_end')) {
@@ -37,8 +66,12 @@
37 66 if (Str::contains($template, 'trial_end')) {
38 67 $previewData['reminder']['stage'] = 'trial_end_3';
39 68 }
40 69
70 + if (Str::contains($template, 'renewal_failed')) {
71 + $previewData['error'] = __('Your card was declined.', 'fluent-cart');
72 + }
73 +
41 74 if (Str::contains($template, 'canceled')) {
42 75 $previewData['reason'] = __('canceled on customer request', 'fluent-cart');
43 76 $previewData['subscription'] = $this->getDummySubscription($previewData['order']);
44 77 }
@@ -46,8 +79,69 @@
46 79
47 80 return $previewData;
48 81 }
49 82
83 + /**
84 + * A stand-in review with every field the review templates and the
85 + * {{review.*}} codes read. A real model, not a stdClass: the templates
86 + * call attachItemLabels() on it and the parser type-checks for one.
87 + */
88 + /**
89 + * The product a review preview is about: a real published one when the
90 + * store has any, so the preview links where the email will — the
91 + * "See your review" button needs a page to point at, and a placeholder
92 + * has none. An empty store previews without the button, as the email
93 + * would for a product since deleted.
94 + */
95 + private function getPreviewProduct(): object
96 + {
97 + $product = Product::query()
98 + ->where('post_status', 'publish')
99 + ->orderBy('ID', 'DESC')
100 + ->first();
101 +
102 + if ($product) {
103 + return $product;
104 + }
105 +
106 + return (object) [
107 + 'ID' => 0,
108 + 'post_title' => 'Sample Product',
109 + ];
110 + }
111 +
112 + private function getDummyReview(int $postId = 0): ProductReview
113 + {
114 + $review = new ProductReview([
115 + 'post_id' => $postId,
116 + 'reviewer_name' => 'John Doe',
117 + 'reviewer_email' => '[email protected]',
118 + 'title' => 'Exactly what I was looking for',
119 + 'review' => 'Arrived quickly and works just as described. Would happily buy again.',
120 + 'rating' => 5,
121 + ]);
122 + $review->id = 0;
123 + $review->status = Status::REVIEW_APPROVED;
124 +
125 + return $review;
126 + }
127 +
128 + private function getDummyReply(int $postId = 0): ProductReview
129 + {
130 + $reply = new ProductReview([
131 + 'post_id' => $postId,
132 + 'parent_id' => 0,
133 + 'reviewer_name' => 'The Store Team',
134 + 'reviewer_email' => '[email protected]',
135 + 'review' => 'Thank you so much for the kind words — we are thrilled it worked out. Enjoy!',
136 + ]);
137 + $reply->id = 0;
138 + $reply->status = Status::REVIEW_APPROVED;
139 + $reply->is_admin_reply = 1;
140 +
141 + return $reply;
142 + }
143 +
50 144 private function getDummyCustomer(): object
51 145 {
52 146 return (object) [
53 147 'full_name' => 'John Doe',
@@ -59,13 +153,18 @@
59 153
60 154 private function getDummyTransaction(): object
61 155 {
62 156 return new class {
63 - public $created_at = '';
157 + public $created_at;
64 158 public $total = 4900;
65 159 public $vendor_charge_id = 'ch_preview_123';
66 160 public $order = null;
67 161
162 + public function __construct()
163 + {
164 + $this->created_at = DateTime::gmtNow();
165 + }
166 +
68 167 public function getPaymentMethodText()
69 168 {
70 169 return 'card';
71 170 }
@@ -76,9 +175,9 @@
76 175 {
77 176 $customer = $this->getDummyCustomer();
78 177 $transaction = $this->getDummyTransaction();
79 178
80 - return new class($customer, $transaction, new Collection()) {
179 + $order = new class($customer, $transaction, new Collection()) {
81 180 public $id = 1001;
82 181 public $uuid = 'preview-order-uuid';
83 182 public $invoice_no = 'FC-1001';
84 183 public $customer;
@@ -98,8 +197,9 @@
98 197 public $total_amount = 4900;
99 198 public $total_paid = 0;
100 199 public $total_refund = 0;
101 200 public $payment_status = 'pending';
201 + public $config = [];
102 202 private $latestTransaction;
103 203
104 204 public function __construct($customer, $transaction, $emptyCollection)
105 205 {
@@ -106,14 +206,28 @@
106 206 $this->customer = $customer;
107 207 $this->latestTransaction = $transaction;
108 208 $this->order_items = new Collection([
109 209 [
110 - 'post_title' => 'Sample Product',
111 - 'quantity' => 1,
112 - 'title' => 'Standard Plan',
113 - 'payment_type' => 'onetime',
114 - 'payment_info' => '',
115 - 'formatted_total' => '$49.00',
210 + 'post_title' => 'Sample Product',
211 + 'quantity' => 1,
212 + 'title' => 'Standard Plan',
213 + 'payment_type' => 'onetime',
214 + 'payment_info' => '',
215 + 'formatted_total' => '$49.00',
216 + 'package_info' => __('Package: Medium Box · 30 × 20 × 15 cm · Wt: 2 kg · Shipping wt: 2.5 kg', 'fluent-cart'),
217 + 'other_info' => [
218 + 'package_name' => 'Medium Box',
219 + 'package_type' => 'box',
220 + 'package_length' => 30,
221 + 'package_width' => 20,
222 + 'package_height' => 15,
223 + 'package_dimension_unit' => 'cm',
224 + 'weight' => 2,
225 + 'weight_unit' => 'kg',
226 + 'package_weight' => 0.5,
227 + 'package_weight_unit' => 'kg',
228 + 'package_slug' => 'medium-box',
229 + ],
116 230 ],
117 231 ]);
118 232 $this->subscriptions = $emptyCollection;
119 233 $this->orderTaxRates = $emptyCollection;
@@ -125,8 +239,26 @@
125 239 {
126 240 return '#';
127 241 }
128 242
243 + public function getMeta($metaKey, $defaultValue = false)
244 + {
245 + if ($metaKey === 'due_date') {
246 + return gmdate('Y-m-d H:i:s', strtotime('+7 days'));
247 + }
248 + return $defaultValue;
249 + }
250 +
251 + public function isReverseChargeTaxOrder()
252 + {
253 + return false;
254 + }
255 +
256 + public function getOrderRcMode()
257 + {
258 + return 'fixed';
259 + }
260 +
129 261 public function getLatestTransaction()
130 262 {
131 263 return $this->latestTransaction;
132 264 }
@@ -140,8 +272,14 @@
140 272 {
141 273 return new Collection([]);
142 274 }
143 275 };
276 +
277 + // Templates reach the order through the transaction too (e.g. the
278 + // renewal admin email's $transaction->order->getViewUrl('admin')).
279 + $transaction->order = $order;
280 +
281 + return $order;
144 282 }
145 283
146 284 private function getDummySubscription($order): object
147 285 {
@@ -151,8 +289,9 @@
151 289 return new class($customer, $transaction, $order) {
152 290 public $id = 2001;
153 291 public $uuid = 'preview-subscription-uuid';
154 292 public $item_name = 'Sample Subscription';
293 + public $display_item_name = 'Sample Subscription';
155 294 public $billing_interval = 'yearly';
156 295 public $next_billing_date;
157 296 public $recurring_total = 4900;
158 297 public $status = 'active';
@@ -165,8 +304,9 @@
165 304 {
166 305 $this->customer = $customer;
167 306 $this->latestTransaction = $transaction;
168 307 $this->order = $order;
308 + $transaction->order = $order;
169 309 $this->next_billing_date = \FluentCart\App\Services\DateTime\DateTime::gmtNow()->addDays(30)->format('Y-m-d H:i:s');
170 310 $this->trial_ends_at = \FluentCart\App\Services\DateTime\DateTime::gmtNow()->addDays(3)->format('Y-m-d H:i:s');
171 311 }
172 312
@@ -182,8 +322,13 @@
182 322
183 323 public function getReactivateUrl()
184 324 {
185 325 return '#';
326 + }
327 +
328 + public function getPaymentMethodText()
329 + {
330 + return 'Visa ***4242';
186 331 }
187 332 };
188 333 }
189 334 }