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 / order / classes / package.php
vikappointments / site / helpers / libraries / order / classes Last commit date
appointment.php 3 days ago empsubscr.php 3 days ago index.html 3 days ago package.php 3 days ago subscr.php 3 days ago
package.php
576 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.order.wrapper');
15
16 /**
17 * Packages order class wrapper.
18 *
19 * @since 1.7
20 */
21 class VAPOrderPackage extends VAPOrderWrapper
22 {
23 /**
24 * Class constructor.
25 *
26 * @param integer $id The order ID.
27 * @param mixed $langtag The language tag. If null, the default one will be used.
28 * @param array $options An array of options to be passed to the order instance.
29 */
30 public function __construct($id, $langtag = null, array $options = array())
31 {
32 // always force translation
33 $options['translate'] = true;
34
35 parent::__construct($id, $langtag, $options);
36 }
37
38 /**
39 * @override
40 * Returns the packages order object.
41 *
42 * @param integer $id The order ID.
43 * @param mixed $langtag The language tag. If null, the default one will be used.
44 * @param array $options An array of options to be passed to the order instance.
45 *
46 * @return mixed The array/object to load.
47 *
48 * @throws Exception
49 */
50 protected function load($id, $langtag = null, array $options = array())
51 {
52 $dbo = JFactory::getDbo();
53 $config = VAPFactory::getConfig();
54 $dispatcher = VAPFactory::getEventDispatcher();
55
56 // create query
57 $q = $dbo->getQuery(true);
58
59 // select all order columns
60 $q->select('o.*');
61 $q->from($dbo->qn('#__vikappointments_package_order', 'o'));
62
63 // select payment details
64 $q->select($dbo->qn('gp.name', 'payment_name'));
65 $q->select($dbo->qn('gp.file', 'payment_file'));
66 $q->select($dbo->qn('gp.note', 'payment_note'));
67 $q->select($dbo->qn('gp.prenote', 'payment_prenote'));
68 $q->select($dbo->qn('gp.icontype', 'payment_icontype'));
69 $q->select($dbo->qn('gp.icon', 'payment_icon'));
70 $q->leftjoin($dbo->qn('#__vikappointments_gpayments', 'gp') . ' ON ' . $dbo->qn('o.id_payment') . ' = ' . $dbo->qn('gp.id'));
71
72 // select purchased packages
73 $q->select($dbo->qn('oi.id', 'item_id'));
74 $q->select($dbo->qn('oi.id_package', 'item_id_package'));
75 $q->select($dbo->qn('oi.quantity', 'item_quantity'));
76 $q->select($dbo->qn('oi.num_app', 'item_num_app'));
77 $q->select($dbo->qn('oi.used_app', 'item_used_app'));
78 $q->select($dbo->qn('oi.validthru', 'item_validthru'));
79 $q->select($dbo->qn('oi.price', 'item_price'));
80 $q->select($dbo->qn('oi.net', 'item_net'));
81 $q->select($dbo->qn('oi.tax', 'item_tax'));
82 $q->select($dbo->qn('oi.gross', 'item_gross'));
83 $q->select($dbo->qn('oi.discount', 'item_discount'));
84 $q->select($dbo->qn('oi.tax_breakdown', 'item_tax_breakdown'));
85 $q->select($dbo->qn('oi.modifiedon', 'item_modified'));
86 $q->select($dbo->qn('p.name', 'item_name'));
87 $q->select($dbo->qn('p.description', 'item_description'));
88 $q->leftjoin($dbo->qn('#__vikappointments_package_order_item', 'oi') . ' ON ' . $dbo->qn('oi.id_order') . ' = ' . $dbo->qn('o.id'));
89 $q->leftjoin($dbo->qn('#__vikappointments_package', 'p') . ' ON ' . $dbo->qn('oi.id_package') . ' = ' . $dbo->qn('p.id'));
90
91 // select package group details
92 $q->select($dbo->qn('gpk.id', 'item_group_id'));
93 $q->select($dbo->qn('gpk.title', 'item_group_name'));
94 $q->leftjoin($dbo->qn('#__vikappointments_package_group', 'gpk') . ' ON ' . $dbo->qn('p.id_group') . ' = ' . $dbo->qn('gpk.id'));
95
96 // filter by order key, if specified
97 if (isset($options['sid']))
98 {
99 $q->where($dbo->qn('o.sid') . ' = ' . $dbo->q($options['sid']));
100 }
101
102 // load order matching the specified ID
103 $q->where($dbo->qn('o.id') . ' = ' . (int) $id);
104
105 // sort by group, package
106 $q->order($dbo->qn('gpk.ordering') . ' ASC');
107 $q->order($dbo->qn('p.ordering') . ' ASC');
108
109 /**
110 * External plugins can attach to this hook in order to manipulate
111 * the query at runtime, in example to alter the default ordering.
112 *
113 * @param mixed &$query A query builder instance.
114 * @param integer $id The ID of the order.
115 * @param mixed $langtag The language tag. If null, the default one will be used.
116 * @param array $options An array of options to be passed to the order instance.
117 *
118 * @return void
119 *
120 * @since 1.7
121 */
122 $dispatcher->trigger('onLoadPackagesOrderDetails', array(&$q, $id, $langtag, $options));
123
124 $dbo->setQuery($q);
125 $list = $dbo->loadObjectList();
126
127 if (!$list)
128 {
129 // order not found raise error
130 throw new Exception(sprintf('Order [%d] not found', $id), 404);
131 }
132
133 // create parent order details
134 $order = $list[0];
135
136 $order->usedApp = 0;
137 $order->totalApp = 0;
138 $order->lastUpdate = null;
139 $order->packages = array();
140
141 foreach ($list as $row)
142 {
143 // check if we have a package to register
144 if (!empty($row->item_id))
145 {
146 // create package
147 $package = new stdClass;
148 $package->id_assoc = $this->detach($row, 'item_id');
149 $package->id = $this->detach($row, 'item_id_package');
150 $package->name = $this->detach($row, 'item_name');
151 $package->description = $this->detach($row, 'item_description');
152 $package->quantity = $this->detach($row, 'item_quantity');
153 $package->price = $this->detach($row, 'item_price');
154 $package->modified = $this->detach($row, 'item_modified');
155 $package->totalApp = $this->detach($row, 'item_num_app');
156 $package->usedApp = $this->detach($row, 'item_used_app');
157 $package->remainingApp = max(array(0, $package->totalApp - $package->usedApp));
158 $package->validthru = $this->detach($row, 'item_validthru');
159
160 if (VAPDateHelper::isNull($package->validthru))
161 {
162 $package->validthru = null;
163 }
164
165 // increase global counters
166 $order->usedApp += $package->usedApp;
167 $order->totalApp += $package->totalApp;
168
169 if ($package->modified > $order->lastUpdate)
170 {
171 // update global last modification date
172 $order->lastUpdate = $package->modified;
173 }
174
175 // create package group
176 if ($row->item_group_id)
177 {
178 $package->group = new stdClass;
179 $package->group->id = $this->detach($row, 'item_group_id');
180 $package->group->name = $this->detach($row, 'item_group_name');
181 }
182 else
183 {
184 $package->group = null;
185 }
186
187 // create package totals
188 $package->totals = new stdClass;
189 $package->totals->net = $this->detach($row, 'item_net');
190 $package->totals->tax = $this->detach($row, 'item_tax');
191 $package->totals->gross = $this->detach($row, 'item_gross');
192 $package->totals->discount = $this->detach($row, 'item_discount');
193 $package->totals->breakdown = $row->item_tax_breakdown ? json_decode($this->detach($row, 'item_tax_breakdown')) : null;
194
195 // register item within packages list
196 $order->packages[] = $package;
197 }
198 }
199
200 // fetch coupon
201 if ($order->coupon)
202 {
203 list($code, $type, $amount) = explode(';;', $this->detach($order, 'coupon'));
204
205 $order->coupon = new stdClass;
206 $order->coupon->code = $code;
207 $order->coupon->amount = $amount;
208 $order->coupon->type = $type;
209 }
210 else
211 {
212 $order->coupon = null;
213 $this->detach($order, 'coupon');
214 }
215
216 // decode stored CF data
217 $order->custom_f = (array) json_decode($order->custom_f, true);
218 $order->fields = $order->custom_f;
219 $order->displayFields = $order->fields;
220
221 $vars = array_values($order->fields);
222 $vars = array_filter($vars, function($elem)
223 {
224 if (is_array($elem))
225 {
226 return (bool) $elem;
227 }
228
229 return strlen($elem);
230 });
231
232 $order->hasFields = (bool) $vars;
233
234 // fetch payment data
235 if ($order->payment_file)
236 {
237 $order->payment = new stdClass;
238 $order->payment->id = $this->detach($order, 'id_payment');
239 $order->payment->name = $this->detach($order, 'payment_name');
240 $order->payment->driver = $this->detach($order, 'payment_file');
241 $order->payment->iconType = $this->detach($order, 'payment_icontype');
242 $order->payment->icon = $this->detach($order, 'payment_icon');
243
244 if ($order->payment->iconType == 1)
245 {
246 // Font Icon
247 $order->payment->fontIcon = $order->payment->icon;
248 }
249 else
250 {
251 // Image Icon
252 $order->payment->iconURI = JUri::root() . $order->payment->icon;
253
254 // fetch Font Icon based on payment driver
255 switch ($order->payment->driver)
256 {
257 case 'bank_transfer.php':
258 $order->payment->fontIcon = 'fas fa-money-bill';
259 break;
260
261 case 'paypal.php':
262 $order->payment->fontIcon = 'fab fa-paypal';
263 break;
264
265 default:
266 $order->payment->fontIcon = 'fas fa-credit-card';
267 }
268 }
269
270 $order->payment->notes = new stdClass;
271 $order->payment->notes->beforePurchase = $this->detach($order, 'payment_prenote');
272 $order->payment->notes->afterPurchase = $this->detach($order, 'payment_note');
273 }
274 else
275 {
276 $order->payment = null;
277 }
278
279 // setup totals
280 $order->totals = new stdClass;
281 $order->totals->net = $this->detach($order, 'total_net');
282 $order->totals->tax = $this->detach($order, 'total_tax');
283 $order->totals->gross = $this->detach($order, 'total_cost');
284 $order->totals->discount = $this->detach($order, 'discount');
285 $order->totals->paid = $this->detach($order, 'tot_paid');
286 $order->totals->payCharge = $this->detach($order, 'payment_charge');
287 $order->totals->payTax = $this->detach($order, 'payment_tax');
288 $order->totals->due = $order->totals->gross - $order->totals->paid;
289
290 // fetch paid flag based on current order status
291 $order->paid = JHtml::fetch('vaphtml.status.ispaid', 'packages', $order->status);
292
293 if ($order->paid)
294 {
295 // amount paid, no remaining balance
296 $order->totals->due = 0;
297 }
298
299 $order->statusRole = null;
300
301 // fetch status role
302 if (JHtml::fetch('vaphtml.status.ispending', 'packages', $order->status))
303 {
304 $order->statusRole = 'PENDING';
305 }
306 else if (JHtml::fetch('vaphtml.status.isapproved', 'packages', $order->status))
307 {
308 $order->statusRole = 'APPROVED';
309 }
310 else if (JHtml::fetch('vaphtml.status.iscancelled', 'packages', $order->status))
311 {
312 $order->statusRole = 'CANCELLED';
313 }
314
315 /**
316 * External plugins can use this event to manipulate the object holding
317 * the details of the order. Useful to inject all the additional data
318 * fetched with the manipulation of the query.
319 *
320 * @param mixed $order The order details object.
321 * @param array $list The query resulting array.
322 *
323 * @return void
324 */
325 $dispatcher->trigger('onSetupPackagesOrderDetails', array($order, $list));
326
327 $unsetList = array(
328 'id_payment'
329 );
330
331 // get rid of not needed properties
332 foreach (get_object_vars($order) as $k => $v)
333 {
334 if (preg_match("/^(payment)_/", $k))
335 {
336 // get rid of blank payment
337 unset($order->{$k});
338 }
339 else if (preg_match("/^__/", $k))
340 {
341 // remove deprecated (back-up) property
342 unset($order->{$k});
343 }
344 else if (in_array($k, $unsetList))
345 {
346 // remove property if contained in the list
347 unset($order->{$k});
348 }
349 }
350
351 return $order;
352 }
353
354 /**
355 * @override
356 * Translates the internal properties.
357 *
358 * @param mixed $langtag The language tag. If null, the default one will be used.
359 *
360 * @return void
361 */
362 protected function translate($langtag = null)
363 {
364 $dispatcher = VAPFactory::getEventDispatcher();
365
366 if (!$langtag)
367 {
368 // use order lang tag in case it was not specified
369 $langtag = $this->get('langtag', null);
370
371 if (!$langtag)
372 {
373 // the order is not assigned to any lang tag, use the current one
374 $langtag = JFactory::getLanguage()->getTag();
375 }
376 }
377
378 // get translator
379 $translator = VAPFactory::getTranslator();
380
381 $package_ids = array();
382 $pack_group_ids = array();
383
384 foreach ($this->packages as $pack)
385 {
386 $package_ids[] = $pack->id;
387
388 if ($pack->group)
389 {
390 $pack_group_ids[] = $pack->group->id;
391 }
392 }
393
394 // pre-load packages translations
395 $packLang = $translator->load('package', array_unique($package_ids), $langtag);
396 // pre-load packages groups translations
397 $packGroupLang = $translator->load('packgroup', array_unique($pack_group_ids), $langtag);
398
399 // iterate packages and apply translationss
400 foreach ($this->packages as $k => $pack)
401 {
402 // translate package for the given language
403 $pack_tx = $packLang->getTranslation($pack->id, $langtag);
404
405 if ($pack_tx)
406 {
407 $pack->name = $pack_tx->name;
408 $pack->description = $pack_tx->description;
409 }
410
411 if ($pack->group)
412 {
413 // translate package group for the given language
414 $grp_tx = $packGroupLang->getTranslation($pack->group->id, $langtag);
415
416 if ($grp_tx)
417 {
418 $pack->group->name = $grp_tx->title;
419 }
420 }
421
422 // update package
423 $this->packages[$k] = $pack;
424 }
425
426 // translate payment if specified
427 if ($this->payment)
428 {
429 // get payment translation
430 $pay_tx = $translator->translate('payment', $this->payment->id, $langtag);
431
432 if ($pay_tx)
433 {
434 // inject translation within order details
435 $this->payment->name = $pay_tx->name;
436 $this->payment->notes->beforePurchase = $pay_tx->prenote;
437 $this->payment->notes->afterPurchase = $pay_tx->note;
438 }
439 }
440
441 // import custom fields loader
442 VAPLoader::import('libraries.customfields.loader');
443
444 // get relevant custom fields only
445 $cf = VAPCustomFieldsLoader::getInstance()
446 ->translate($langtag)
447 ->noRequiredCheckbox()
448 ->noSeparator()
449 ->fetch();
450
451 // translate CF data object
452 $this->fields = VAPCustomFieldsLoader::translateObject($this->fields, $cf, $langtag);
453
454 // reset display fields
455 $this->displayFields = array();
456
457 foreach ($cf as $field)
458 {
459 $k = $field['name'];
460
461 // always skip file custom fields
462 if (!array_key_exists($k, $this->fields) || $field['type'] == 'file')
463 {
464 // field not found inside the given object, go to next one
465 continue;
466 }
467
468 $v = $this->fields[$k];
469
470 // take only if the value is not empty
471 if ((is_scalar($v) && strlen($v)) || !empty($v))
472 {
473 // get a more readable label/text of the saved value
474 $this->displayFields[$field['langname']] = $v;
475 }
476 }
477
478 /**
479 * External plugins can use this event to apply the translations to
480 * additional details manually included within the order object.
481 *
482 * @param mixed $order The order details object.
483 * @param string $langtag The requested language tag.
484 *
485 * @return void
486 *
487 * @since 1.7
488 */
489 $dispatcher->trigger('onTranslatePackagesOrderDetails', array($this, $langtag));
490 }
491
492 /**
493 * @override
494 * Returns the billing details of the user that made the order.
495 *
496 * @return object
497 */
498 protected function getBilling()
499 {
500 $dbo = JFactory::getDbo();
501
502 $q = $dbo->getQuery(true)
503 ->select('*')
504 ->from($dbo->qn('#__vikappointments_users'))
505 ->where($dbo->qn('id') . ' = ' . (int) $this->id_user)
506 ->orWhere(array(
507 $dbo->qn('billing_mail') . ' <> ' . $dbo->q(''),
508 $dbo->qn('billing_mail') . ' IS NOT NULL',
509 $dbo->qn('billing_mail') . ' = ' . $dbo->q($this->purchaser_mail),
510 ), 'AND');
511
512 $dbo->setQuery($q, 0, 1);
513 return $dbo->loadObject() ?? false;
514 }
515
516 /**
517 * @override
518 * Returns the account details of the order author.
519 *
520 * @return object
521 */
522 protected function getAuthor()
523 {
524 if ($this->createdby <= 0)
525 {
526 // no registered author, do not go ahead
527 return false;
528 }
529
530 $dbo = JFactory::getDbo();
531
532 $q = $dbo->getQuery(true)
533 ->select($dbo->qn('name'))
534 ->select($dbo->qn('username'))
535 ->select($dbo->qn('email'))
536 ->from($dbo->qn('#__users'))
537 ->where($dbo->qn('id') . ' = ' . (int) $this->createdby);
538
539 $dbo->setQuery($q, 0, 1);
540 return $dbo->loadObject() ?? false;
541 }
542
543 /**
544 * @override
545 * Returns the invoice details of the order.
546 *
547 * @return mixed The invoice object if exists, false otherwise.
548 */
549 protected function getInvoice()
550 {
551 return JModelVAP::getInstance('invoice')->getInvoice($this->id, 'packages');
552 }
553
554 /**
555 * @override
556 * Returns the history of the status codes set for the order.
557 *
558 * @return array
559 */
560 protected function getHistory()
561 {
562 return VAPOrderStatus::getInstance('package_order')->getOrderTrack($this->id, $locale = true);
563 }
564
565 /**
566 * @override
567 * Returns a list of notes assigned to this order.
568 *
569 * @return array
570 */
571 protected function getNotes()
572 {
573 return array();
574 }
575 }
576