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 / helpers / libraries / cartpack / cart.php
vikappointments / site / helpers / libraries / cartpack Last commit date
cart.php 6 days ago index.html 6 days ago item.php 6 days ago
cart.php
724 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.cartpack.item');
15 VAPLoader::import('libraries.cart.discount');
16
17 /**
18 * Class used to handle a cart to book the packages.
19 *
20 * @since 1.6
21 */
22 class VAPCartPackages implements JsonSerializable
23 {
24 /**
25 * The instance of the Cart.
26 * There should be only one cart instance for the whole session.
27 *
28 * @var VAPCartPackages
29 *
30 * @since 1.6
31 */
32 protected static $instance = null;
33
34 /**
35 * The list containing the selected items.
36 *
37 * @var VAPCartPackagesItem[]
38 */
39 private $cart = array();
40
41 /**
42 * A list of applied discounts.
43 *
44 * @var VAPCartDiscount[]
45 * @since 1.7
46 */
47 private $discounts = array();
48
49 /**
50 * The configuration array.
51 *
52 * @var array
53 */
54 private $params = array(
55 self::MAX_SIZE => self::UNLIMITED,
56 );
57
58 /**
59 * Returns the instance of the cart object, only creating it
60 * if doesn't exist yet.
61 *
62 * @param array $cart The array containing all the items to push.
63 * @param array $params The settings array.
64 *
65 * @return self A new instance.
66 *
67 * @since 1.6
68 */
69 public static function getInstance(array $cart = array(), array $params = array())
70 {
71 if (static::$instance === null)
72 {
73 // get cart from session
74 $session_cart = JFactory::getSession()->get(self::CART_SESSION_KEY, null);
75
76 if (empty($session_cart))
77 {
78 $cart = new static($cart, $params);
79 }
80 else
81 {
82 $cart = unserialize($session_cart);
83 }
84
85 static::$instance = $cart;
86 }
87
88 // always overwrite existing params
89 static::$instance->setParams($params);
90
91 return static::$instance;
92 }
93
94 /**
95 * Class constructor.
96 *
97 * @param array $cart The array containing all the items to push.
98 * @param array $params The settings array.
99 *
100 * @uses setParams()
101 */
102 public function __construct(array $cart = array(), array $params = array())
103 {
104 $this->cart = $cart;
105 $this->setParams($params);
106 }
107
108 /**
109 * Store this instance into the PHP session.
110 *
111 * @return self This object to support chaining.
112 *
113 * @since 1.6
114 */
115 public function store()
116 {
117 JFactory::getSession()->set(self::CART_SESSION_KEY, serialize($this));
118
119 return $this;
120 }
121
122 /**
123 * Sets the configuration of the cart.
124 *
125 * @param array $params The settings array.
126 *
127 * @return self This object to support chaining.
128 */
129 public function setParams(array $params = array())
130 {
131 foreach ($params as $k => $v)
132 {
133 $this->params[$k] = $v;
134 }
135
136 return $this;
137 }
138
139 /**
140 * Empties the items within the cart.
141 *
142 * @return self This object to support chaining.
143 */
144 public function emptyCart()
145 {
146 $this->cart = array();
147
148 // reset discounts too
149 $this->discounts = array();
150
151 return $this;
152 }
153
154 /**
155 * Checks if the cart is empty.
156 *
157 * @return boolean True if empty, false otherwise.
158 */
159 public function isEmpty()
160 {
161 return count($this->cart) == 0;
162 }
163
164 /**
165 * Balances the cart in order to empty the free slots
166 * created after removing one or more items.
167 *
168 * @return self This object to support chaining.
169 *
170 * @deprecated 1.8 Without replacement.
171 */
172 public function balance()
173 {
174 // do nothing, balance is automatically made every
175 // time an item gets removed
176
177 return $this;
178 }
179
180 /**
181 * Pushes a new package within the cart.
182 * This method checks if the item can be added as the cart
183 * may own an internal size limit.
184 *
185 * @param VAPCartPackagesItem $pack The package to push.
186 *
187 * @return boolean True on success, false otherwise.
188 *
189 * @uses getPackagesInCart()
190 * @uses indexOf()
191 */
192 public function addPackage(VAPCartPackagesItem $pack)
193 {
194 if ($this->params[self::MAX_SIZE] == self::UNLIMITED || $this->getPackagesInCart() < $this->params[self::MAX_SIZE])
195 {
196 $index = $this->indexOf($pack->getID());
197
198 if ($index == -1)
199 {
200 // add package at the end of the cart
201 $this->cart[] = $pack;
202 }
203 else
204 {
205 // increase quantity by the specified one
206 $this->cart[$index]->addQuantity($pack->getQuantity());
207 }
208
209 return true;
210 }
211
212 return false;
213 }
214
215 /**
216 * Removes the specified package.
217 *
218 * @param integer $id The package ID.
219 * @param integer $units The number of units to remove.
220 *
221 * @return boolean True on success, false otherwise.
222 *
223 * @uses indexOf()
224 */
225 public function removePackage($id, $units = 1)
226 {
227 $index = $this->indexOf($id);
228
229 if ($index != -1)
230 {
231 // remove by the specified units
232 $q = $this->cart[$index]->removeQuantity($units);
233
234 if ($q == 0)
235 {
236 // no more units, delete the option from the list
237 array_splice($this->cart, $index, 1);
238 }
239
240 return true;
241 }
242
243 return false;
244 }
245
246 /**
247 * Returns the position within the list of the specified package.
248 *
249 * @param integer $id The package ID.
250 *
251 * @return integer The package index if exists, -1 otherwise.
252 *
253 * @uses getCartLength()
254 */
255 public function indexOf($id)
256 {
257 for ($i = 0; $i < $this->getCartLength(); $i++)
258 {
259 if ($this->cart[$i]->getID() == $id)
260 {
261 return $i;
262 }
263 }
264
265 return -1;
266 }
267
268 /**
269 * Returns the total cost of the cart.
270 *
271 * @return float
272 */
273 public function getTotalCost()
274 {
275 $total = 0;
276
277 foreach ($this->cart as $p)
278 {
279 $total += $p->getTotalCost();
280 }
281
282 return $total;
283 }
284
285 /**
286 * Returns the total net of the cart.
287 *
288 * @return float
289 *
290 * @since 1.7
291 */
292 public function getTotalNet()
293 {
294 $this->prepareDiscounts();
295
296 $total = 0;
297
298 foreach ($this->cart as $i)
299 {
300 $total += $i->getTotalNet($this);
301 }
302
303 return $total;
304 }
305
306 /**
307 * Returns the total tax of the cart.
308 *
309 * @return float
310 *
311 * @since 1.7
312 */
313 public function getTotalTax()
314 {
315 $this->prepareDiscounts();
316
317 $total = 0;
318
319 foreach ($this->cart as $i)
320 {
321 $total += $i->getTotalTax($this);
322 }
323
324 return $total;
325 }
326
327 /**
328 * Returns the total gross of the cart.
329 *
330 * @return float
331 *
332 * @since 1.7
333 */
334 public function getTotalGross()
335 {
336 $this->prepareDiscounts();
337
338 $total = 0;
339
340 foreach ($this->cart as $i)
341 {
342 $total += $i->getTotalGross($this);
343 }
344
345 return $total;
346 }
347
348 /**
349 * Returns the total discount.
350 *
351 * @param array &$lookup A lookup used to track the applied discounts.
352 *
353 * @return float
354 *
355 * @since 1.7
356 */
357 public function getTotalDiscount(&$lookup = array())
358 {
359 $this->prepareDiscounts();
360
361 $total = 0;
362
363 foreach ($this->cart as $i)
364 {
365 // calculate the difference between the item full price
366 // and the discounted price, if any
367 $total += $i->getTotalCost() - $i->getDiscountedPrice($this, $lookup);
368 }
369
370 return round($total, 2);
371 }
372
373 /**
374 * Returns the totals per each registered item and option.
375 *
376 * @return array An array of discounts, matching the index
377 * of the related item.
378 *
379 * @since 1.7
380 */
381 public function getTotalsPerItem()
382 {
383 $this->prepareDiscounts();
384
385 $items = array();
386
387 $options = array();
388 // $options['id_user'] = JFactory::getUser()->id;
389
390 foreach ($this->cart as $i => $item)
391 {
392 $itemTotals = new stdClass;
393 // calculate original price
394 $itemTotals->priceBeforeDiscount = $item->getPrice();
395 // calculate final price per item and related discount
396 $itemTotals->price = $item->getDiscountedPrice($this);
397 $itemTotals->discount = $itemTotals->priceBeforeDiscount - $itemTotals->price;
398
399 $options['subject'] = 'package';
400
401 // re-calculate totals of discounted item
402 $tmp = VAPTaxFactory::calculate($item->getID(), $itemTotals->price, $options);
403
404 // register new totals inside the object
405 foreach ($tmp as $k => $v)
406 {
407 $itemTotals->{$k} = $v;
408 }
409
410 // register item
411 $items[] = $itemTotals;
412 }
413
414 return $items;
415 }
416
417 /**
418 * Returns the total discount per each registered offer.
419 *
420 * @return array A lookup of discounts, where the key is the
421 * title/ID and the value is the discount.
422 *
423 * @since 1.7
424 */
425 public function getTotalDiscountPerOffer()
426 {
427 // pass a junk variable to the method used to calculate the
428 // total discount per each offer
429 $this->getTotalDiscount($lookup);
430
431 $map = array();
432
433 // iterate all registered discounts
434 foreach ($this->getDiscounts() as $discount)
435 {
436 $id = $discount->getID();
437
438 if (!isset($lookup[$id]))
439 {
440 // discount not set, go ahead
441 continue;
442 }
443
444 // try to check whether the discount supports a readable title
445 $k = $discount->get('title');
446
447 if ($k)
448 {
449 // title given, try to translate it
450 $k = JText::translate($k);
451 }
452 else
453 {
454 // missing title, use the ID
455 $k = $id;
456 }
457
458 // register discount total
459 $map[$k] = round($lookup[$id], 2);
460 }
461
462 return $map;
463 }
464
465 /**
466 * Returns the package at the specified position.
467 *
468 * @param integer $index
469 *
470 * @return mixed The package if exists, null otherwise.
471 *
472 * @uses getCartLength()
473 */
474 public function getPackageAt($index)
475 {
476 if ($index >= 0 && $index < $this->getCartLength())
477 {
478 return $this->cart[$index];
479 }
480
481 return null;
482 }
483
484 /**
485 * Returns the number of packages within the cart.
486 * The list may contain also packages that are no more
487 * active.
488 *
489 * @return integer
490 */
491 public function getCartLength()
492 {
493 return count($this->cart);
494 }
495
496 /**
497 * Returns the number of active packages within the list.
498 *
499 * @return integer
500 */
501 public function getPackagesInCart()
502 {
503 $count = 0;
504
505 foreach ($this->cart as $p)
506 {
507 $count += $p->getQuantity();
508 }
509
510 return $count;
511 }
512
513 /**
514 * Returns a list containing all the active packages.
515 *
516 * @return array
517 */
518 public function getPackagesList()
519 {
520 return $this->cart;
521 }
522
523 /**
524 * Configures the discount objects before being used.
525 *
526 * @return self
527 *
528 * @since 1.7
529 */
530 protected function prepareDiscounts()
531 {
532 $count = 0;
533
534 // counts the total number of items that have a cost
535 foreach ($this->cart as $item)
536 {
537 if ($item->getPrice() > 0)
538 {
539 // item with cost, increase counter
540 $count++;
541 }
542 }
543
544 foreach ($this->discounts as $discount)
545 {
546 // reset internal index
547 $discount->set('count', 0);
548 // reset internal total discount
549 $discount->set('disctot', 0);
550 // set total number of items with cost
551 $discount->set('length', $count);
552 // register the total cost of the order
553 $discount->set('total', $this->getTotalCost());
554 }
555
556 return $this;
557 }
558
559 /**
560 * Registers a new discount within the cart.
561 *
562 * @param VAPCartDiscount $discount The discount to apply.
563 *
564 * @return self This object to support chaining.
565 *
566 * @since 1.7
567 */
568 public function addDiscount(VAPCartDiscount $discount)
569 {
570 // add discount element
571 $this->discounts[] = $discount;
572
573 return $this;
574 }
575
576 /**
577 * Removes a discount from the cart, if any.
578 *
579 * @param mixed $discount Either the discount ID or an object.
580 *
581 * @return mixed The deleted discount on success, false otherwise.
582 *
583 * @since 1.7
584 */
585 public function removeDiscount($discount)
586 {
587 foreach ($this->discounts as $i => $elem)
588 {
589 if ($elem === $discount || (is_scalar($discount) && $elem->getID() == $discount)
590 || ($discount instanceof VAPCartDiscount && $discount->getID() == $elem->getID()))
591 {
592 return array_splice($this->discounts, $i, 1);
593 }
594 }
595
596 return false;
597 }
598
599 /**
600 * Sets a discount within the cart. In case the same discount
601 * is already set into the cart, the old one will be replaced
602 * by the new one.
603 *
604 * @param VAPCartDiscount $discount The discount to apply.
605 *
606 * @return self This object to support chaining.
607 *
608 * @since 1.7
609 */
610 public function setDiscount(VAPCartDiscount $discount)
611 {
612 // remove discount first
613 $this->removeDiscount($discount);
614
615 // then add new discount element
616 $this->discounts[] = $discount;
617
618 return $this;
619 }
620
621 /**
622 * Returns the discount matching the specified code.
623 *
624 * @param mixed $discount Either the discount ID or an object.
625 *
626 * @return mixed The discount object on success, null otherwise.
627 *
628 * @since 1.7
629 */
630 public function getDiscount($discount)
631 {
632 foreach ($this->discounts as $i => $elem)
633 {
634 if ((is_scalar($discount) && $elem->getID() == $discount)
635 || ($discount instanceof VAPCartDiscount && $discount->getID() == $elem->getID()))
636 {
637 return $elem;
638 }
639 }
640
641 return null;
642 }
643
644 /**
645 * Returns the list containing all the discounts.
646 *
647 * @return array
648 *
649 * @since 1.7
650 */
651 public function getDiscounts()
652 {
653 return $this->discounts;
654 }
655
656 /**
657 * Returns the first available index to push a new item.
658 * Used to replace a unactive item with a new one.
659 *
660 * @return integer
661 *
662 * @uses getCartLength()
663 *
664 * @deprecated 1.8 Without replacement.
665 */
666 protected function getFirstAvailableIndex()
667 {
668 return $this->getCartLength();
669 }
670
671 /**
672 * Magic method used to return a string representation of this instance.
673 *
674 * @return string
675 */
676 public function __tostring()
677 {
678 return '<pre>' . print_r($this, true) . '</pre><br />Total Cost = ' . $this->getTotalCost();
679 }
680
681 /**
682 * Creates a standard object, containing all the supported properties,
683 * to be used when this class is passed to "json_encode()".
684 *
685 * @return object
686 *
687 * @since 1.7
688 *
689 * @see JsonSerializable
690 */
691 #[ReturnTypeWillChange]
692 public function jsonSerialize()
693 {
694 return array(
695 'cart' => $this->cart,
696 'discounts' => $this->discounts,
697 );
698 }
699
700 /**
701 * Identifier used to make the size of the cart unlimited.
702 *
703 * @var integer
704 */
705 const UNLIMITED = -1;
706
707 /**
708 * Setting name used to retrieve the maximum number of items
709 * that can be added within the list.
710 *
711 * @var string
712 */
713 const MAX_SIZE = "maxsize";
714
715 /**
716 * CART_SESSION_KEY identifier for session key.
717 *
718 * @var string
719 *
720 * @since 1.6
721 */
722 const CART_SESSION_KEY = 'vapcartpackdev';
723 }
724