PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / site / helpers / history.php

history.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at site/helpers/history.php

810 lines 21.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Handles all the events involving a reservation.
15 */
16 class VboBookingHistory
17 {
18 /**
19 * @var int
20 */
21 protected $bid = null;
22
23 /**
24 * @var array
25 */
26 protected $prevBooking = null;
27
28 /**
29 * @var mixed
30 */
31 protected $data = null;
32
33 /**
34 * @var object
35 */
36 protected $dbo = null;
37
38 /**
39 * @var array
40 */
41 protected $typesMap = [];
42
43 /**
44 * List of event types worthy of a notification.
45 *
46 * @var array
47 *
48 * @since 1.15.0 (J) - 1.5.0 (WP)
49 */
50 protected $worthy_events = [
51 'NC',
52 'MW',
53 'NP',
54 'P0',
55 'PN',
56 'CR',
57 'CW',
58 'MC',
59 'CC',
60 'NO',
61 'PO',
62 'IR',
63 'PC',
64 'UR',
65 ];
66
67 /**
68 * Class constructor.
69 *
70 * @param int $bid optional booking ID to set.
71 *
72 * @since 1.16.5 (J) - 1.6.5 (WP) added argument $bid.
73 */
74 public function __construct($bid = 0)
75 {
76 $this->dbo = JFactory::getDbo();
77 $this->typesMap = $this->getTypesMap();
78
79 if ($bid) {
80 $this->bid = (int)$bid;
81 }
82 }
83
84 /**
85 * Returns an array of types mapped to
86 * the corresponding language definition.
87 * All the history types should be listed here.
88 *
89 * @return array
90 */
91 public function getTypesMap()
92 {
93 return [
94 //New booking with status Confirmed
95 'NC' => JText::translate('VBOBOOKHISTORYTNC'),
96 //Booking modified from website
97 'MW' => JText::translate('VBOBOOKHISTORYTMW'),
98 //Booking modified from back-end
99 'MB' => JText::translate('VBOBOOKHISTORYTMB'),
100 //New booking from back-end
101 'NB' => JText::translate('VBOBOOKHISTORYTNB'),
102 //New booking with status Pending
103 'NP' => JText::translate('VBOBOOKHISTORYTNP'),
104 //Booking paid for the first time
105 'P0' => JText::translate('VBOBOOKHISTORYTP0'),
106 //Booking paid for a second time
107 'PN' => JText::translate('VBOBOOKHISTORYTPN'),
108 //Cancellation request message
109 'CR' => JText::translate('VBOBOOKHISTORYTCR'),
110 //Booking cancelled via front-end website
111 'CW' => JText::translate('VBOBOOKHISTORYTCW'),
112 //Booking auto cancelled via front-end
113 'CA' => JText::translate('VBOBOOKHISTORYTCA'),
114 //Booking cancelled via back-end by admin
115 'CB' => JText::translate('VBOBOOKHISTORYTCB'),
116 //Booking Receipt generated via back-end
117 'BR' => JText::translate('VBOBOOKHISTORYTBR'),
118 //Booking Invoice generated
119 'BI' => JText::translate('VBOBOOKHISTORYTBI'),
120 //Booking registration unset status by admin
121 'RA' => JText::translate('VBOBOOKHISTORYTRA'),
122 //Booking checked-in status set by admin
123 'RB' => JText::translate('VBOBOOKHISTORYTRB'),
124 //Booking checked-out status set by admin
125 'RC' => JText::translate('VBOBOOKHISTORYTRC'),
126 //Booking no-show status set by admin
127 'RZ' => JText::translate('VBOBOOKHISTORYTRZ'),
128 //Booking set to Confirmed by admin
129 'TC' => JText::translate('VBOBOOKHISTORYTTC'),
130 //Booking set to Confirmed via App
131 'AC' => JText::translate('VBOBOOKHISTORYTAC'),
132 //Booking modified from channel
133 'MC' => JText::translate('VBOBOOKHISTORYTMC'),
134 //Booking cancelled from channel
135 'CC' => JText::translate('VBOBOOKHISTORYTCC'),
136 //Booking removed via App
137 'AR' => JText::translate('VBOBOOKHISTORYTAR'),
138 //Booking modified via App
139 'AM' => JText::translate('VBOBOOKHISTORYTAM'),
140 //New booking via App
141 'AN' => JText::translate('VBOBOOKHISTORYTAN'),
142 //New Booking from OTA
143 'NO' => JText::translate('VBOBOOKHISTORYTNO'),
144 //Report affecting the booking
145 'RP' => JText::translate('VBOBOOKHISTORYTRP'),
146 //Custom email sent to the customer by admin
147 'CE' => JText::translate('VBOBOOKHISTORYTCE'),
148 //Custom SMS sent to the customer by admin
149 'CS' => JText::translate('VBOBOOKHISTORYTCS'),
150 //Confirmation email re-sent by admin to guest
151 'ER' => JText::translate('VBRESENDORDEMAIL'),
152 //Payment Update (a new amount paid has been set)
153 'PU' => JText::translate('VBOBOOKHISTORYTPU'),
154 // Upsell Extras via front-end
155 'UE' => JText::translate('VBOBOOKHISTORYTUE'),
156 // Report guest misconduct to OTA
157 'GM' => JText::translate('VBOBOOKHISTORYTGM'),
158 // Send Email Cancellation by admin
159 'EC' => JText::translate('VBOBOOKHISTORYTEC'),
160 // Amount refunded
161 'RF' => JText::translate('VBOBOOKHISTORYTRF'),
162 // Refund Updated
163 'RU' => JText::translate('VBOBOOKHISTORYTRU'),
164 // Payable Amount Updated
165 'PB' => JText::translate('VBOBOOKHISTORYTPB'),
166 // Channel Manager custom event
167 'CM' => JText::translate('VBOBOOKHISTORYTCM'),
168 // Channel Manager payout notification
169 'PO' => JText::translate('VBOBOOKHISTORYTPO'),
170 // Inquiry reservation (website)
171 'IR' => JText::translate('VBOBOOKHISTORYTIR'),
172 // Pre-checkin updated via front-end
173 'PC' => JText::translate('VBOBOOKHISTORYTPC'),
174 // Guest review received
175 'GR' => JText::translate('VBOBOOKHISTORYTGR'),
176 // Upgrade room
177 'UR' => JText::translate('VBOBOOKHISTORYTUR'),
178 // Overbooking
179 'OB' => JText::translate('VBOBOOKHISTORYTOB'),
180 ];
181 }
182
183 /**
184 * Accesses the groups of history event types, useful to group multiple events.
185 * Groups are categories of events containing worthy event types of the same context.
186 *
187 * @param string $group get the list of event types of this group.
188 *
189 * @return array associative array of types group or empty array.
190 *
191 * @since 1.16.0 (J) - 1.6.0 (WP)
192 */
193 public function getTypeGroups($group = null)
194 {
195 // we group various types of events per category
196 $type_groups = [
197 // group "Bookings"
198 'GBK' => [
199 'name' => JText::translate('VBMENUTHREE'),
200 'types' => [
201 'NC',
202 'MW',
203 'NP',
204 'CR',
205 'CW',
206 'MC',
207 'CC',
208 'NO',
209 'IR',
210 'UR',
211 'OB',
212 ],
213 ],
214 // group "Channel Manager"
215 'GCM' => [
216 'name' => JText::translate('VBMENUCHANNELMANAGER'),
217 'types' => [
218 'MC',
219 'CC',
220 'NO',
221 'PO',
222 'CM',
223 'OB',
224 ],
225 ],
226 // group "Website"
227 'GWB' => [
228 'name' => JText::translate('VBORDFROMSITE'),
229 'types' => [
230 'NC',
231 'MW',
232 'NP',
233 'CR',
234 'CW',
235 'IR',
236 'PC',
237 'UE',
238 'GR',
239 'UR',
240 ],
241 ],
242 // group "Payments"
243 'GPM' => [
244 'name' => JText::translate('VBO_HISTORY_GPM'),
245 'types' => [
246 'P0',
247 'PN',
248 'PO',
249 'RF',
250 ],
251 ],
252 ];
253
254 if (!empty($group)) {
255 // attempt to fetch the requested group
256 return isset($type_groups[$group]) ? $type_groups[$group] : [];
257 }
258
259 // return the whole associative list
260 return $type_groups;
261 }
262
263 /**
264 * Sets the current booking ID.
265 *
266 * @param int $bid
267 *
268 * @return self
269 **/
270 public function setBid($bid)
271 {
272 $this->bid = (int)$bid;
273
274 return $this;
275 }
276
277 /**
278 * Sets the previous booking array.
279 * To calculate what has changed in the booking after the
280 * modification, VBO uses the method getLogBookingModification().
281 * VCM instead should use this method to tell the class that
282 * what has changed should be calculated to obtain the 'descr'
283 * text of the history record that will be stored.
284 *
285 * @param array $booking
286 *
287 * @return self
288 **/
289 public function setPrevBooking($booking)
290 {
291 if (is_array($booking)) {
292 $this->prevBooking = $booking;
293 }
294
295 return $this;
296 }
297
298 /**
299 * Sets extra data for the current history log.
300 *
301 * @param mixed $data array, object or string of extra data.
302 * Useful to store the amount paid to be invoiced,
303 * or the transaction details of the payments.
304 *
305 * @return self
306 **/
307 public function setExtraData($data)
308 {
309 $this->data = $data;
310
311 return $this;
312 }
313
314 /**
315 * Checks whether the type for the history record is valid.
316 *
317 * @param string $type
318 * @param [bool] $returnit if true, the translated value is returned. Otherwise boolean is returned
319 *
320 * @return boolean
321 */
322 public function validType($type, $returnit = false)
323 {
324 if ($returnit) {
325 return isset($this->typesMap[strtoupper($type)]) ? $this->typesMap[strtoupper($type)] : $type;
326 }
327
328 return isset($this->typesMap[strtoupper($type)]);
329 }
330
331 /**
332 * Reads the booking record.
333 * Returns false in case of failure.
334 *
335 * @param mixed
336 *
337 * @return array
338 */
339 protected function getBookingInfo()
340 {
341 $q = "SELECT * FROM `#__vikbooking_orders` WHERE `id`=" . (int)$this->bid;
342 $this->dbo->setQuery($q, 0, 1);
343
344 $record = $this->dbo->loadAssoc();
345
346 if (!$record) {
347 return false;
348 }
349
350 return $record;
351 }
352
353 /**
354 * Stores a new history record for the booking.
355 *
356 * @param string $type the char-type of store we are making for the history
357 * @param [string] $descr the description of this booking record (optional)
358 *
359 * @return boolean
360 */
361 public function store($type, $descr = '')
362 {
363 if (is_null($this->bid) || !$this->validType($type)) {
364 return false;
365 }
366
367 if (!$booking_info = $this->getBookingInfo()) {
368 return false;
369 }
370
371 if (empty($descr) && is_array($this->prevBooking)) {
372 /**
373 * VCM (including the App) could set the previous booking information,
374 * so we need to calculate what has changed with the booking.
375 * Load VBO language.
376 */
377 $lang = JFactory::getLanguage();
378 $lang->load('com_vikbooking', (VBOPlatformDetection::isWordPress() ? VIKBOOKING_ADMIN_LANG : JPATH_ADMINISTRATOR), $lang->getTag(), true);
379 if (!class_exists('VikBooking')) {
380 require_once(VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikbooking.php');
381 }
382 $descr = VikBooking::getLogBookingModification($this->prevBooking);
383 }
384
385 // get the event dispatcher
386 $dispatcher = VBOFactory::getPlatform()->getDispatcher();
387
388 // build record object
389 $history_record = new stdClass;
390 $history_record->idorder = $this->bid;
391 $history_record->dt = JFactory::getDate()->toSql();
392 $history_record->type = $type;
393 $history_record->descr = $descr ?: null;
394 $history_record->totpaid = (float)$booking_info['totpaid'];
395 $history_record->total = (float)$booking_info['total'];
396 $history_record->data = $this->data;
397
398 /**
399 * Trigger event before saving a new history record.
400 *
401 * @since 1.16.0 (J) - 1.6.0 (WP)
402 */
403 $results = $dispatcher->filter('onBeforeSaveBookingHistoryVikBooking', [$history_record, $this->prevBooking]);
404 if (in_array(false, $results, true)) {
405 return false;
406 }
407
408 /**
409 * Store the extra data for this history log. Useful to store
410 * information about the amount paid and its invoicing status.
411 *
412 * @since 1.12 (J) - 1.1.7 (WP)
413 */
414 if (!is_null($history_record->data) && !is_scalar($history_record->data)) {
415 $history_record->data = json_encode($history_record->data);
416 }
417
418 // store record
419 if (!$this->dbo->insertObject('#__vikbooking_orderhistory', $history_record, 'id') || !isset($history_record->id)) {
420 return false;
421 }
422
423 /**
424 * Trigger event for the newly history record added.
425 *
426 * @since 1.16.0 (J) - 1.6.0 (WP)
427 */
428 $dispatcher->trigger('onAfterSaveBookingHistoryVikBooking', [$history_record, $this->prevBooking]);
429
430 return true;
431 }
432
433 /**
434 * Loads the history records for the booking ID set.
435 *
436 * @param int $offset query offset limit start.
437 * @param int $limit query maximum records to fetch.
438 *
439 * @return array
440 *
441 * @since 1.16.5 (J) - 1.6.5 (WP) added arguments $offset and $limit.
442 */
443 public function loadHistory($offset = 0, $limit = 0)
444 {
445 if (empty($this->bid)) {
446 return [];
447 }
448
449 $q = $this->dbo->getQuery(true)
450 ->select('*')
451 ->from($this->dbo->qn('#__vikbooking_orderhistory'))
452 ->where($this->dbo->qn('idorder') . ' = ' . (int)$this->bid)
453 ->order($this->dbo->qn('dt') . ' DESC')
454 ->order($this->dbo->qn('id') . ' DESC');
455
456 $this->dbo->setQuery($q, $offset, $limit);
457
458 return $this->dbo->loadAssocList();
459 }
460
461 /**
462 * Checks whether this booking has an event of the given type.
463 *
464 * @param string $type the type of the event.
465 *
466 * @return mixed last date on success, false otherwise.
467 *
468 * @since 1.13.5 (J) - 1.3.5 (WP)
469 */
470 public function hasEvent($type)
471 {
472 if (empty($type) || empty($this->bid)) {
473 return false;
474 }
475
476 $q = $this->dbo->getQuery(true)
477 ->select($this->dbo->qn('dt'))
478 ->from($this->dbo->qn('#__vikbooking_orderhistory'))
479 ->where($this->dbo->qn('idorder') . ' = ' . (int)$this->bid)
480 ->where($this->dbo->qn('type') . ' = ' . $this->dbo->q($type))
481 ->order($this->dbo->qn('dt') . ' DESC');
482
483 $this->dbo->setQuery($q, 0, 1);
484
485 $dt = $this->dbo->loadResult();
486
487 if (!$dt) {
488 return false;
489 }
490
491 return $dt;
492 }
493
494 /**
495 * Returns a list of records with data defined for the given event type.
496 * Useful to get a list of transactions data for the refund operations.
497 *
498 * @param mixed $type string or array event type(s).
499 * @param callable $callvalid callback for the data validation.
500 * @param bool $onlydata whether to get just the event data.
501 *
502 * @return mixed false or array with data records.
503 *
504 * @since 1.14.0 (J) - 1.4.0 (WP)
505 */
506 public function getEventsWithData($type, $callvalid = null, $onlydata = true)
507 {
508 if (empty($type) || empty($this->bid)) {
509 return false;
510 }
511
512 if (!is_array($type)) {
513 $type = [$type];
514 }
515
516 // quote all given types
517 $types = array_map([$this->dbo, 'q'], $type);
518
519 $q = $this->dbo->getQuery(true)
520 ->select('*')
521 ->from($this->dbo->qn('#__vikbooking_orderhistory'))
522 ->where($this->dbo->qn('idorder') . ' = ' . (int)$this->bid)
523 ->where($this->dbo->qn('type') . ' IN (' . implode(', ', $types) . ')')
524 ->order($this->dbo->qn('dt') . ' ASC');
525
526 $this->dbo->setQuery($q);
527 $events = $this->dbo->loadAssocList();
528
529 if ($events) {
530 $datas = [];
531 foreach ($events as $k => $e) {
532 $data = json_decode($e['data']);
533 if (!empty($data)) {
534 $events[$k]['data'] = $data;
535 }
536 $valid_data = true;
537 if (is_callable($callvalid)) {
538 $valid_data = call_user_func($callvalid, $events[$k]['data']);
539 }
540 if ($valid_data) {
541 array_push($datas, $events[$k]['data']);
542 }
543 }
544
545 return $onlydata ? $datas : $events;
546 }
547
548 return false;
549 }
550
551 /**
552 * Gets the latest history event per booking within a list.
553 * Only the latest event per booking will be considered.
554 *
555 * @param int $start the query start offset.
556 * @param int $limit the query limit.
557 * @param int $min_id optional minimum history ID to fetch.
558 * @param array $types optional list of event types (or groups) to fetch.
559 *
560 * @return array the list of recent history record objects.
561 *
562 * @since 1.15.0 (J) - 1.5.0 (WP)
563 * @since 1.16.0 (J) - 1.6.0 (WP) query modified to fetch the customer picture.
564 * @since 1.16.0 (J) - 1.6.0 (WP) fourth argument $types introduced.
565 */
566 public function getLatestBookingEvents($start = 0, $limit = 20, $min_id = 0, array $types = [])
567 {
568 $clauses = [];
569
570 if ($min_id > 0) {
571 $clauses[] = "`h`.`id` > " . (int)$min_id;
572 } else {
573 $clauses[] = "`h`.`id` > 0";
574 }
575
576 if ($types) {
577 // scan for groups
578 $groups = [];
579 foreach ($types as $k => $type) {
580 if ($type && strlen($type) === 3 && $group = $this->getTypeGroups($type)) {
581 $groups = array_merge($groups, $group['types']);
582 unset($types[$k]);
583 }
584 }
585
586 if ($groups) {
587 // merge all types
588 $types = array_merge(array_values($types), array_unique($groups));
589 }
590
591 // quote all values
592 $types = array_map(array($this->dbo, 'q'), array_filter($types));
593
594 if ($types) {
595 // add query clause
596 $clauses[] = "`h`.`type` IN (" . implode(', ', $types) . ")";
597 }
598 }
599
600 /**
601 * On some servers with just 30k records in the history and 5k in bookings,
602 * the INNER JOIN to group the latest history events by booking ID is taking
603 * ~25 seconds to complete, which is not acceptable. This doesn't seem to happen
604 * on every database, but since the admin widget "latest events" requires to get
605 * just the latest history event ID by passing the limit to 1, we should not
606 * use the INNER JOIN all the times to save server resources.
607 * Refactoring performed to abandon also the LEFT JOINS which were timing out in
608 * case of several thousands of records. The widget "latest_events" crashed websites.
609 *
610 * @since 1.15.6 (J) - 1.5.12 (WP)
611 * @since 1.16.0 (J) - 1.6.0 (WP) INNER JOIN dismissed because too slow and not needed.
612 * @since 1.16.1 (J) - 1.6.1 (WP) LEFT JOIN dismissed because too slow, queries split.
613 */
614
615 // query just the history records without joining any other table, or the records would become hundreds of thousands
616 $q = $this->dbo->getQuery(true)
617 ->select($this->dbo->qn('h') . '.*')
618 ->from($this->dbo->qn('#__vikbooking_orderhistory', 'h'))
619 ->where(implode(' AND ', $clauses))
620 ->order($this->dbo->qn('h.dt') . ' DESC')
621 ->order($this->dbo->qn('h.id') . ' DESC');
622
623 $this->dbo->setQuery($q, $start, $limit);
624 $rows = $this->dbo->loadObjectList();
625 if (!$rows) {
626 return [];
627 }
628
629 // build a list of reservation IDs
630 $history_orders = [];
631 foreach ($rows as $row) {
632 $history_orders[] = (int)$row->idorder;
633 }
634 $history_orders = array_unique($history_orders);
635
636 // fetch the rest of the information
637 $q = "SELECT `o`.`id` AS `idorder`, `o`.`custdata`, `o`.`status`, `o`.`days`, `o`.`checkin`, `o`.`checkout`, `o`.`totpaid`,
638 `o`.`total`, `o`.`idorderota`, `o`.`channel`, `o`.`country`, `o`.`closure`, `o`.`type` AS `booking_type`, `c`.`pic`,
639 (
640 SELECT CONCAT_WS(' ',`or`.`t_first_name`,`or`.`t_last_name`)
641 FROM `#__vikbooking_ordersrooms` AS `or`
642 WHERE `or`.`idorder`=`o`.`id` LIMIT 1
643 ) AS `nominative`
644 FROM `#__vikbooking_orders` AS `o`
645 LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id`
646 LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer`
647 WHERE `o`.`id` IN (" . implode(', ', $history_orders) . ");";
648
649 $this->dbo->setQuery($q);
650 $rows_data = $this->dbo->loadObjectList();
651 if (!$rows_data) {
652 return [];
653 }
654
655 // merge information to object records
656 foreach ($rows as &$row) {
657 foreach ($rows_data as $row_data) {
658 if ($row_data->idorder != $row->idorder) {
659 continue;
660 }
661
662 // merge object properties by using casting
663 $row = (object) array_merge((array) $row, (array) $row_data);
664
665 break;
666 }
667 }
668
669 // unset last reference
670 unset($row);
671
672 // return the final list
673 return $rows;
674 }
675
676 /**
677 * Helper method to set a new list of worthy event types.
678 *
679 * @param array $worthy_events list of event type identifiers.
680 *
681 * @return self
682 *
683 * @since 1.15.0 (J) - 1.5.0 (WP)
684 */
685 public function setWorthyEventTypes($worthy_events = [])
686 {
687 if (is_array($worthy_events)) {
688 $this->worthy_events = $worthy_events;
689 }
690
691 return $this;
692 }
693
694 /**
695 * Helper method to get the list of worthy event types.
696 *
697 * @return array
698 *
699 * @since 1.15.4 (J) - 1.5.10 (WP)
700 */
701 public function getWorthyEventTypes()
702 {
703 return $this->worthy_events;
704 }
705
706 /**
707 * Gets the latest history events worthy of a notification.
708 *
709 * @param int $min_id optional minimum history ID to fetch.
710 * @param int $limit the query limit.
711 *
712 * @return array the list of worthy history record objects.
713 *
714 * @since 1.15.0 (J) - 1.5.0 (WP)
715 * @since 1.16.0 (J) - 1.6.0 (WP) query modified to fetch the customer picture.
716 * INNER JOIN dismissed.
717 */
718 public function getWorthyEvents($min_id = 0, $limit = 0)
719 {
720 $clauses = [
721 "`o`.`status` IS NOT NULL",
722 "`o`.`closure` = 0"
723 ];
724
725 if ($min_id > 0) {
726 $clauses[] = "`h`.`id` > " . (int)$min_id;
727 }
728
729 if ($this->worthy_events) {
730 // quote all worthy types of event
731 $worthy_types = array_map(array($this->dbo, 'quote'), $this->worthy_events);
732 $clauses[] = "`h`.`type` IN (" . implode(', ', $worthy_types) . ")";
733 }
734
735 // join as few tables as possible to reduce the execution timing
736 $q = $this->dbo->getQuery(true)
737 ->select($this->dbo->qn('h') . '.*')
738 ->select([
739 $this->dbo->qn('o.status'),
740 $this->dbo->qn('o.days'),
741 $this->dbo->qn('o.checkin'),
742 $this->dbo->qn('o.checkout'),
743 $this->dbo->qn('o.totpaid'),
744 $this->dbo->qn('o.total'),
745 $this->dbo->qn('o.idorderota'),
746 $this->dbo->qn('o.channel'),
747 $this->dbo->qn('o.country'),
748 ])
749 ->from($this->dbo->qn('#__vikbooking_orderhistory', 'h'))
750 ->leftJoin($this->dbo->qn('#__vikbooking_orders', 'o') . ' ON ' . $this->dbo->qn('h.idorder') . ' = ' . $this->dbo->qn('o.id'))
751 ->where(implode(' AND ', $clauses))
752 ->order($this->dbo->qn('h.dt') . ' DESC')
753 ->order($this->dbo->qn('h.id') . ' DESC');
754
755 $this->dbo->setQuery($q, 0, $limit);
756 $rows = $this->dbo->loadObjectList();
757
758 if (!$rows) {
759 return [];
760 }
761
762 // set the "pic" property to a default empty value
763 foreach ($rows as &$row) {
764 $row->pic = '';
765 }
766
767 // unset last reference
768 unset($row);
769
770 // build a list of reservation IDs
771 $history_orders = [];
772 foreach ($rows as $row) {
773 $history_orders[] = (int)$row->idorder;
774 }
775 $history_orders = array_unique($history_orders);
776
777 // fetch the rest of the information
778 $q = $this->dbo->getQuery(true)
779 ->select([
780 $this->dbo->qn('o.id'),
781 $this->dbo->qn('c.pic'),
782 ])
783 ->from($this->dbo->qn('#__vikbooking_orders', 'o'))
784 ->leftJoin($this->dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $this->dbo->qn('co.idorder') . ' = ' . $this->dbo->qn('o.id'))
785 ->leftJoin($this->dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $this->dbo->qn('c.id') . ' = ' . $this->dbo->qn('co.idcustomer'))
786 ->where($this->dbo->qn('o.id') . ' IN (' . implode(', ', $history_orders) . ')');
787
788 $this->dbo->setQuery($q);
789 $rows_data = $this->dbo->loadObjectList();
790
791 if ($rows_data) {
792 // merge information
793 foreach ($rows as &$row) {
794 foreach ($rows_data as $row_data) {
795 if ($row_data->id != $row->idorder) {
796 continue;
797 }
798
799 // set picture property
800 $row->pic = $row_data->pic;
801 break;
802 }
803 }
804 }
805
806 // return all records found
807 return $rows;
808 }
809 }
810