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 / cart / option.php
vikappointments / site / helpers / libraries / cart Last commit date
cart.php 2 days ago discount.php 2 days ago index.html 2 days ago item.php 2 days ago option.php 2 days ago utils.php 2 days ago
option.php
605 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 /**
15 * Class used to handle the options that can be attached to the cart items.
16 *
17 * @since 1.6
18 */
19 class VAPCartOption implements JsonSerializable
20 {
21 /**
22 * The option identifier.
23 *
24 * @var integer
25 */
26 private $id;
27
28 /**
29 * The variation identifier.
30 *
31 * @var integer
32 */
33 private $idVariation;
34
35 /**
36 * The option name. If the variation is set, the name is built as:
37 * [OPTION_NAME] - [VARIATION_NAME]
38 *
39 * @var string
40 */
41 private $name;
42
43 /**
44 * The option price plus the variation price (if any).
45 *
46 * @var float
47 */
48 private $price;
49
50 /**
51 * The option (plus the variation) extra duration.
52 *
53 * @var int
54 * @since 1.7.3
55 */
56 private $duration;
57
58 /**
59 * The selected quantity of the option.
60 *
61 * @var integer
62 */
63 private $quantity;
64
65 /**
66 * The minimum number of units that can be selected.
67 *
68 * @var integer
69 * @since 1.7.4
70 */
71 private $minQuantity = 1;
72
73 /**
74 * The maximum number of units that can be selected.
75 *
76 * @var integer
77 */
78 private $maxQuantity;
79
80 /**
81 * Flag used to check if the option is required (mandatory selection) or not.
82 *
83 * @var boolean
84 */
85 private $required;
86
87 /**
88 * An object holding the item totals.
89 *
90 * @var object
91 * @since 1.7
92 */
93 private $totals;
94
95 /**
96 * Whether the units are shared with other options.
97 *
98 * @var bool
99 * @since 1.7.8
100 */
101 private $shared = false;
102
103 /**
104 * Class constructor.
105 *
106 * @param integer $id The option ID.
107 * @param integer $id_var The variation ID (-1 if not specified).
108 * @param string $name The option name (variation included).
109 * @param float $price The option price (variation included).
110 * @param integer $maxq The maximum quantity.
111 * @param boolean $required True if required, false otherwise.
112 * @param integer $quantity The selected units.
113 * @param integer $duration The extra duration, in minutes (added @since 1.7.3).
114 */
115 public function __construct($id, $var, $name, $price, $maxq, $required, $quantity = 1, $duration = 0)
116 {
117 $this->id = (int) $id;
118 $this->idVariation = (int) $var;
119 $this->name = $name;
120 $this->quantity = (int) $quantity;
121 $this->maxQuantity = (int) $maxq;
122 $this->required = (bool) $required;
123 $this->duration = abs((int) $duration);
124
125 /**
126 * Check whether the selected option needs to have the number of units always
127 * equal to the number of selected participants. In that case, the minimum
128 * quantity should be equal to the maximum one.
129 *
130 * @since 1.7.4
131 */
132 if ($this->maxQuantity > 1 && JModelVAP::getInstance('option')->getItem($id, $blank = true)->maxqpeople == 2)
133 {
134 $this->minQuantity = $this->maxQuantity;
135 }
136
137 $this->setPrice($price);
138 }
139
140 /**
141 * Returns the option identifier.
142 *
143 * @return integer
144 */
145 public function getID()
146 {
147 return $this->id;
148 }
149
150 /**
151 * Returns the option variation identifier.
152 * If not set, returns -1.
153 *
154 * @return integer
155 */
156 public function getVariationID()
157 {
158 return $this->idVariation > 0 ? $this->idVariation : -1;
159 }
160
161 /**
162 * Returns the option and variation name.
163 *
164 * @return string
165 */
166 public function getName()
167 {
168 /**
169 * Translate option name at runtime.
170 *
171 * @since 1.7
172 */
173 $translator = VAPFactory::getTranslator();
174 // translate the specified option
175 $tx = $translator->translate('option', $this->getID());
176
177 if ($tx)
178 {
179 // use the specified translation
180 $name = $tx->name;
181 }
182 else
183 {
184 // use the default option name
185 $name = $this->name;
186 }
187
188 if ($this->getVariationID() > 0)
189 {
190 // translate the specified variation
191 $tx = $translator->translate('optionvar', $this->getVariationID());
192
193 if ($tx)
194 {
195 // use the specified translation
196 $name .= ' - ' . $tx->name;
197 }
198 }
199
200 return $name;
201 }
202
203 /**
204 * Returns the option price.
205 *
206 * @return float
207 */
208 public function getPrice()
209 {
210 return (float) $this->price;
211 }
212
213 /**
214 * Returns the option price multiplied by the selected units.
215 *
216 * @return float
217 *
218 * @since 1.7
219 */
220 public function getTotalPrice()
221 {
222 return (float) $this->price * $this->getQuantity();
223 }
224
225 /**
226 * Returns the resulting price after applying the discounts.
227 *
228 * @param mixed $cart When specified, the system will try to apply
229 * the discounts to the base price.
230 * @param array &$lookup A lookup used to track the applied discounts.
231 *
232 * @return float
233 *
234 * @since 1.7
235 */
236 public function getDiscountedPrice($cart = null, &$lookup = array())
237 {
238 $price = $this->getTotalPrice();
239
240 if (!$price)
241 {
242 // the option has no cost, ignore discounts
243 return 0.0;
244 }
245
246 // in case the cart was specified, check whether there are some
247 // discounts to apply
248 if (!$cart || !$cart->getDiscounts())
249 {
250 // nope, use default price
251 return $price;
252 }
253
254 $base = $price;
255
256 foreach ($cart->getDiscounts() as $discount)
257 {
258 $old = $price;
259
260 // apply discount on cascade
261 $price = $discount->apply($price, $base, $this);
262
263 if (!isset($lookup[$discount->getID()]))
264 {
265 // create discount repository
266 $lookup[$discount->getID()] = 0;
267 }
268
269 // increase repo by subtracting the price after the discount
270 // from the price before the discount
271 $lookup[$discount->getID()] += $old - $price;
272 }
273
274 // make sure the price is not lower than 0
275 return max(array(0.0, $price));
276 }
277
278 /**
279 * Returns the total net of the option.
280 *
281 * @param mixed $cart When specified, the system will try to apply
282 * the discounts to the base price.
283 *
284 * @return float
285 *
286 * @since 1.7
287 */
288 public function getTotalNet($cart = null)
289 {
290 $price = $this->getPrice();
291
292 if ($price <= 0)
293 {
294 // the option has not cost
295 return 0.0;
296 }
297
298 // get discounted price
299 $price = $this->getDiscountedPrice($cart);
300
301 if ($price == $this->getTotalPrice())
302 {
303 // no discount, use default total net
304 return $this->totals->net;
305 }
306
307 $options = array();
308 $options['subject'] = 'option';
309 // $options['id_user'] = JFactory::getUser()->id;
310
311 // re-calculate net of discounted item
312 return VAPTaxFactory::calculate($this->getID(), $price, $options)->net;
313 }
314
315 /**
316 * Returns the total taxes of the option.
317 *
318 * @param mixed $cart When specified, the system will try to apply
319 * the discounts to the base price.
320 *
321 * @return float
322 *
323 * @since 1.7
324 */
325 public function getTotalTax($cart = null)
326 {
327 $price = $this->getPrice();
328
329 if ($price <= 0)
330 {
331 // the option has not cost
332 return 0.0;
333 }
334
335 // get discounted price
336 $price = $this->getDiscountedPrice($cart);
337
338 if ($price == $this->getTotalPrice())
339 {
340 // no discount, use default total tax
341 return $this->totals->tax;
342 }
343
344 $options = array();
345 $options['subject'] = 'option';
346 // $options['id_user'] = JFactory::getUser()->id;
347
348 // re-calculate taxes of discounted item
349 return VAPTaxFactory::calculate($this->getID(), $price, $options)->tax;
350 }
351
352 /**
353 * Returns the total gross of the option.
354 *
355 * @param mixed $cart When specified, the system will try to apply
356 * the discounts to the base price.
357 *
358 * @return float
359 *
360 * @since 1.7
361 */
362 public function getTotalGross($cart = null)
363 {
364 $price = $this->getPrice();
365
366 if ($price <= 0)
367 {
368 // the option has not cost
369 return 0.0;
370 }
371
372 // get discounted price
373 $price = $this->getDiscountedPrice($cart);
374
375 if ($price == $this->getTotalPrice())
376 {
377 // no discount, use default total gross
378 return $this->totals->gross;
379 }
380
381 $options = array();
382 $options['subject'] = 'option';
383 // $options['id_user'] = JFactory::getUser()->id;
384
385 // re-calculate gross of discounted item
386 return VAPTaxFactory::calculate($this->getID(), $price, $options)->gross;
387 }
388
389 /**
390 * Sets the option price.
391 *
392 * @param float $price The option cost.
393 *
394 * @return self This object to support chaining.
395 *
396 * @since 1.6.6
397 */
398 public function setPrice($price)
399 {
400 $this->price = (float) $price;
401
402 VAPLoader::import('libraries.tax.factory');
403
404 $options = array();
405 $options['subject'] = 'option';
406 // $options['id_user'] = JFactory::getUser()->id;
407
408 // calculate taxes
409 $this->totals = VAPTaxFactory::calculate($this->getID(), $this->getTotalPrice(), $options);
410
411 return $this;
412 }
413
414 /**
415 * Sets whether the option should share the units among the other shareable options
416 * under the same group.
417 *
418 * @param bool $shared
419 *
420 * @return self
421 *
422 * @since 1.7.8
423 */
424 public function setShared(bool $shared = true)
425 {
426 $this->shared = $shared;
427
428 return $this;
429 }
430
431 /**
432 * Checks whether the option should share the units among the other shareable options
433 * under the same group.
434 *
435 * @return bool $shared
436 *
437 * @since 1.7.8
438 */
439 public function isShared()
440 {
441 return $this->shared;
442 }
443
444 /**
445 * Returns the number of selected units.
446 *
447 * @return integer
448 */
449 public function getQuantity()
450 {
451 return $this->quantity;
452 }
453
454 /**
455 * Returns the maximum number of units that can be selected.
456 *
457 * @return integer
458 */
459 public function getMaxQuantity()
460 {
461 return $this->maxQuantity;
462 }
463
464 /**
465 * Checks if the option is required.
466 *
467 * @return boolean
468 */
469 public function isRequired()
470 {
471 return $this->required;
472 }
473
474 /**
475 * Returns the extra duration applied by the option.
476 *
477 * @return integer
478 *
479 * @since 1.7.3
480 */
481 public function getDuration()
482 {
483 return $this->duration;
484 }
485
486 /**
487 * Returns the option duration multiplied by the selected units.
488 *
489 * @return integer
490 *
491 * @since 1.7.3
492 */
493 public function getTotalDuration()
494 {
495 return $this->duration * $this->getQuantity();
496 }
497
498 /**
499 * Decreases the number of selected units by the specified amount.
500 *
501 * @param integer $unit The number of units to remove (1 by default).
502 *
503 * @return integer The remaining quantity.
504 */
505 public function remove($unit = 1)
506 {
507 // always take the maximum value between the provided units and the minimum allowed ones
508 $units = max($this->minQuantity, abs($unit));
509
510 // decrease units
511 $this->quantity -= $units;
512
513 if ($this->quantity <= 0)
514 {
515 if ($this->required)
516 {
517 // cannot definitively remove a required option
518 $this->quantity = 1;
519 }
520 else
521 {
522 // permanently removed
523 $this->quantity = 0;
524 }
525 }
526
527 if ($this->quantity)
528 {
529 // refresh taxes
530 $this->setPrice($this->getPrice());
531 }
532
533 return $this->quantity;
534 }
535
536 /**
537 * Increases the number of selected units by the specified amount.
538 *
539 * @param integer $unit The number of units to add (1 by default).
540 *
541 * @return self This object to support chaining.
542 */
543 public function add($unit = 1)
544 {
545 // increase quantity by the specified units
546 $this->quantity += abs($unit);
547 // final quantity cannot exceed the maximum amount
548 $this->quantity = min(array($this->quantity, $this->maxQuantity));
549
550 // refresh taxes
551 $this->setPrice($this->getPrice());
552
553 return $this;
554 }
555
556 /**
557 * Magic method used to return a string representation of this instance.
558 *
559 * @return string
560 */
561 public function __tostring()
562 {
563 return 'ID = ' . $this->id . '<br />' .
564 'Variation ID = '. $this->idVariation . '<br />' .
565 'Name = ' . $this->getName() . '<br />' .
566 'Price = ' . $this->price . '<br />' .
567 'Quantity = ' . $this->quantity;
568 }
569
570 /**
571 * Returns an array containing the details of this instance.
572 *
573 * @return array
574 */
575 public function toArray()
576 {
577 return array(
578 'id' => $this->getID(),
579 'id_variation' => $this->getVariationID(),
580 'name' => $this->getName(),
581 'price' => $this->getPrice(),
582 'quantity' => $this->getQuantity(),
583 'max_quantity' => $this->getMaxQuantity(),
584 'required' => $this->isRequired(),
585 'totals' => $this->totals,
586 );
587 }
588
589 /**
590 * Creates a standard object, containing all the supported properties,
591 * to be used when this class is passed to "json_encode()".
592 *
593 * @return object
594 *
595 * @since 1.7
596 *
597 * @see JsonSerializable
598 */
599 #[ReturnTypeWillChange]
600 public function jsonSerialize()
601 {
602 return $this->toArray();
603 }
604 }
605