1: <?php
2: namespace Opencart\Catalog\Controller\Mail;
3: /**
4: * Class Subscription
5: *
6: * @package Opencart\Catalog\Controller\Mail
7: */
8: class Subscription extends \Opencart\System\Engine\Controller {
9: /**
10: * @param string $route
11: * @param array<int, mixed> $args
12: * @param array<mixed> $output
13: *
14: * addHistory
15: *
16: * @return void
17: */
18: public function index(string &$route, array &$args, &$output): void {
19: if (isset($args[0])) {
20: $subscription_id = $args[0];
21: } else {
22: $subscription_id = 0;
23: }
24:
25: if (isset($args[1]['subscription'])) {
26: $subscription = $args[1]['subscription'];
27: } else {
28: $subscription = [];
29: }
30:
31: if (isset($args[2])) {
32: $comment = $args[2];
33: } else {
34: $comment = '';
35: }
36:
37: if (isset($args[3])) {
38: $notify = $args[3];
39: } else {
40: $notify = '';
41: }
42: /*
43: $subscription['order_product_id']
44: $subscription['customer_id']
45: $subscription['order_id']
46: $subscription['subscription_plan_id']
47: $subscription['customer_payment_id'],
48: $subscription['name']
49: $subscription['description']
50: $subscription['trial_price']
51: $subscription['trial_frequency']
52: $subscription['trial_cycle']
53: $subscription['trial_duration']
54: $subscription['trial_remaining']
55: $subscription['trial_status']
56: $subscription['price']
57: $subscription['frequency']
58: $subscription['cycle']
59: $subscription['duration']
60: $subscription['remaining']
61: $subscription['date_next']
62: $subscription['status']
63:
64:
65: if ($subscription['trial_duration'] && $subscription['trial_remaining']) {
66: $date_next = date('Y-m-d', strtotime('+' . $subscription['trial_cycle'] . ' ' . $subscription['trial_frequency']));
67: } elseif ($subscription['duration'] && $subscription['remaining']) {
68: $date_next = date('Y-m-d', strtotime('+' . $subscription['cycle'] . ' ' . $subscription['frequency']));
69: }
70:
71: // Subscription
72: $this->load->model('account/subscription');
73:
74: $filter_data = [
75: 'filter_subscription_id' => $subscription_id,
76: 'filter_date_next' => $date_next,
77: 'filter_subscription_status_id' => $this->config->get('config_subscription_active_status_id'),
78: 'start' => 0,
79: 'limit' => 1
80: ];
81:
82: $subscriptions = $this->model_account_subscription->getSubscriptions($filter_data);
83:
84: if ($subscriptions) {
85: $this->load->language('mail/subscription');
86:
87: foreach ($subscriptions as $value) {
88: // Only match the latest order ID of the same customer ID
89: // since new subscriptions cannot be re-added with the same
90: // order ID; only as a new order ID added by an extension
91: if ($value['customer_id'] == $subscription['customer_id'] && $value['order_id'] == $subscription['order_id']) {
92: // Payment Methods
93: $this->load->model('sale/subscription');
94:
95: $payment_method = $this->model_sale_subscription->getTotalSubscriptions(['filter_customer_id' => $value['customer_id']]);
96:
97: if ($payment_method) {
98: // Subscription
99: $this->load->model('checkout/subscription');
100:
101: $subscription_order_product = $this->model_checkout_subscription->getSubscriptionByOrderProductId($value['order_product_id']);
102:
103: if ($subscription_order_product) {
104: // Orders
105: $this->load->model('account/order');
106:
107: // Order Products
108: $order_product = $this->model_account_order->getProduct($value['order_id'], $value['order_product_id']);
109:
110: if ($order_product && $order_product['order_product_id'] == $subscription['order_product_id']) {
111: $products = $this->cart->getProducts();
112:
113: $description = '';
114:
115: foreach ($products as $product) {
116: if ($product['product_id'] == $order_product['product_id']) {
117:
118:
119: if ($product['subscription']['trial_status']) {
120: $trial_price = $this->currency->format($this->tax->calculate($value['trial_price'], $product['tax_class_id'], $this->config->get('config_tax')), $this->config->get('config_currency'));
121: $trial_cycle = $value['trial_cycle'];
122: $trial_frequency = $this->language->get('text_' . $value['trial_frequency']);
123: $trial_duration = $value['trial_duration'];
124:
125: $description .= sprintf($this->language->get('text_subscription_trial'), $trial_price, $trial_cycle, $trial_frequency, $trial_duration);
126: }
127:
128: $price = $this->currency->format($this->tax->calculate($value['price'], $product['tax_class_id'], $this->config->get('config_tax')), $this->config->get('config_currency'));
129: $cycle = $value['cycle'];
130: $frequency = $this->language->get('text_' . $value['frequency']);
131: $duration = $value['duration'];
132:
133: if ($duration) {
134: $description .= sprintf($this->language->get('text_subscription_duration'), $price, $cycle, $frequency, $duration);
135: } else {
136: $description .= sprintf($this->language->get('text_subscription_cancel'), $price, $cycle, $frequency);
137: }
138: }
139: }
140:
141:
142: // Orders
143: $this->load->model('checkout/order');
144:
145: $order_info = $this->model_checkout_order->getOrder($value['order_id']);
146:
147: if ($order_info) {
148: // Stores
149: $this->load->model('setting/store');
150:
151: // Settings
152: $this->load->model('setting/setting');
153:
154: $store_info = $this->model_setting_store->getStore($order_info['store_id']);
155:
156: if ($store_info) {
157: $store_logo = html_entity_decode($this->model_setting_setting->getValue('config_logo', $store_info['store_id']), ENT_QUOTES, 'UTF-8');
158: $store_name = html_entity_decode($store_info['name'], ENT_QUOTES, 'UTF-8');
159:
160: $store_url = $store_info['url'];
161: } else {
162: $store_logo = html_entity_decode($this->config->get('config_logo'), ENT_QUOTES, 'UTF-8');
163: $store_name = html_entity_decode($this->config->get('config_name'), ENT_QUOTES, 'UTF-8');
164:
165: $store_url = HTTP_SERVER;
166: }
167:
168: // Subscription Status
169: $subscription_status_query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "subscription_status` WHERE `subscription_status_id` = '" . (int)$value['subscription_status_id'] . "' AND `language_id` = '" . (int)$order_info['language_id'] . "'");
170:
171: if ($subscription_status_query->num_rows) {
172: $data['order_status'] = $subscription_status_query->row['name'];
173: } else {
174: $data['order_status'] = '';
175: }
176:
177: // Languages
178: $this->load->model('localisation/language');
179:
180: $language_info = $this->model_localisation_language->getLanguage($order_info['language_id']);
181:
182: // We need to compare both language IDs as they both need to match.
183: if ($language_info) {
184: $language_code = $language_info['code'];
185: } else {
186: $language_code = $this->config->get('config_language');
187: }
188:
189: // Load the language for any mails using a different country code and prefixing it so it does not pollute the main data pool.
190: $this->load->language('default', 'mail', $language_code);
191: $this->load->language('mail/order_add', 'mail', $language_code);
192:
193: // Add language vars to the template folder
194: $results = $this->language->all('mail');
195:
196: foreach ($results as $key => $value) {
197: $data[$key] = $value;
198: }
199:
200: $subject = sprintf($this->language->get('mail_text_subject'), $store_name, $order_info['order_id']);
201:
202: // Image files
203: $this->load->model('tool/image');
204:
205: if (is_file(DIR_IMAGE . $store_logo)) {
206: $data['logo'] = $store_url . 'image/' . $store_logo;
207: } else {
208: $data['logo'] = '';
209: }
210:
211: $data['title'] = sprintf($this->language->get('mail_text_subject'), $store_name, $order_info['order_id']);
212:
213: $data['text_greeting'] = sprintf($this->language->get('mail_text_greeting'), $order_info['store_name']);
214:
215: $data['store'] = $store_name;
216: $data['store_url'] = $order_info['store_url'];
217:
218: $data['customer_id'] = $order_info['customer_id'];
219: $data['link'] = $order_info['store_url'] . 'index.php?route=account/subscription.info&subscription_id=' . $subscription_id;
220:
221: $data['order_id'] = $order_info['order_id'];
222: $data['date_added'] = date($this->language->get('date_format_short'), strtotime($value['date_added']));
223: $data['payment_method'] = $order_info['payment_method'];
224: $data['email'] = $order_info['email'];
225: $data['telephone'] = $order_info['telephone'];
226: $data['ip'] = $order_info['ip'];
227:
228: // Order Totals
229: $data['totals'] = [];
230:
231: $order_totals = $this->model_checkout_order->getTotals($subscription['order_id']);
232:
233: foreach ($order_totals as $order_total) {
234: $data['totals'][] = [
235: 'title' => $order_total['title'],
236: 'text' => $this->currency->format($order_total['value'], $order_info['currency_code'], $order_info['currency_value']),
237: ];
238: }
239:
240: // Subscription
241: if ($comment && $notify) {
242: $data['comment'] = nl2br($comment);
243: } else {
244: $data['comment'] = '';
245: }
246:
247: $data['description'] = $value['description'];
248:
249: // Products
250: $data['name'] = $order_product['name'];
251: $data['quantity'] = $order_product['quantity'];
252: $data['price'] = $this->currency->format($order_product['price'], $order_info['currency_code'], $order_info['currency_value']);
253: $data['total'] = $this->currency->format($order_product['total'], $order_info['currency_code'], $order_info['currency_value']);
254:
255: $data['order'] = $this->url->link('account/order.info', 'order_id=' . $value['order_id']);
256: $data['product'] = $this->url->link('product/product', 'product_id=' . $value['product_id']);
257:
258: // Settings
259: $from = $this->model_setting_setting->getValue('config_email', $order_info['store_id']);
260:
261: if (!$from) {
262: $from = $this->config->get('config_email');
263: }
264:
265: if ($this->config->get('payment_' . $payment_info['code'] . '_status')) {
266: $this->load->model('extension/payment/' . $payment_info['code']);
267:
268: // Promotion
269: if (isset($this->{'model_extension_payment_' . $payment_info['code']}->promotion)) {
270: $subscription_status_id = $this->{'model_extension_payment_' . $payment_info['code']}->promotion($value['subscription_id']);
271:
272: if ($store_info) {
273: $config_subscription_active_status_id = $this->model_setting_setting->getValue('config_subscription_active_status_id', $store_info['store_id']);
274: } else {
275: $config_subscription_active_status_id = $this->config->get('config_subscription_active_status_id');
276: }
277:
278: if ($config_subscription_active_status_id == $subscription_status_id) {
279: $subscription_info = $this->model_account_subscription->getSubscription($value['subscription_id']);
280:
281: // Validate the latest subscription values with the ones edited
282: // by promotional extensions
283: if ($subscription_info && $subscription_info['status'] && $subscription_info['customer_id'] == $value['customer_id'] && $subscription_info['order_id'] == $value['order_id'] && $subscription_info['order_product_id'] == $value['order_product_id']) {
284: $this->load->model('account/customer');
285:
286: $customer_info = $this->model_account_customer->getCustomer($subscription_info['customer_id']);
287:
288: $frequencies = [
289: 'day',
290: 'week',
291: 'semi_month',
292: 'month',
293: 'year'
294: ];
295:
296: // We need to validate frequencies in compliance of the admin subscription plans
297: // as with the use of the APIs
298: if ($customer_info && (int)$subscription_info['cycle'] >= 0 && $subscription_info['cycle'] == $value['cycle'] && in_array($subscription_info['frequency'], $frequencies)) {
299: if ($subscription_info['frequency'] == 'semi_month') {
300: $period = strtotime("2 weeks");
301: } else {
302: $period = strtotime($subscription_info['cycle'] . ' ' . $subscription_info['frequency']);
303: }
304:
305: // New customer once the trial period has ended
306: $customer_period = strtotime($customer_info['date_added']);
307:
308: $trial_period = 0;
309: $validate_trial = 0;
310:
311: // Trial
312: if ($subscription_info['trial_cycle'] && $subscription_info['trial_frequency'] && $subscription_info['trial_cycle'] == $value['trial_cycle'] && $subscription_info['trial_frequency'] == $value['trial_frequency']) {
313: if ($subscription_info['trial_frequency'] == 'semi_month') {
314: $trial_period = strtotime("2 weeks");
315: } else {
316: $trial_period = strtotime($subscription_info['trial_cycle'] . ' ' . $subscription_info['trial_frequency']);
317: }
318:
319: $trial_period = ($trial_period - $customer_period);
320: $validate_trial = round($trial_period / (60 * 60 * 24));
321: }
322:
323: // Calculates the remaining days between the subscription
324: // promotional period and the date added period
325: $period = ($period - $customer_period);
326:
327: // Calculate remaining period of each features
328: $period = round($period / (60 * 60 * 24));
329:
330: // Promotional features description must be identical
331: // until the time period has exceeded. Therefore, the current
332: // period must be matched as well
333: if (($period == 0 && ($validate_trial > 0 || !$validate_trial)) && $value['description'] == $description && $subscription_info['subscription_plan_id'] == $value['subscription_plan_id']) {
334: // Products
335: $this->load->model('catalog/product');
336:
337: $product_subscription_info = $this->model_catalog_product->getSubscription($order_product['product_id'], $subscription_info['subscription_plan_id']);
338:
339: if ($product_subscription_info) {
340: // For the next billing cycle
341: $this->model_account_subscription->addTransaction($value['subscription_id'], $value['order_id'], $this->language->get('text_promotion'), $subscription_info['amount'], $subscription_info['type'], $subscription_info['payment_method'], $subscription_info['payment_code']);
342: }
343: }
344: }
345: }
346: }
347: }
348: }
349:
350: // Mail
351: if ($this->config->get('config_mail_engine')) {
352: $mail_option = [
353: 'parameter' => $this->config->get('config_mail_parameter'),
354: 'smtp_hostname' => $this->config->get('config_mail_smtp_hostname'),
355: 'smtp_username' => $this->config->get('config_mail_smtp_username'),
356: 'smtp_password' => html_entity_decode($this->config->get('config_mail_smtp_password'), ENT_QUOTES, 'UTF-8'),
357: 'smtp_port' => $this->config->get('config_mail_smtp_port'),
358: 'smtp_timeout' => $this->config->get('config_mail_smtp_timeout')
359: ];
360:
361: $mail = new \Opencart\System\Library\Mail($this->config->get('config_mail_engine'), $mail_option);
362: $mail->setTo($order_info['email']);
363: $mail->setFrom($from);
364: $mail->setSender($store_name);
365: $mail->setSubject($subject);
366: $mail->setHtml($this->load->view('mail/subscription', $data));
367: $mail->send();
368: }
369: }
370: }
371: }
372: }
373: }
374: }
375: }
376: */
377: }
378:
379: // catalog/model/checkout/order/addHistory/before
380:
381: /**
382: * Alert
383: *
384: * @param string $route
385: * @param array<int, mixed> $args
386: *
387: * @throws \Exception
388: *
389: * @return void
390: */
391: public function alert(string &$route, array &$args): void {
392: if (isset($args[0])) {
393: $order_id = $args[0];
394: } else {
395: $order_id = 0;
396: }
397:
398: if (isset($args[1])) {
399: $order_status_id = $args[1];
400: } else {
401: $order_status_id = 0;
402: }
403:
404: if (isset($args[2])) {
405: $comment = $args[2];
406: } else {
407: $comment = '';
408: }
409:
410: if (isset($args[3])) {
411: $notify = $args[3];
412: } else {
413: $notify = '';
414: }
415:
416: $order_info = $this->model_checkout_order->getOrder($order_id);
417:
418: if ($order_info && !$order_info['order_status_id'] && $order_status_id && in_array('order', (array)$this->config->get('config_mail_alert'))) {
419: $this->load->language('mail/order_alert');
420:
421: $subject = html_entity_decode(sprintf($this->language->get('text_subject'), $this->config->get('config_name'), $order_info['order_id']), ENT_QUOTES, 'UTF-8');
422:
423: $data['order_id'] = $order_info['order_id'];
424: $data['date_added'] = date($this->language->get('date_format_short'), strtotime($order_info['date_added']));
425:
426: $order_status_query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "order_status` WHERE `order_status_id` = '" . (int)$order_status_id . "' AND `language_id` = '" . (int)$this->config->get('config_language_id') . "'");
427:
428: if ($order_status_query->num_rows) {
429: $data['order_status'] = $order_status_query->row['name'];
430: } else {
431: $data['order_status'] = '';
432: }
433:
434: $this->load->model('tool/upload');
435:
436: $data['products'] = [];
437:
438: $order_products = $this->model_checkout_order->getProducts($order_id);
439:
440: foreach ($order_products as $order_product) {
441: $option_data = [];
442:
443: $order_options = $this->model_checkout_order->getOptions($order_info['order_id'], $order_product['order_product_id']);
444:
445: foreach ($order_options as $order_option) {
446: if ($order_option['type'] != 'file') {
447: $value = $order_option['value'];
448: } else {
449: $upload_info = $this->model_tool_upload->getUploadByCode($order_option['value']);
450:
451: if ($upload_info) {
452: $value = $upload_info['name'];
453: } else {
454: $value = '';
455: }
456: }
457:
458: $option_data[] = [
459: 'name' => $order_option['name'],
460: 'value' => (oc_strlen($value) > 20 ? oc_substr($value, 0, 20) . '..' : $value)
461: ];
462: }
463:
464: $description = '';
465:
466: $this->load->model('checkout/subscription');
467:
468: $subscription_info = $this->model_checkout_order->getSubscription($order_info['order_id'], $order_product['order_product_id']);
469:
470: if ($subscription_info) {
471: if ($subscription_info['trial_status']) {
472: $trial_price = $this->currency->format($subscription_info['trial_price'] + ($this->config->get('config_tax') ? $subscription_info['trial_tax'] : 0), $this->session->data['currency']);
473: $trial_cycle = $subscription_info['trial_cycle'];
474: $trial_frequency = $this->language->get('text_' . $subscription_info['trial_frequency']);
475: $trial_duration = $subscription_info['trial_duration'];
476:
477: $description .= sprintf($this->language->get('text_subscription_trial'), $trial_price, $trial_cycle, $trial_frequency, $trial_duration);
478: }
479:
480: $price = $this->currency->format($subscription_info['price'] + ($this->config->get('config_tax') ? $subscription_info['tax'] : 0), $this->session->data['currency']);
481: $cycle = $subscription_info['cycle'];
482: $frequency = $this->language->get('text_' . $subscription_info['frequency']);
483: $duration = $subscription_info['duration'];
484:
485: if ($duration) {
486: $description .= sprintf($this->language->get('text_subscription_duration'), $price, $cycle, $frequency, $duration);
487: } else {
488: $description .= sprintf($this->language->get('text_subscription_cancel'), $price, $cycle, $frequency);
489: }
490: }
491:
492: $data['products'][] = [
493: 'name' => $order_product['name'],
494: 'model' => $order_product['model'],
495: 'quantity' => $order_product['quantity'],
496: 'option' => $option_data,
497: 'subscription' => $description,
498: 'total' => html_entity_decode($this->currency->format($order_product['total'] + ($this->config->get('config_tax') ? $order_product['tax'] * $order_product['quantity'] : 0), $order_info['currency_code'], $order_info['currency_value']), ENT_NOQUOTES, 'UTF-8')
499: ];
500: }
501:
502: $data['vouchers'] = [];
503:
504: $order_vouchers = $this->model_checkout_order->getVouchers($order_id);
505:
506: foreach ($order_vouchers as $order_voucher) {
507: $data['vouchers'][] = [
508: 'description' => $order_voucher['description'],
509: 'amount' => html_entity_decode($this->currency->format($order_voucher['amount'], $order_info['currency_code'], $order_info['currency_value']), ENT_NOQUOTES, 'UTF-8')
510: ];
511: }
512:
513: $data['totals'] = [];
514:
515: $order_totals = $this->model_checkout_order->getTotals($order_id);
516:
517: foreach ($order_totals as $order_total) {
518: $data['totals'][] = [
519: 'title' => $order_total['title'],
520: 'value' => html_entity_decode($this->currency->format($order_total['value'], $order_info['currency_code'], $order_info['currency_value']), ENT_NOQUOTES, 'UTF-8')
521: ];
522: }
523:
524: $data['comment'] = nl2br($order_info['comment']);
525:
526: $data['store'] = html_entity_decode($order_info['store_name'], ENT_QUOTES, 'UTF-8');
527: $data['store_url'] = $order_info['store_url'];
528:
529: if ($this->config->get('config_mail_engine')) {
530: $mail_option = [
531: 'parameter' => $this->config->get('config_mail_parameter'),
532: 'smtp_hostname' => $this->config->get('config_mail_smtp_hostname'),
533: 'smtp_username' => $this->config->get('config_mail_smtp_username'),
534: 'smtp_password' => html_entity_decode($this->config->get('config_mail_smtp_password'), ENT_QUOTES, 'UTF-8'),
535: 'smtp_port' => $this->config->get('config_mail_smtp_port'),
536: 'smtp_timeout' => $this->config->get('config_mail_smtp_timeout')
537: ];
538:
539: $mail = new \Opencart\System\Library\Mail($this->config->get('config_mail_engine'), $mail_option);
540: $mail->setTo($this->config->get('config_email'));
541: $mail->setFrom($this->config->get('config_email'));
542: $mail->setSender(html_entity_decode($order_info['store_name'], ENT_QUOTES, 'UTF-8'));
543: $mail->setSubject($subject);
544: $mail->setHtml($this->load->view('mail/order_alert', $data));
545: $mail->send();
546:
547: // Send to additional alert emails
548: $emails = explode(',', $this->config->get('config_mail_alert_email'));
549:
550: foreach ($emails as $email) {
551: if ($email && filter_var($email, FILTER_VALIDATE_EMAIL)) {
552: $mail->setTo(trim($email));
553: $mail->send();
554: }
555: }
556: }
557: }
558: }
559: }
560: