PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / models / subscrcart.php
vikappointments / site / models Last commit date
allorders.php 1 day ago calendarweek.php 1 day ago cart.php 1 day ago confirmapp.php 1 day ago empaccountstat.php 1 day ago empattachser.php 1 day ago empcoupons.php 1 day ago empcustfields.php 1 day ago empeditcoupon.php 1 day ago empeditcustfield.php 1 day ago empeditlocation.php 1 day ago empeditpay.php 1 day ago empeditprofile.php 1 day ago empeditservice.php 1 day ago empeditwdays.php 1 day ago emplocations.php 1 day ago emplocwdays.php 1 day ago emplogin.php 1 day ago employeesearch.php 1 day ago employeeslist.php 1 day ago empmanres.php 1 day ago emppaylist.php 1 day ago empserviceslist.php 1 day ago empsettingsman.php 1 day ago empsubscrcart.php 1 day ago empsubscrhistory.php 1 day ago empsubscrorder.php 1 day ago empwdays.php 1 day ago index.html 1 day ago packages.php 1 day ago packagescart.php 1 day ago packagesconfirm.php 1 day ago packorders.php 1 day ago servicesearch.php 1 day ago serviceslist.php 1 day ago subscrcart.php 1 day ago subscrhistory.php 1 day ago subscrpayment.php 1 day ago
subscrcart.php
570 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 VAPLoader::import('libraries.mvc.model');
15 VAPLoader::import('libraries.models.subscriptions');
16 VAPLoader::import('libraries.cart.discount');
17
18 /**
19 * VikAppointments subscription cart model.
20 *
21 * @since 1.7
22 */
23 class VikAppointmentsModelSubscrcart extends JModelVAP
24 {
25 /**
26 * An associative array holding the subscription details.
27 *
28 * @var array
29 */
30 private $subscription = null;
31
32 /**
33 * An associative array holding the payment details.
34 *
35 * @var array
36 */
37 private $payment = null;
38
39 /**
40 * A list of applied discounts.
41 *
42 * @var VAPCartDiscount[]
43 */
44 private $discounts = null;
45
46 /**
47 * An optional suffix to be included within the session keys.
48 *
49 * @var string
50 */
51 protected $suffix = '';
52
53 /**
54 * Loads all the subscriptions available for the current user.
55 *
56 * @return array
57 */
58 public function getAllSubscriptions()
59 {
60 static $subscriptions = null;
61
62 // load subscriptions only once
63 if (is_null($subscriptions))
64 {
65 // load all the active subscriptions (ignore TRIAL, if any)
66 $subscriptions = VAPSubscriptions::getList();
67
68 // flag used to include the trial version
69 $trial = VAPSubscriptions::getTrial();
70
71 if (VikAppointments::isUserLogged())
72 {
73 // get customer details of currently logged-in user
74 $user = VikAppointments::getCustomer();
75
76 // make sure the user never subscribed before
77 if ($user && !VAPDateHelper::isNull($user->active_since))
78 {
79 // nope, the user cannot benefit of any TRIAL
80 $trial = false;
81 }
82 }
83
84 if ($trial)
85 {
86 // fetch trial subscription and add it as first element
87 array_unshift($subscriptions, $trial);
88 }
89 }
90
91 return $subscriptions;
92 }
93
94 /**
95 * Loads the details of the selected subscription plan.
96 *
97 * @return array
98 */
99 public function getSubscription()
100 {
101 // load selected subscription only once
102 if (is_null($this->subscription))
103 {
104 $input = JFactory::getApplication()->input;
105
106 // load selected subscription (from cookie)
107 $id_subscr = $input->cookie->getUint('vikappointments_subscr' . $this->suffix . '_id', null);
108
109 if ($id_subscr)
110 {
111 // try to load the details of the selected subscription
112 $this->subscription = $this->fetchSubscription($id_subscr);
113 }
114
115 if (!$this->subscription)
116 {
117 $all = $this->getAllSubscriptions();
118
119 if (!$all)
120 {
121 // No available subscriptions...
122 // Throw an exception to prevent unexpected behaviors
123 throw new RuntimeException('Unsupported subscriptions', 500);
124 }
125
126 // auto-select the first available subscription
127 $this->subscription = $all[0];
128 }
129 }
130
131 return $this->subscription;
132 }
133
134 /**
135 * Helper method used to fetch the subscription data.
136 * Children classes can override this method to fetch
137 * the subscription data in a different way.
138 *
139 * @param integer $id_subscr The subscription ID.
140 *
141 * @return array
142 */
143 protected function fetchSubscription($id_subscr)
144 {
145 $subscr = VAPSubscriptions::get($id_subscr);
146
147 if ($subscr['trial'])
148 {
149 // do not auto-select the trial offer because it might not be
150 // active for this user
151 return null;
152 }
153
154 return $subscr;
155 }
156
157 /**
158 * Subscription plan setter.
159 *
160 * @param integer $id The subscription plan to use.
161 *
162 * @return self This object to support chaining.
163 */
164 public function setSubscription($id)
165 {
166 // change subscription plan by overriding the cookie
167 $input = JFactory::getApplication()->input;
168 $input->cookie->set('vikappointments_subscr' . $this->suffix . '_id', (int) $id);
169
170 // unset cached property to refresh the internal contents
171 // at the next getter access
172 $this->subscription = null;
173
174 return $this;
175 }
176
177 /**
178 * Loads all the payment methods available for the current user.
179 *
180 * @return array
181 */
182 public function getPaymentMethods()
183 {
184 static $payments = null;
185
186 // load payments only once
187 if (is_null($payments))
188 {
189 // load payments and translate them
190 $payments = VikAppointments::getPayments($this->suffix . 'subscriptions');
191 VikAppointments::translatePayments($payments);
192 }
193
194 return $payments;
195 }
196
197 /**
198 * Loads the details of the selected payment method.
199 *
200 * @return array|null
201 */
202 public function getPayment()
203 {
204 // load selected payment method only once
205 if (is_null($this->payment))
206 {
207 $input = JFactory::getApplication()->input;
208
209 // load selected payment method (from cookie)
210 $id_payment = $input->cookie->getUint('vikappointments_subscr' . $this->suffix . '_payment', null);
211
212 $all = $this->getPaymentMethods();
213
214 if ($id_payment)
215 {
216 for ($i = 0; $i < count($all) && !$this->payment; $i++)
217 {
218 if ($all[$i]['id'] == $id_payment)
219 {
220 // payment found
221 $this->payment = $all[$i];
222 }
223 }
224 }
225
226 if (!$this->payment && $all)
227 {
228 // auto-select the first available payment, if any
229 $this->payment = $all[0];
230 }
231 }
232
233 return $this->payment;
234 }
235
236 /**
237 * Payment method setter.
238 *
239 * @param integer $id The payment method to use.
240 *
241 * @return self This object to support chaining.
242 */
243 public function setPayment($id)
244 {
245 // change payment method by overriding the cookie
246 $input = JFactory::getApplication()->input;
247 $input->cookie->set('vikappointments_subscr' . $this->suffix . '_payment', (int) $id);
248
249 // unset cached property to refresh the internal contents
250 // at the next getter access
251 $this->payment = null;
252
253 return $this;
254 }
255
256 /**
257 * Calculates the totals for the specified subscription and payment method.
258 *
259 * @return object
260 */
261 public function getTotals()
262 {
263 VAPLoader::import('libraries.tax.factory');
264
265 $options = array();
266 $options['lang'] = JFactory::getLanguage()->getTag();
267 $options['suffix'] = $this->suffix ?: 'cust';
268 // $options['id_user'] = JFactory::getUser()->id;
269
270 // always revalidate coupon code
271 $this->revalidateCoupon();
272
273 // get subscription details
274 $subscr = $this->getSubscription();
275
276 // get subscription base amount
277 $amount = $subscr['price'];
278
279 // get selected payment, if any
280 $payment = $this->getPayment();
281
282 if ($payment && $payment['charge'] < 0)
283 {
284 // treat payment charge as a discount
285 $this->setDiscount(new VAPCartDiscount('payment', $payment['charge']));
286 }
287 else
288 {
289 // alternatively remove the payment discount, if was registered
290 $this->removeDiscount('payment');
291 }
292
293 foreach ($this->getDiscounts() as $discount)
294 {
295 // reset internal index
296 $discount->set('count', 0);
297 // reset internal total discount
298 $discount->set('disctot', 0);
299 // set total number of itemd (only 1)
300 $discount->set('length', 1);
301 // register the base cost of the subscription
302 $discount->set('total', $amount);
303
304 // apply discount
305 $amount = $discount->apply($amount, $subscr['price'], $subscr);
306 }
307
308 // calculate taxes of subscription
309 $result = VAPTaxFactory::calculate($subscr['id_tax'], $amount, $options);
310
311 // calculate total discount
312 $result->discount = $subscr['price'] - $amount;
313
314 // ignore payment selection in case the subscription plan has no cost
315 if ($result->gross && $payment && $payment['charge'] > 0)
316 {
317 // calculate taxes of payment method
318 $pay = VAPTaxFactory::calculate($payment['id_tax'], $payment['charge'], $options);
319
320 $result->payment = $pay;
321
322 // sum totals to subscription
323 $result->gross += $pay->gross;
324 $result->net += $pay->net;
325 $result->tax += $pay->tax;
326
327 // merge breakdowns
328 $result->breakdown = array_merge($result->breakdown, $pay->breakdown);
329 }
330
331 return $result;
332 }
333
334 /**
335 * Helper method used to redeem the specified coupon code.
336 *
337 * @param mixed $coupon Either the coupon details or its code.
338 *
339 * @return boolean True on success, false otherwise.
340 */
341 public function redeemCoupon($coupon)
342 {
343 if (empty($coupon))
344 {
345 // coupon code not specified
346 $this->setError(JText::translate('VAPCOUPONNOTVALID'));
347 return false;
348 }
349
350 if (is_string($coupon))
351 {
352 // get model to load coupon details
353 $couponModel = JModelVAP::getInstance('coupon');
354 $coupon = $couponModel->getCoupon($coupon);
355
356 if (!$coupon)
357 {
358 // coupon not found in database
359 $this->setError(JText::translate('VAPCOUPONNOTVALID'));
360 return false;
361 }
362 }
363
364 // validate the coupon code
365 if (!VikAppointments::validateSubscriptionsCoupon($coupon, $this))
366 {
367 // cannot apply the coupon code
368 $this->setError(JText::translate('VAPCOUPONNOTVALID'));
369 return false;
370 }
371
372 // coupon valid, create discount object
373 $discount = new VAPCartDiscount('coupon', $coupon->value, $coupon->percentot == 1);
374 // register coupon data for later use
375 $discount->set('couponData', $coupon);
376
377 // apply discount, by replacing any other coupon discount previously set
378 $this->setDiscount($discount);
379
380 return true;
381 }
382
383 /**
384 * Revalidates the internal coupon code, since the cart might be no more
385 * compliant with the coupon restrictions after some changes.
386 *
387 * @return boolean True in case of valid coupon, false otherwise.
388 */
389 public function revalidateCoupon()
390 {
391 // get coupon discount, if any
392 $discount = $this->getDiscount('coupon');
393
394 if (!$discount)
395 {
396 // coupon discount not set
397 return false;
398 }
399
400 // extract coupon data
401 $coupon = $discount->get('couponData');
402
403 // try to redeem the coupon code
404 $res = $this->redeemCoupon($coupon);
405
406 if (!$res)
407 {
408 // coupon no more valid, unset it
409 $this->removeDiscount($discount);
410
411 return false;
412 }
413
414 // coupon still valid
415 return true;
416 }
417
418 /**
419 * Registers a new discount within the cart.
420 *
421 * @param VAPCartDiscount $discount The discount to apply.
422 *
423 * @return self This object to support chaining.
424 */
425 public function addDiscount(VAPCartDiscount $discount)
426 {
427 // load discounts first
428 $this->getDiscounts();
429
430 // add discount element
431 $this->discounts[] = $discount;
432
433 // commit changes
434 $this->saveDiscounts();
435
436 return $this;
437 }
438
439 /**
440 * Removes a discount from the cart, if any.
441 *
442 * @param mixed $discount Either the discount ID or an object.
443 *
444 * @return mixed The deleted discount on success, false otherwise.
445 */
446 public function removeDiscount($discount)
447 {
448 // load discounts first
449 $this->getDiscounts();
450
451 foreach ($this->discounts as $i => $elem)
452 {
453 if ($elem === $discount || (is_scalar($discount) && $elem->getID() == $discount)
454 || ($discount instanceof VAPCartDiscount && $discount->getID() == $elem->getID()))
455 {
456 // remove from array
457 array_splice($this->discounts, $i, 1);
458
459 // commit changes
460 $this->saveDiscounts();
461
462 return $elem;
463 }
464 }
465
466 return false;
467 }
468
469 /**
470 * Sets a discount within the cart. In case the same discount
471 * is already set into the cart, the old one will be replaced
472 * by the new one.
473 *
474 * @param VAPCartDiscount $discount The discount to apply.
475 *
476 * @return self This object to support chaining.
477 */
478 public function setDiscount(VAPCartDiscount $discount)
479 {
480 // remove discount first
481 $this->removeDiscount($discount);
482
483 // then add new discount element
484 $this->discounts[] = $discount;
485
486 // commit changes
487 $this->saveDiscounts();
488
489 return $this;
490 }
491
492 /**
493 * Returns the discount matching the specified code.
494 *
495 * @param mixed $discount Either the discount ID or an object.
496 *
497 * @return mixed The discount object on success, null otherwise.
498 */
499 public function getDiscount($discount)
500 {
501 // load discounts first
502 $this->getDiscounts();
503
504 foreach ($this->discounts as $i => $elem)
505 {
506 if ((is_scalar($discount) && $elem->getID() == $discount)
507 || ($discount instanceof VAPCartDiscount && $discount->getID() == $elem->getID()))
508 {
509 return $elem;
510 }
511 }
512
513 return null;
514 }
515
516 /**
517 * Returns the list containing all the discounts.
518 *
519 * @return array
520 */
521 public function getDiscounts()
522 {
523 // fetch discounts from session only once
524 if (is_null($this->discounts))
525 {
526 // load from session
527 $list = JFactory::getSession()->get('vap.subscr' . $this->suffix . '.discounts', null);
528
529 if ($list)
530 {
531 $this->discounts = unserialize($list);
532 }
533 else
534 {
535 $this->discounts = array();
536 }
537 }
538
539 return $this->discounts;
540 }
541
542 /**
543 * Empties the current cart items.
544 *
545 * @return void
546 */
547 public function emptyCart()
548 {
549 // unset subscription
550 $this->setSubscription(null);
551 // unset payment method
552 $this->setPayment(null);
553
554 // clear applied discounts
555 $this->discounts = [];
556 $this->saveDiscounts();
557 }
558
559 /**
560 * Helper method used to save the registered discounts within the
561 * user session.
562 *
563 * @return void
564 */
565 protected function saveDiscounts()
566 {
567 JFactory::getSession()->set('vap.subscr' . $this->suffix . '.discounts', serialize($this->getDiscounts()));
568 }
569 }
570