PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.2
VikBooking Hotel Booking Engine & PMS v1.7.2
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 / admin / helpers / widgets / booking_details.php

booking_details.php in VikBooking Hotel Booking Engine & PMS 1.7.2, at admin/helpers/widgets/booking_details.php

1,701 lines 64.5 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 srl
6 * @copyright Copyright (C) 2023 E4J srl. 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 * Class handler for admin widget "booking details".
15 *
16 * @since 1.16.5 (J) - 1.6.5 (WP)
17 */
18 class VikBookingAdminWidgetBookingDetails extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget. Since we do not load individual parameters
22 * for each widget's instance, we use a static counter to determine its settings.
23 *
24 * @var int
25 */
26 protected static $instance_counter = -1;
27
28 /**
29 * Class constructor will define the widget name and identifier.
30 */
31 public function __construct()
32 {
33 // call parent constructor
34 parent::__construct();
35
36 $this->widgetName = JText::translate('VBMAINORDEREDIT');
37 $this->widgetDescr = JText::translate('JSEARCH_TOOLS');
38 $this->widgetId = basename(__FILE__, '.php');
39
40 // define widget and icon and style name
41 $this->widgetIcon = '<i class="' . VikBookingIcons::i('address-card') . '"></i>';
42 $this->widgetStyleName = 'light-blue';
43 }
44
45 /**
46 * Preload the necessary assets.
47 *
48 * @return void
49 */
50 public function preload()
51 {
52 // load assets for contextual menu
53 $this->vbo_app->loadContextMenuAssets();
54
55 // JS lang def
56 JText::script('VBODASHSEARCHKEYS');
57 JText::script('VIKLOADING');
58 JText::script('VBDASHUPRESONE');
59 JText::script('VBOCHANNEL');
60 JText::script('VBCOUPON');
61 JText::script('VBCUSTOMERNOMINATIVE');
62 JText::script('VBO_COPY');
63 JText::script('VBO_COPIED');
64 JText::script('VBO_CONF_RM_OVERBOOKING_FLAG');
65 }
66
67 /**
68 * Custom method for this widget only to load the booking details.
69 * The method is called by the admin controller through an AJAX request.
70 * The visibility should be public, it should not exit the process, and
71 * any content sent to output will be returned to the AJAX response.
72 * In this case we return an array because this method requires "return":1.
73 */
74 public function loadBookingDetails()
75 {
76 $app = JFactory::getApplication();
77 $dbo = JFactory::getDbo();
78
79 $wrapper = $app->input->getString('wrapper', '');
80
81 $booking_key = $app->input->getString('booking_key', '');
82 $booking_id = $app->input->getString('bid', '');
83
84 if (empty($booking_key) && empty($booking_id)) {
85 // no bookings found
86 VBOHttpDocument::getInstance()->close(400, JText::translate('VBOREPORTSERRNORESERV'));
87 }
88
89 $q = $dbo->getQuery(true)
90 ->select($dbo->qn('o') . '.*')
91 ->from($dbo->qn('#__vikbooking_orders', 'o'));
92
93 if (!empty($booking_id)) {
94 // search by booking ID or OTA booking ID only
95 if (preg_match("/^[0-9]+$/", (string)$booking_id)) {
96 // only numbers could be both website and OTA
97 $q->where([
98 $dbo->qn('o.id') . ' = ' . (int)$booking_id,
99 $dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_id),
100 ], $glue = 'OR');
101 } else {
102 // alphanumeric IDs can only belong to an OTA reservation
103 $q->where($dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_id));
104 }
105 } else {
106 // search by different values
107 $q->where(1);
108 if (stripos($booking_key, 'id:') === 0) {
109 // search by ID or OTA ID
110 $seek_parts = explode('id:', $booking_key);
111 $seek_value = trim($seek_parts[1]);
112 $q->andWhere([
113 $dbo->qn('o.id') . ' = ' . $dbo->q($seek_value),
114 $dbo->qn('o.idorderota') . ' = ' . $dbo->q($seek_value),
115 ], $glue = 'OR');
116 } elseif (stripos($booking_key, 'otaid:') === 0) {
117 // search by OTA Booking ID
118 $seek_parts = explode('otaid:', $booking_key);
119 $seek_value = trim($seek_parts[1]);
120 $q->where($dbo->qn('o.idorderota') . ' = ' . $dbo->q($seek_value));
121 } elseif (stripos($booking_key, 'coupon:') === 0) {
122 // search by coupon code
123 $seek_parts = explode('coupon:', $booking_key);
124 $seek_value = trim($seek_parts[1]);
125 $q->where($dbo->qn('o.coupon') . ' LIKE ' . $dbo->q("%{$seek_value}%"));
126 } elseif (stripos($booking_key, 'name:') === 0) {
127 // search by customer nominative
128 $seek_parts = explode('name:', $booking_key);
129 $seek_value = trim($seek_parts[1]);
130 $q->leftJoin($dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $dbo->qn('co.idorder') . ' = ' . $dbo->qn('o.id'));
131 $q->leftJoin($dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('co.idcustomer'));
132 $q->where('CONCAT_WS(\' \', ' . $dbo->qn('c.first_name') . ', ' . $dbo->qn('c.last_name') . ') LIKE ' . $dbo->q("%{$seek_value}%"));
133 } else {
134 // seek for various values
135 $q->andWhere([
136 $dbo->qn('o.id') . ' = ' . $dbo->q($booking_key),
137 $dbo->qn('o.confirmnumber') . ' = ' . $dbo->q($booking_key),
138 $dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_key),
139 ], $glue = 'OR');
140 }
141 }
142
143 $q->order($dbo->qn('id') . ' DESC');
144
145 $dbo->setQuery($q, 0, 1);
146 $details = $dbo->loadAssoc();
147
148 if (!$details) {
149 // no bookings found
150 VBOHttpDocument::getInstance()->close(404, JText::translate('VBOREPORTSERRNORESERV'));
151 }
152
153 // get customer information
154 $cpin = VikBooking::getCPinInstance();
155 $customer = $cpin->getCustomerFromBooking($details['id']);
156 if ($customer && !empty($customer['country'])) {
157 if (is_file(implode(DIRECTORY_SEPARATOR, [VBO_ADMIN_PATH, 'resources', 'countries', $customer['country'] . '.png']))) {
158 $customer['country_img'] = '<img src="' . VBO_ADMIN_URI . 'resources/countries/' . $customer['country'] . '.png' . '" title="' . $customer['country'] . '" class="vbo-country-flag vbo-country-flag-left"/>';
159 }
160 }
161
162 // availability helper
163 $av_helper = VikBooking::getAvailabilityInstance();
164
165 // get rooms
166 $booking_rooms = VikBooking::loadOrdersRoomsData($details['id']);
167
168 // guests and tariffs information
169 $tars = [];
170 $guests = [];
171 $tot_adults = 0;
172 $tot_children = 0;
173 $tot_pets = 0;
174 foreach ($booking_rooms as $k => $booking_room) {
175 $guests[] = [
176 'adults' => $booking_room['adults'],
177 'children' => $booking_room['children'],
178 'pets' => $booking_room['pets'],
179 't_first_name' => $booking_room['t_first_name'],
180 't_last_name' => $booking_room['t_last_name'],
181 ];
182
183 if (!$k || !$details['split_stay']) {
184 $tot_adults += $booking_room['adults'];
185 $tot_children += $booking_room['children'];
186 $tot_pets += $booking_room['pets'];
187 }
188
189 $q = $dbo->getQuery(true)
190 ->select('*')
191 ->from($dbo->qn('#__vikbooking_dispcost'))
192 ->where($dbo->qn('id') . ' = ' . (int)$booking_room['idtar']);
193
194 $dbo->setQuery($q, 0, 1);
195 $tars[($k + 1)] = $dbo->loadAssocList();
196 }
197
198 // room stay dates in case of split stay (or modified room nights)
199 $room_stay_dates = [];
200 $room_stay_records = [];
201 if ($details['split_stay']) {
202 if ($details['status'] == 'confirmed') {
203 $room_stay_dates = $av_helper->loadSplitStayBusyRecords($details['id']);
204 } else {
205 $room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $details['id'], []);
206 }
207 } elseif (!$details['split_stay'] && $details['roomsnum'] > 1 && $details['days'] > 1 && $details['status'] == 'confirmed') {
208 // load the occupied stay dates for each room in case they were modified
209 $room_stay_records = $av_helper->loadSplitStayBusyRecords($details['id']);
210 }
211
212 // currency
213 $otacurrency = !empty($details['channel']) && !empty($details['chcurrency']) ? $details['chcurrency'] : '';
214 $currencysymb = VikBooking::getCurrencySymb();
215
216 // channel name and reservation ID
217 $otachannel_name = JText::translate('VBORDFROMSITE');
218 $otachannel_bid = '';
219 if (!empty($details['channel'])) {
220 $channelparts = explode('_', $details['channel']);
221 $otachannel = isset($channelparts[1]) && strlen((string)$channelparts[1]) ? $channelparts[1] : ucwords($channelparts[0]);
222 $otachannel_name = $otachannel;
223 $otachannel_bid = !empty($details['idorderota']) ? $details['idorderota'] : '';
224 }
225
226 // readable dates
227 $checkin_info = getdate($details['checkin']);
228 $checkin_wday = JText::translate('VB'.strtoupper(substr($checkin_info['weekday'], 0, 3)));
229 $checkout_info = getdate($details['checkout']);
230 $checkout_wday = JText::translate('VB'.strtoupper(substr($checkout_info['weekday'], 0, 3)));
231 $checkin_read_dt = $checkin_wday . ', ' . implode(' ', [$checkin_info['mday'], VikBooking::sayMonth($checkin_info['mon']), $checkin_info['year'], date('H:i', $details['checkin'])]);
232 $checkout_read_dt = $checkout_wday . ', ' . implode(' ', [$checkout_info['mday'], VikBooking::sayMonth($checkout_info['mon']), $checkout_info['year'], date('H:i', $details['checkout'])]);
233
234 // check for special requests
235 $special_requests = VBOModelReservation::getInstance($details)->extractSpecialRequests();
236
237 // check for guest messaging
238 $messaging_supported = class_exists('VCMChatMessaging');
239 $tot_guest_messages = 0;
240 $tot_unread_messages = 0;
241 $last_guest_messages = [];
242 if ($messaging_supported) {
243 $messaging_handler = VCMChatMessaging::getInstance($details);
244 $tot_guest_messages = $messaging_handler->countBookingGuestMessages();
245 if ($tot_guest_messages) {
246 $tot_unread_messages = $messaging_handler->countBookingGuestMessages($unread = true);
247 if (method_exists($messaging_handler, 'loadBookingLatestMessages')) {
248 /**
249 * At the moment we save a query and we do not display
250 * a snapshot of the latest guest messages.
251 */
252 // $last_guest_messages = $messaging_handler->loadBookingLatestMessages($details['id'], 0, 5);
253 }
254 }
255 }
256
257 // load the Channel Manager notifications for this booking, if any
258 $cm_notifications = [];
259 $cm_channels = [];
260 if (class_exists('VikChannelManager') && VikChannelManager::isAvailabilityRequest($api_channel = true)) {
261 list($cm_notifications, $cm_channels) = $this->loadChannelManagerNotifications($details);
262 }
263
264 // check if we got data by clicking on a push/web notification
265 $from_push = $this->options()->gotPushData();
266
267 // start output buffering
268 ob_start();
269
270 ?>
271 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact" data-bookingid="<?php echo $details['id']; ?>">
272 <div class="vbo-params-wrap">
273 <div class="vbo-params-container">
274
275 <div class="vbo-params-block">
276 <div class="vbo-param-container">
277 <div class="vbo-param-setting">
278 <?php
279 $ch_logo_obj = VikBooking::getVcmChannelsLogo($details['channel'], true);
280 $channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
281 $custpic_used = false;
282 ?>
283 <div class="vbo-customer-info-box">
284 <div class="vbo-customer-info-box-avatar vbo-customer-avatar-medium">
285 <span>
286 <?php
287 if (!empty($channel_logo)) {
288 // channel logo has got the highest priority
289 ?>
290 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>">
291 <img src="<?php echo $channel_logo; ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');" />
292 </span>
293 <?php
294 } elseif (!empty($customer['pic'])) {
295 // customer profile picture
296 $custpic_used = true;
297 ?>
298 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>">
299 <img src="<?php echo strpos($customer['pic'], 'http') === 0 ? $customer['pic'] : VBO_SITE_URI . 'resources/uploads/' . $customer['pic']; ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');" />
300 </span>
301 <?php
302 } else {
303 // we use an icon as fallback
304 ?>
305 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');"><?php VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon'); ?></span>
306 <?php
307 }
308 ?>
309 </span>
310 </div>
311 </div>
312 <span class="label label-info"><?php echo JText::translate('VBDASHUPRESONE') . ' ' . $details['id']; ?></span>
313 <?php
314 if (!empty($otachannel_bid)) {
315 ?>
316 <span class="label label-info"><?php echo $otachannel_name . ' ' . $otachannel_bid; ?></span>
317 <?php
318 }
319 $status_type = '';
320 if (!empty($details['type'])) {
321 $status_type = JText::translate('VBO_BTYPE_' . strtoupper($details['type']));
322 if (!strcasecmp($details['type'], 'overbooking')) {
323 $status_type = '<span class="label label-error vbo-label-nested vbo-label-overbooking" onclick="vboWidgetBookDetsToggleOverbooking(\'' . $wrapper . '\');">' . $status_type . '</span>';
324 }
325 $status_type .= ' / ';
326 }
327 $extra_status = $details['refund'] > 0 ? ' / ' . JText::translate('VBO_STATUS_REFUNDED') : '';
328 if ($details['status'] == "confirmed") {
329 $saystaus = '<span class="label label-success">' . $status_type . JText::translate('VBCONFIRMED') . $extra_status . '</span>';
330 } elseif ($details['status'] == "standby") {
331 $saystaus = '<span class="label label-warning">' . $status_type . JText::translate('VBSTANDBY') . $extra_status . '</span>';
332 } else {
333 $saystaus = '<span class="label label-error">' . $status_type . JText::translate('VBCANCELLED') . $extra_status . '</span>';
334 }
335 echo $saystaus;
336 if ($details['closure']) {
337 ?>
338 <span class="label label-error"><?php VikBookingIcons::e('ban'); ?> <?php echo JText::translate('VBDBTEXTROOMCLOSED'); ?></span>
339 <?php
340 }
341 ?>
342 </div>
343 </div>
344
345 <?php
346 if (!$customer && !$details['closure'] && !empty($details['custdata'])) {
347 ?>
348 <div class="vbo-param-container">
349 <div class="vbo-param-setting"><?php echo $details['custdata']; ?></div>
350 </div>
351 <?php
352 }
353 ?>
354
355 <div class="vbo-param-container">
356 <div class="vbo-param-label">
357 <div class="vbo-customer-info-box">
358 <div class="vbo-customer-info-box-name">
359 <?php echo (isset($customer['country_img']) ? $customer['country_img'] . ' ' : '') . ($customer ? ltrim($customer['first_name'] . ' ' . $customer['last_name']) : JText::translate('VBPVIEWORDERSPEOPLE')); ?>
360 </div>
361 <?php
362 if (!$custpic_used && !empty($customer['pic'])) {
363 $customer_name = $customer ? ltrim($customer['first_name'] . ' ' . $customer['last_name']) : '';
364 ?>
365 <div class="vbo-customer-info-box-avatar vbo-customer-avatar-small vbo-widget-bookdets-cpic-zoom">
366 <span>
367 <img src="<?php echo strpos($customer['pic'], 'http') === 0 ? $customer['pic'] : VBO_SITE_URI . 'resources/uploads/' . $customer['pic']; ?>" data-caption="<?php echo JHtml::fetch('esc_attr', $customer_name); ?>" />
368 </span>
369 </div>
370 <?php
371 }
372 ?>
373 </div>
374 </div>
375 <div class="vbo-param-setting">
376 <?php
377 $guest_counters = [];
378 if ($tot_adults) {
379 $guest_counters[] = $tot_adults . ' ' . JText::translate(($tot_adults > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'));
380 }
381 if ($tot_children) {
382 $guest_counters[] = $tot_children . ' ' . JText::translate(($tot_children > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD'));
383 }
384 if ($tot_pets) {
385 $guest_counters[] = $tot_pets . ' ' . JText::translate(($tot_pets > 1 ? 'VBO_PETS' : 'VBO_PET'));
386 }
387 echo implode(', ', $guest_counters);
388 ?>
389 </div>
390 </div>
391
392 <div class="vbo-param-container">
393 <div class="vbo-param-label"><?php echo JText::translate('VBPICKUPAT'); ?></div>
394 <div class="vbo-param-setting">
395 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $checkin_read_dt); ?>"><?php echo date(str_replace("/", $this->datesep, $this->df), $details['checkin']); ?></span>
396 </div>
397 </div>
398
399 <div class="vbo-param-container">
400 <div class="vbo-param-label"><?php echo JText::translate('VBRELEASEAT'); ?></div>
401 <div class="vbo-param-setting">
402 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $checkout_read_dt); ?>"><?php echo date(str_replace("/", $this->datesep, $this->df), $details['checkout']); ?></span>
403 <span>(<?php echo $details['days'] . ' ' . ($details['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?>)</span>
404 <?php
405 if ($details['split_stay']) {
406 ?>
407 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_SPLIT_STAY')); ?>"><?php VikBookingIcons::e('random'); ?></span>
408 <?php
409 }
410 ?>
411 </div>
412 </div>
413
414 <div class="vbo-param-container">
415 <div class="vbo-param-label"><?php echo JText::translate('VBOINVCREATIONDATE'); ?></div>
416 <div class="vbo-param-setting">
417 <?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i', $details['ts']); ?>
418 </div>
419 </div>
420
421 <?php
422 if ($special_requests) {
423 ?>
424 <div class="vbo-param-container">
425 <div class="vbo-param-setting">
426 <blockquote class="vbo-booking-special-requests"><?php echo $special_requests; ?></blockquote>
427 </div>
428 </div>
429 <?php
430 }
431
432 if ($tot_unread_messages) {
433 ?>
434 <div class="vbo-param-container">
435 <div class="vbo-param-label">&nbsp;</div>
436 <div class="vbo-param-setting">
437 <button type="button" class="btn vbo-config-btn vbo-btn-icon-right" data-hasguestmessages="1">
438 <span><?php echo JText::translate('VBO_GUEST_MESSAGING'); ?></span>
439 <span class="vbo-bookings-guestmessages-bubble-cont vbo-admin-tipsicon"><i class="<?php echo VikBookingIcons::i('comments'); ?>" data-message-count="<?php echo $tot_unread_messages; ?>"></i></span>
440 </button>
441 </div>
442 </div>
443 <?php
444 } elseif ($tot_guest_messages) {
445 ?>
446 <div class="vbo-param-container">
447 <div class="vbo-param-label">&nbsp;</div>
448 <div class="vbo-param-setting">
449 <button type="button" class="btn vbo-config-btn vbo-btn-icon-right" data-hasguestmessages="1">
450 <span><?php echo JText::translate('VBO_GUEST_MESSAGING'); ?> <?php VikBookingIcons::e('comment-dots'); ?></span>
451 </button>
452 </div>
453 </div>
454 <?php
455 }
456 ?>
457 </div>
458
459 <div class="vbo-params-block">
460 <?php
461 foreach ($booking_rooms as $ind => $booking_room) {
462 $num = $ind + 1;
463 $room_icon = $details['split_stay'] && $ind > 0 ? 'random' : 'bed';
464 ?>
465 <div class="vbo-param-container">
466 <div class="vbo-param-label">
467 <span><?php VikBookingIcons::e($room_icon); ?> <?php echo $booking_room['room_name']; ?></span>
468 <?php
469 // room sub-unit
470 $room_number = '';
471 if (!empty($booking_room['roomindex']) && !$details['closure'] && !empty($booking_room['params'])) {
472 $room_params = json_decode($booking_room['params'], true);
473 $arr_features = [];
474 if (is_array($room_params) && !empty($room_params['features']) && is_array($room_params['features'])) {
475 // parse distinctive features
476 foreach ($room_params['features'] as $rind => $rfeatures) {
477 if ($rind != $booking_room['roomindex']) {
478 continue;
479 }
480 foreach ($rfeatures as $fname => $fval) {
481 if (strlen((string)$fval)) {
482 $room_number = '#' . $fval;
483 break 2;
484 }
485 }
486 }
487 }
488 }
489 if ($room_number) {
490 ?>
491 <div>
492 <small><?php echo $room_number; ?></small>
493 </div>
494 <?php
495 }
496 if ($details['roomsnum'] > 1 && !$details['split_stay'] && isset($guests[$ind])) {
497 // print guest details for this room
498 $room_guest_counters = [];
499 if (!empty($guests[$ind]['t_first_name']) || !empty($guests[$ind]['t_last_name'])) {
500 $room_guest_counters[] = trim($guests[$ind]['t_first_name'] . ' ' . $guests[$ind]['t_last_name']);
501 }
502 if ($guests[$ind]['adults']) {
503 $room_guest_counters[] = $guests[$ind]['adults'] . ' ' . JText::translate(($guests[$ind]['adults'] > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'));
504 }
505 if ($guests[$ind]['children']) {
506 $room_guest_counters[] = $guests[$ind]['children'] . ' ' . JText::translate(($guests[$ind]['children'] > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD'));
507 }
508 if ($guests[$ind]['pets']) {
509 $room_guest_counters[] = $guests[$ind]['pets'] . ' ' . JText::translate(($guests[$ind]['pets'] > 1 ? 'VBO_PETS' : 'VBO_PET'));
510 }
511 ?>
512 <div>
513 <small><?php echo implode(', ', $room_guest_counters); ?></small>
514 </div>
515 <?php
516 }
517 ?>
518 </div>
519 <div class="vbo-param-setting">
520 <div class="vbo-widget-bookdets-roomrate">
521 <?php
522 $active_rplan_id = 0;
523 if (!empty($details['pkg']) || $booking_room['cust_cost'] > 0) {
524 if (!empty($booking_room['pkg_name'])) {
525 // package
526 echo $booking_room['pkg_name'];
527 } else {
528 // custom cost can have an OTA Rate Plan name
529 if (!empty($booking_room['otarplan'])) {
530 echo ucwords($booking_room['otarplan']);
531 } else {
532 echo JText::translate('VBOROOMCUSTRATEPLAN');
533 }
534 }
535 } elseif (!empty($tars[$num]) && !empty($tars[$num][0]['idprice'])) {
536 $active_rplan_id = $tars[$num][0]['idprice'];
537 echo VikBooking::getPriceName($tars[$num][0]['idprice']);
538 } elseif (!empty($booking_room['otarplan'])) {
539 echo ucwords($booking_room['otarplan']);
540 } elseif (!$details['closure']) {
541 echo JText::translate('VBOROOMNORATE');
542 }
543 ?>
544 </div>
545 <?php
546 // meals included in the room rate
547 if (!empty($booking_room['meals'])) {
548 // display included meals defined at room-reservation record
549 $included_meals = VBOMealplanManager::getInstance()->roomRateIncludedMeals($booking_room);
550 } else {
551 // fetch default included meals in the selected rate plan
552 $included_meals = $active_rplan_id ? VBOMealplanManager::getInstance()->ratePlanIncludedMeals($active_rplan_id) : [];
553 }
554 if (!$included_meals && empty($booking_room['meals']) && !empty($details['idorderota']) && !empty($details['channel']) && !empty($details['custdata'])) {
555 // attempt to fetch the included meal plans from the raw customer data or OTA reservation and room
556 $included_meals = VBOMealplanManager::getInstance()->otaDataIncludedMeals($details, $booking_room);
557 }
558 if ($included_meals) {
559 ?>
560 <div class="vbo-widget-bookdets-roommeals vbo-wider-badges-wrap">
561 <?php
562 foreach ($included_meals as $included_meal) {
563 ?>
564 <span class="badge badge-info"><?php echo $included_meal; ?></span>
565 <?php
566 }
567 ?>
568 </div>
569 <?php
570 }
571
572 // check for split-stay or modified stay-dates
573 if ($details['split_stay'] && $room_stay_dates && isset($room_stay_dates[$ind]) && $room_stay_dates[$ind]['idroom'] == $booking_room['idroom']) {
574 // print split stay information for this room
575 $room_stay_checkin = !empty($room_stay_dates[$ind]['checkin_ts']) ? $room_stay_dates[$ind]['checkin_ts'] : $room_stay_dates[$ind]['checkin'];
576 $room_stay_checkout = !empty($room_stay_dates[$ind]['checkout_ts']) ? $room_stay_dates[$ind]['checkout_ts'] : $room_stay_dates[$ind]['checkout'];
577 $room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout);
578 ?>
579 <div class="vbo-cal-splitstay-details vbo-bookdet-splitstay-details">
580 <div class="vbo-cal-splitstay-dates">
581 <span class="vbo-cal-splitstay-room-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo $room_stay_nights . ' ' . ($room_stay_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?></span>
582 <span class="vbo-cal-splitstay-dates-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkin); ?></span>
583 <span class="vbo-cal-splitstay-dates-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkout); ?></span>
584 </div>
585 </div>
586 <?php
587 } elseif (!$details['split_stay'] && $room_stay_records && isset($room_stay_records[$ind]) && $room_stay_records[$ind]['idroom'] == $booking_room['idroom']) {
588 // print modified stay dates information for this room
589 $room_stay_checkin = $room_stay_records[$ind]['checkin'];
590 $room_stay_checkout = $room_stay_records[$ind]['checkout'];
591 $room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout);
592 if ($room_stay_checkin != $details['checkin'] || $room_stay_checkout != $details['checkout']) {
593 ?>
594 <div class="vbo-cal-splitstay-details vbo-bookdet-splitstay-details vbo-bookdet-roomdatesmod-details">
595 <div class="vbo-cal-splitstay-dates">
596 <span class="vbo-cal-splitstay-room-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo $room_stay_nights . ' ' . ($room_stay_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?></span>
597 <span class="vbo-cal-splitstay-dates-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkin); ?></span>
598 <span class="vbo-cal-splitstay-dates-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkout); ?></span>
599 </div>
600 </div>
601 <?php
602 }
603 }
604 ?>
605 </div>
606 </div>
607 <?php
608 }
609 ?>
610 </div>
611
612 <?php
613 if (!$details['closure']) {
614 ?>
615 <div class="vbo-params-block">
616 <div class="vbo-param-container">
617 <div class="vbo-param-label"><?php echo JText::translate('VBEDITORDERNINE'); ?></div>
618 <div class="vbo-param-setting">
619 <strong><?php echo ($otacurrency ? "({$otacurrency}) " : '') . $currencysymb . ' ' . VikBooking::numberFormat($details['total']); ?></strong>
620 </div>
621 </div>
622 <?php
623 if ($details['totpaid'] > 0) {
624 ?>
625 <div class="vbo-param-container">
626 <div class="vbo-param-label"><?php echo JText::translate('VBPEDITBUSYTOTPAID'); ?></div>
627 <div class="vbo-param-setting">
628 <span><?php echo $currencysymb . ' ' . VikBooking::numberFormat($details['totpaid']); ?></span>
629 </div>
630 </div>
631 <?php
632 }
633 ?>
634 </div>
635 <?php
636 }
637
638 // check for administrator notes
639 if (!empty($details['adminnotes'])) {
640 ?>
641 <div class="vbo-params-fieldset">
642 <div class="vbo-params-fieldset-label"><?php echo JText::translate('VBADMINNOTESTOGGLE'); ?></div>
643 <div class="vbo-params-block">
644 <div class="vbo-param-container">
645 <div class="vbo-param-setting">
646 <blockquote class="vbo-booking-admin-notes"><?php echo nl2br($details['adminnotes']); ?></blockquote>
647 </div>
648 </div>
649 </div>
650 </div>
651 <?php
652 }
653
654 // check if this booking has got a reminder
655 $reminders_helper = VBORemindersHelper::getInstance();
656 $has_reminders = $reminders_helper->bookingHasReminder($details['id']);
657 $few_reminders = [];
658 if ($has_reminders) {
659 $few_reminders = $reminders_helper->loadReminders([
660 'idorder' => $details['id'],
661 'onlyorder' => 1,
662 'completed' => 1,
663 'expired' => 1,
664 ], 0, 5);
665 }
666 ?>
667 <div class="vbo-params-fieldset">
668 <div class="vbo-params-fieldset-label"><?php echo JText::translate('VBO_W_REMINDERS_TITLE'); ?></div>
669 <div class="vbo-params-block">
670 <?php
671 foreach ($few_reminders as $reminder) {
672 $diff_data = [];
673 if (!empty($reminder->duedate)) {
674 // calculate distance to expiration date from today
675 $diff_data = $reminders_helper->relativeDatesDiff($reminder->duedate);
676 }
677 ?>
678 <div class="vbo-param-container">
679 <div class="vbo-param-setting">
680 <div class="vbo-widget-reminders-record-info">
681 <div class="vbo-widget-reminders-record-txt">
682 <div class="vbo-widget-reminder-title"><?php echo htmlspecialchars($reminder->title); ?></div>
683 <?php
684 if (!empty($reminder->descr)) {
685 ?>
686 <div class="vbo-widget-reminder-descr"><?php echo htmlspecialchars($reminder->descr); ?></div>
687 <?php
688 }
689 ?>
690 </div>
691 <div class="vbo-widget-reminders-record-due">
692 <?php
693 if (!empty($reminder->duedate)) {
694 ?>
695 <div class="vbo-widget-reminders-record-due-datetime">
696 <span class="vbo-widget-reminders-record-due-date">
697 <span title="<?php echo $reminder->duedate; ?>"><?php echo $diff_data['relative']; ?></span>
698 </span>
699 <?php
700 if ($reminder->usetime) {
701 ?>
702 <span class="vbo-widget-reminders-record-due-time">
703 <span><?php echo $diff_data['date_a']->format('H:i'); ?></span>
704 </span>
705 <?php
706 }
707 ?>
708 </div>
709 <?php
710 }
711 ?>
712 </div>
713 </div>
714 </div>
715 </div>
716 <?php
717 }
718 ?>
719 <div class="vbo-param-container">
720 <div class="vbo-param-label">&nbsp;</div>
721 <div class="vbo-param-setting">
722 <?php
723 if ($has_reminders) {
724 ?>
725 <button type="button" class="btn vbo-config-btn" data-hasreminders="1"><?php VikBookingIcons::e('bell'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
726 <?php
727 } else {
728 ?>
729 <button type="button" class="btn btn-success" data-hasreminders="0"><?php VikBookingIcons::e('plus-circle'); ?> <?php echo JText::translate('VBO_ADD_NEW'); ?></button>
730 <?php
731 }
732 ?>
733 </div>
734 </div>
735 </div>
736 </div>
737
738 <?php
739 if ($customer) {
740 /**
741 * Allow to manage the customer documents and display the registration status.
742 *
743 * @since 1.16.10 (J) - 1.6.10 (WP)
744 */
745
746 $checked_status = null;
747 $checked_cls = 'label-info';
748 if (!$details['closure'] && $details['status'] == 'confirmed') {
749 switch ($details['checked']) {
750 case -1:
751 $checked_status = JText::translate('VBOCHECKEDSTATUSNOS');
752 $checked_cls = 'label-danger';
753 break;
754 case 1:
755 $checked_status = JText::translate('VBOCHECKEDSTATUSIN');
756 $checked_cls = 'label-success';
757 break;
758 case 2:
759 $checked_status = JText::translate('VBOCHECKEDSTATUSOUT');
760 $checked_cls = 'label-warning';
761 break;
762 default:
763 if (!empty($customer['pax_data'])) {
764 // pre check-in performed via front-end
765 $checked_status = JText::translate('VBOCHECKEDSTATUSPRECHECKIN');
766 }
767 break;
768 }
769 }
770 ?>
771 <div class="vbo-params-fieldset vbo-widget-bookdets-customer-docs">
772 <div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOCUSTOMERDOCUMENTS'); ?></div>
773 <div class="vbo-params-block">
774 <?php
775 if ($checked_status) {
776 // display the registration status
777 ?>
778 <div class="vbo-param-container">
779 <div class="vbo-param-label"><?php echo JText::translate('VBOCHECKEDSTATUS'); ?></div>
780 <div class="vbo-param-setting">
781 <span class="label <?php echo $checked_cls; ?>"><?php echo $checked_status; ?></span>
782 </div>
783 </div>
784 <?php
785 }
786 ?>
787 <div class="vbo-param-container">
788 <div class="vbo-param-setting">
789 <?php
790 /**
791 * Render the customer-dropfiles layout to handle the customer documents.
792 */
793 $layout_data = [
794 'caller' => 'widget',
795 'customer' => $customer,
796 ];
797
798 // render the permissions layout
799 echo JLayoutHelper::render('customer.dropfiles', $layout_data, null, [
800 'component' => 'com_vikbooking',
801 'client' => 'administrator',
802 ]);
803 ?>
804 </div>
805 </div>
806 </div>
807 </div>
808 <?php
809 }
810
811 // booking history
812 $history_obj = VikBooking::getBookingHistoryInstance($details['id']);
813 $history_list = $history_obj->loadHistory();
814 if ($history_list) {
815 ?>
816 <div class="vbo-params-fieldset vbo-widget-bookdets-history-list">
817 <div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOBOOKHISTORYTAB'); ?></div>
818 <div class="vbo-params-block">
819 <?php
820 $max_display_records = 3;
821 foreach ($history_list as $hind => $hist) {
822 $html_descr = strpos($hist['descr'], '<') !== false ? $hist['descr'] : nl2br($hist['descr']);
823 $text_descr = strip_tags($hist['descr']);
824 $text_lines = preg_split("/[\r\n]/", $text_descr);
825 $read_more = false;
826 if ($text_lines && count($text_lines) > 1) {
827 $first_line = $text_lines[0];
828 unset($text_lines[0]);
829 if (strlen($first_line) < strlen(implode('', $text_lines))) {
830 $text_descr = $first_line;
831 $read_more = true;
832 }
833 }
834
835 $tip_info = [
836 JText::translate('VBOBOOKHISTORYLBLTOT') . ': ' . $currencysymb . ' ' . VikBooking::numberFormat($hist['total']),
837 JText::translate('VBOBOOKHISTORYLBLTPAID') . ': ' . $currencysymb . ' ' . VikBooking::numberFormat($hist['totpaid']),
838 ];
839 ?>
840 <div class="vbo-param-container vbo-widget-bookdets-history-record" style="<?php echo $hind >= $max_display_records ? 'display: none;' : ''; ?>">
841 <div class="vbo-param-label">
842 <div>
843 <span class="vbo-tooltip vbo-tooltip-top vbo-widget-bookdets-history-evname" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', implode(', ', $tip_info)); ?>"><?php echo $history_obj->validType($hist['type'], true); ?></span>
844 </div>
845 <div>
846 <span class="vbo-widget-bookdets-history-evdate"><?php echo JHtml::fetch('date', $hist['dt'], 'Y-m-d H:i:s'); ?></span>
847 </div>
848 </div>
849 <div class="vbo-param-setting vbo-widget-bookdets-history-descr-wrap">
850 <?php
851 if ($read_more) {
852 ?>
853 <div class="vbo-widget-bookdets-history-descr-txt">
854 <span><?php echo $text_descr; ?></span>
855 <div>
856 <span class="vbo-tooltip vbo-tooltip-top vbo-widget-bookdets-history-descr-more" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_READ_MORE')); ?>" onclick="vboWidgetBookDetsReadFullHistory(this);">
857 <a href="javascript:void(0);">[...]</a>
858 </span>
859 </div>
860 </div>
861 <?php
862 }
863 ?>
864 <div class="vbo-widget-bookdets-history-descr-html" style="<?php echo $read_more ? 'display: none;' : ''; ?>"><?php echo $html_descr; ?></div>
865 </div>
866 </div>
867 <?php
868 }
869
870 if (count($history_list) > $max_display_records) {
871 // display button to display all history records
872 ?>
873 <div class="vbo-param-container vbo-widget-bookdets-history-showall">
874 <div class="vbo-param-label">&nbsp;</div>
875 <div class="vbo-param-setting">
876 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsHistoryAll('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('history'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
877 </div>
878 </div>
879 <?php
880 }
881 ?>
882 </div>
883 </div>
884 <?php
885 }
886
887 // channel manager notifications and channels involved
888 if ($cm_notifications) {
889 ?>
890 <div class="vbo-params-fieldset">
891 <div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOBOOKHISTORYTCM'); ?></div>
892 <div class="vbo-params-block">
893 <?php
894 $max_display_notifications = 2;
895 foreach ($cm_notifications as $notif_ind => $cm_notification) {
896 $badge_class = 'success';
897 $badge_icon = 'check-circle';
898 if ($cm_notification['type'] == 0) {
899 $badge_class = 'error';
900 $badge_icon = 'times-circle';
901 } elseif ($cm_notification['type'] == 2) {
902 $badge_class = 'warning';
903 $badge_icon = 'exclamation-triangle';
904 }
905 ?>
906 <div class="vbo-params-fieldset vbo-widget-bookdets-cmnotifs-record" style="<?php echo $notif_ind >= $max_display_notifications ? 'display: none;' : ''; ?>">
907 <div class="vbo-params-fieldset-label">
908 <span class="label label-<?php echo $badge_class; ?>">
909 <?php VikBookingIcons::e($badge_icon); ?>
910 <span><?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i:s', $cm_notification['ts']); ?></span>
911 </span>
912 </div>
913 <div class="vbo-params-block vbo-params-block-compact">
914 <?php
915 foreach ($cm_notification['children'] as $channel_key => $channel_notifs) {
916 $logo_obj = VikBooking::getVcmChannelsLogo($cm_channels[$channel_key]['name'], true);
917 $channel_logo = $logo_obj ? $logo_obj->getSmallLogoURL() : '';
918 if (!$channel_logo) {
919 continue;
920 }
921 // build readable channel name
922 $raw_ch_name = (string)$cm_channels[$channel_key]['name'];
923 $lower_name = strtolower($raw_ch_name);
924 $lower_name = preg_replace("/hotel$/", ' hotel', $lower_name);
925 $channel_name = ucwords(preg_replace("/api$/", '', $lower_name));
926 ?>
927 <div class="vbo-param-container">
928 <div class="vbo-param-label">
929 <div class="vbo-customer-info-box">
930 <div class="vbo-customer-info-box-avatar vbo-customer-avatar-small">
931 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $channel_name); ?>">
932 <img src="<?php echo $channel_logo; ?>" />
933 </span>
934 </div>
935 </div>
936 </div>
937 <div class="vbo-param-setting">
938 <?php
939 $channel_responses = [];
940 foreach ($channel_notifs as $resp_ind => $channel_notif) {
941 // build update result string
942 $upd_result = (string)$channel_notif['cont'];
943 // strip "e4j." from the beginning of the string
944 $upd_result = preg_replace("/^e4j./i", '', $upd_result);
945 // strip ending hotel code "{hotelid 000000}"
946 $upd_result = preg_replace("/\s?\{hotelid\s?[0-9A-Z]+\}$/i", '', $upd_result);
947
948 // default values for extra logging
949 $log_result = '';
950 $ruid_codes = [];
951
952 // we expect this to be a channel update result
953 if ($resp_ind) {
954 // parse the response type
955 if (preg_match("/^OK\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
956 // successful response example ("OK.Airbnb.AR_RS")
957 $upd_result = trim(str_replace($matches[0], "OK {$channel_name}", $upd_result));
958 } elseif (preg_match("/^warning\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
959 // warning response example ("warning.Airbnb.AR_RS")
960 $upd_result = trim(str_replace($matches[0], $channel_name, $upd_result));
961 $log_result = 'warning';
962 } elseif (preg_match("/^error\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
963 // error response example ("error.Airbnb.AR_RS")
964 $upd_result = trim(str_replace($matches[0], $channel_name, $upd_result));
965 $log_result = 'error';
966 }
967
968 // check for RUID codes (there could be more than one)
969 if (preg_match_all("/RUID: \[.+\]/", $upd_result, $ruid_matches)) {
970 $ruid_codes = is_array($ruid_matches[0]) ? $ruid_matches[0] : [];
971 foreach ($ruid_matches[0] as $ruid_log) {
972 // strip log from update result because it will be displayed separately
973 $upd_result = str_replace($ruid_log, '', $upd_result);
974 }
975 }
976 }
977
978 if (in_array($upd_result, $channel_responses)) {
979 // do not display duplicate responses from the same channel (i.e. duplicate unexpected notifications)
980 continue;
981 }
982
983 // register channel response
984 $channel_responses[] = $upd_result;
985
986 ?>
987 <div class="vbo-widget-bookdets-cm-updresult">
988 <?php
989 // arrow icon
990 VikBookingIcons::e(($resp_ind === 0 ? 'long-arrow-right' : 'long-arrow-left'));
991
992 // print a label in case of errors or warning
993 if ($log_result) {
994 ?>
995 <span class="label label-<?php echo $log_result == 'warning' ? 'warning' : 'error'; ?>"><?php echo $log_result; ?></span>
996 <?php
997 }
998 ?>
999 <span class="vbo-widget-bookdets-cm-updresult-log"><?php echo $upd_result; ?></span>
1000 <?php
1001 // check for extra logs (RUIDs)
1002 if ($ruid_codes) {
1003 ?>
1004 <span class="vbo-widget-bookdets-cm-ruids">
1005 <button class="btn btn-small vbo-btn-icon-right" type="button" onclick="vboWidgetBookDetsCopyRuids(this);">
1006 <span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_COPY')); ?>">RUID <?php VikBookingIcons::e('copy'); ?></span>
1007 </button>
1008 </span>
1009 <textarea class="vbo-textarea-copyable"><?php echo htmlentities(implode("\n", $ruid_codes)); ?></textarea>
1010 <?php
1011 }
1012 ?>
1013 </div>
1014 <?php
1015 }
1016
1017 // check if the channel was notified but did not respond
1018 if ($channel_responses && (count($channel_responses) % 2) != 0) {
1019 ?>
1020 <div><?php echo str_repeat('-', 6); ?></div>
1021 <?php
1022 }
1023 ?>
1024 </div>
1025 </div>
1026 <?php
1027 }
1028 ?>
1029 </div>
1030 </div>
1031 <?php
1032 }
1033
1034 // check if some notifications were hidden
1035 if (count($cm_notifications) > $max_display_notifications) {
1036 // display button to show all channel manager notifications
1037 ?>
1038 <div class="vbo-param-container vbo-widget-bookdets-cmnotifs-showall">
1039 <div class="vbo-param-label">&nbsp;</div>
1040 <div class="vbo-param-setting">
1041 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsCmnotifsAll('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('network-wired'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
1042 </div>
1043 </div>
1044 <?php
1045 }
1046 ?>
1047 </div>
1048 </div>
1049 <?php
1050 }
1051 ?>
1052
1053 <div class="vbo-param-container">
1054 <div class="vbo-param-setting">
1055 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');"><?php echo JText::translate('VBOVIEWBOOKINGDET'); ?></button>
1056 <button type="button" class="btn" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>', 'edit');"><?php echo JText::translate('VBMODRES'); ?></button>
1057 </div>
1058 </div>
1059
1060 </div>
1061 </div>
1062 </div>
1063
1064 <script type="text/javascript">
1065 jQuery(function() {
1066
1067 jQuery('#<?php echo $wrapper; ?>').find('button[data-hasreminders]').on('click', function() {
1068 // check if the clicked button indicates that there are reminders
1069 let has_reminders = jQuery(this).attr('data-hasreminders') == '1';
1070
1071 // render the reminders widget by injecting the proper options
1072 VBOCore.handleDisplayWidgetNotification({widget_id: 'reminders'}, {
1073 bid: <?php echo $details['id']; ?>,
1074 action: has_reminders ? '' : 'add_new',
1075 completed: has_reminders ? 1 : 0,
1076 expired: has_reminders ? 1 : 0,
1077 modal_options: {
1078 /**
1079 * Overwrite modal options for rendering the admin widget.
1080 * We need to use a different suffix in case this current widget was
1081 * also rendered within a modal, or it would get dismissed in favour
1082 * of the newly opened admin widget. Prepending to body is needed
1083 * because the reminders widget use the datepicker calendar.
1084 */
1085 suffix: 'widget_modal_inner_reminders',
1086 body_prepend: true,
1087 },
1088 });
1089 });
1090
1091 jQuery('#<?php echo $wrapper; ?>').find('button[data-hasguestmessages="1"]').on('click', function() {
1092 // render the guest messages widget by injecting the proper options
1093 VBOCore.handleDisplayWidgetNotification({widget_id: 'guest_messages'}, {
1094 bid: <?php echo $details['id']; ?>,
1095 modal_options: {
1096 /**
1097 * Overwrite modal options for rendering the admin widget.
1098 * We need to use a different suffix in case this current widget was
1099 * also rendered within a modal, or it would get dismissed in favour
1100 * of the newly opened admin widget.
1101 */
1102 suffix: 'widget_modal_inner_guest_messages',
1103 },
1104 });
1105 });
1106
1107 jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookdets-cpic-zoom').find('img').on('click', function() {
1108 // display modal
1109 VBOCore.displayModal({
1110 suffix: 'zoom-image',
1111 title: jQuery(this).attr('data-caption'),
1112 body: jQuery(this).clone(),
1113 });
1114 });
1115
1116 // check for action button
1117 var vbo_widget_bookdets_action_btn = jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookdets-actionbtn');
1118 if (vbo_widget_bookdets_action_btn.length) {
1119 vbo_widget_bookdets_action_btn.attr('href', vbo_widget_bookdets_action_btn.attr('href').replace('%d', '<?php echo $details['id']; ?>'));
1120 vbo_widget_bookdets_action_btn.closest('.vbo-widget-push-notification-action').show();
1121 }
1122
1123 });
1124 <?php
1125 if ($from_push) {
1126 // emit the event to read all notifications in the current context when clicking on a push/web notification
1127 ?>
1128 setTimeout(() => {
1129 VBOCore.emitEvent('vbo-nc-read-notifications', {
1130 criteria: {
1131 group: '<?php echo !empty($details['idorderota']) && !empty($details['channel']) ? 'otas' : 'website'; ?>',
1132 idorder: '<?php echo $details['id']; ?>',
1133 }
1134 });
1135 }, 200);
1136 <?php
1137 }
1138 ?>
1139 </script>
1140 <?php
1141
1142 // get the HTML buffer
1143 $html_content = ob_get_contents();
1144 ob_end_clean();
1145
1146 // return an associative array of values
1147 return [
1148 'html' => $html_content,
1149 ];
1150 }
1151
1152 /**
1153 * Main method to invoke the widget. Contents will be loaded
1154 * through AJAX requests, not via PHP when the page loads.
1155 *
1156 * @param VBOMultitaskData $data
1157 *
1158 * @return void
1159 */
1160 public function render(VBOMultitaskData $data = null)
1161 {
1162 // increase widget's instance counter
1163 static::$instance_counter++;
1164
1165 // check whether the widget is being rendered via AJAX when adding it through the customizer
1166 $is_ajax = $this->isAjaxRendering();
1167
1168 // generate a unique ID for the sticky notes wrapper instance
1169 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
1170 $wrapper_id = 'vbo-widget-bookdets-' . $wrapper_instance;
1171
1172 // get permissions
1173 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
1174 if (!$vbo_auth_bookings) {
1175 // display nothing
1176 return;
1177 }
1178
1179 // check multitask data
1180 $load_bid = 0;
1181 $js_modal_id = '';
1182 $is_modal_rendering = false;
1183 if ($data) {
1184 // access Multitask data
1185 $is_modal_rendering = $data->isModalRendering();
1186 if ($is_modal_rendering) {
1187 // get modal JS identifier
1188 $js_modal_id = $data->getModalJsIdentifier();
1189 }
1190
1191 /**
1192 * This widget should not get the current ID from Multitask data
1193 * to avoid displaying duplicate contents, it should rather get
1194 * the ID from the injected options (i.e. new Push notification).
1195 */
1196 $load_bid = $this->options()->fetchBookingId();
1197 }
1198
1199 // check if we got data by clicking on a push notification from the ServiceWorker
1200 $from_push = $this->options()->gotPushData();
1201
1202 ?>
1203 <div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>" data-loadbid="<?php echo $load_bid; ?>">
1204
1205 <div class="vbo-admin-widget-head">
1206 <div class="vbo-admin-widget-head-inline">
1207 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
1208 </div>
1209 </div>
1210
1211 <div class="vbo-widget-bookdets-wrap">
1212 <div class="vbo-widget-bookdets-inner">
1213
1214 <div class="vbo-widget-element-filter-top">
1215 <div class="btn-group-inline">
1216 <button type="button" class="btn btn-secondary vbo-context-menu-btn vbo-widget-bookdets-searchtype">
1217 <span class="vbo-context-menu-ico"><?php VikBookingIcons::e('sort-down'); ?></span>
1218 </button>
1219 <input type="text" name="booking_key" value="<?php echo $load_bid ? "id: {$load_bid}" : ''; ?>" placeholder="<?php echo htmlspecialchars(JText::translate('VBOFILTCONFNUMCUST')); ?>" autocomplete="off" />
1220 <button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('search'); ?> <span class="vbo-widget-bookdets-searchbtn"><?php echo JText::translate('VBODASHSEARCHKEYS'); ?></span></button>
1221 </div>
1222 </div>
1223
1224 <?php
1225 // check if we need to display the Push notification message
1226 if ($from_push) {
1227 // get the title and message
1228 $push_title = $this->getOption('title', '');
1229 $push_message = $this->getOption('message', '');
1230
1231 // check for notification action and "from"
1232 $push_action = $this->getOption('action', '');
1233 $push_from = $this->getOption('from', '');
1234
1235 // notification severity
1236 $push_severity = $this->getOption('severity', 'info');
1237
1238 // replace new line characters with a white space
1239 $push_message = preg_replace("/[\r\n]/", ' ', $push_message);
1240
1241 // get rid of white space characters (only white space, no new lines or tabs like "\s") used more than once in a row
1242 $push_message = preg_replace("/ {2,}/", ' ', $push_message);
1243 ?>
1244 <div class="vbo-widget-push-notification vbo-widget-push-notification-<?php echo $push_severity; ?>">
1245 <?php
1246 if (!empty($push_title)) {
1247 ?>
1248 <div class="vbo-widget-push-notification-title">
1249 <strong><?php echo $push_title; ?></strong>
1250 </div>
1251 <?php
1252 }
1253 if (!empty($push_message)) {
1254 ?>
1255 <div class="vbo-widget-push-notification-message">
1256 <span><?php echo $push_message; ?></span>
1257 </div>
1258 <?php
1259 }
1260 if ($push_action && !strcasecmp($push_from, 'airbnb')) {
1261 // supported action
1262 if (!strcasecmp($push_action, 'host_guest_review')) {
1263 // prepare link to trigger the host-to-guest review action
1264 ?>
1265 <div class="vbo-widget-push-notification-action" style="display: none;">
1266 <a class="btn vbo-config-btn vbo-widget-bookdets-actionbtn" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d&notif_action=airbnb_host_guest_review', $xhtml = false); ?>" target="_blank"><?php VikBookingIcons::e('star'); ?> <?php echo JText::translate('VBO_REVIEW_YOUR_GUEST'); ?></a>
1267 </div>
1268 <?php
1269 } elseif (!strcasecmp($push_action, 'new_guest_review')) {
1270 // prepare link to display the guest review received
1271 ?>
1272 <div class="vbo-widget-push-notification-action" style="display: none;">
1273 <a class="btn vbo-config-btn vbo-widget-bookdets-actionbtn" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d&notif_action=see_guest_review', $xhtml = false); ?>" target="_blank"><?php VikBookingIcons::e('star'); ?> <?php echo JText::translate('VBOSEEGUESTREVIEW'); ?></a>
1274 </div>
1275 <?php
1276 }
1277 }
1278 ?>
1279 </div>
1280 <?php
1281 }
1282 ?>
1283
1284 <div class="vbo-widget-element-body"></div>
1285
1286 </div>
1287 </div>
1288
1289 </div>
1290 <?php
1291
1292 if (static::$instance_counter === 0 || $is_ajax) {
1293 /**
1294 * Print the JS code only once for all instances of this widget.
1295 * The real rendering is made through AJAX, not when the page loads.
1296 */
1297 ?>
1298 <a class="vbo-widget-bookdets-basenavuri" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d', $xhtml = false); ?>" style="display: none;"></a>
1299 <a class="vbo-widget-editbook-basenavuri" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editbusy&cid[]=%d', $xhtml = false); ?>" style="display: none;"></a>
1300
1301 <script type="text/javascript">
1302
1303 /**
1304 * Open the booking details (or edit booking) page for the clicked reservation.
1305 */
1306 function vboWidgetBookDetsOpenBooking(id, edit) {
1307 var open_url = jQuery((edit ? '.vbo-widget-editbook-basenavuri' : '.vbo-widget-bookdets-basenavuri')).first().attr('href');
1308 open_url = open_url.replace('%d', id);
1309 // navigate in a new tab
1310 window.open(open_url, '_blank');
1311 }
1312
1313 /**
1314 * Searches for a booking according to input filter.
1315 */
1316 function vboWidgetBookDetsSearch(wrapper, options, bid) {
1317 var widget_instance = jQuery('#' + wrapper);
1318 if (!widget_instance.length) {
1319 return false;
1320 }
1321
1322 var booking_key = widget_instance.find('input[name="booking_key"]').val();
1323
1324 // show loading
1325 widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VIKLOADING'));
1326
1327 // the widget method to call
1328 var call_method = 'loadBookingDetails';
1329
1330 // make a request to load the booking details
1331 VBOCore.doAjax(
1332 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1333 {
1334 widget_id: "<?php echo $this->getIdentifier(); ?>",
1335 call: call_method,
1336 return: 1,
1337 bid: bid,
1338 booking_key: booking_key,
1339 _options: options,
1340 wrapper: wrapper,
1341 tmpl: "component"
1342 },
1343 (response) => {
1344 // hide loading
1345 widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VBODASHSEARCHKEYS'));
1346 try {
1347 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1348 if (!obj_res.hasOwnProperty(call_method)) {
1349 widget_instance.find('.vbo-widget-element-body').html('');
1350 console.error('Unexpected JSON response', obj_res);
1351 return false;
1352 }
1353
1354 // replace HTML with new content
1355 widget_instance.find('.vbo-widget-element-body').html(obj_res[call_method]['html']);
1356 } catch(err) {
1357 console.error('could not parse JSON response', err, response);
1358 }
1359 },
1360 (error) => {
1361 // hide loading
1362 widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VBODASHSEARCHKEYS'));
1363 // display no bookings message
1364 widget_instance.find('.vbo-widget-element-body').html((error.status != 400 ? '<p class="err">' + error.responseText + '</p>' : ''));
1365 }
1366 );
1367 }
1368
1369 /**
1370 * Attempts to load a booking ID from the injected options.
1371 */
1372 function vboWidgetBookDetsLoad(wrapper, options) {
1373 var widget_instance = jQuery('#' + wrapper);
1374 if (!widget_instance.length) {
1375 return false;
1376 }
1377
1378 var load_bid = widget_instance.attr('data-loadbid');
1379 if (!load_bid || load_bid == '0') {
1380 return false;
1381 }
1382
1383 // load booking
1384 vboWidgetBookDetsSearch(wrapper, options, load_bid);
1385 }
1386
1387 /**
1388 * Prepares the input search field with the proper type hint.
1389 */
1390 function vboWidgetBookDetsSearchType(wrapper, search_hint) {
1391 var widget_instance = jQuery('#' + wrapper);
1392 if (!widget_instance.length) {
1393 return false;
1394 }
1395
1396 if (!search_hint) {
1397 search_hint = '';
1398 } else {
1399 search_hint += ': ';
1400 }
1401
1402 widget_instance.find('input[name="booking_key"]').val(search_hint).focus();
1403 }
1404
1405 /**
1406 * Displays the full history record description.
1407 */
1408 function vboWidgetBookDetsReadFullHistory(btn) {
1409 var descr_wrapper = jQuery(btn).closest('.vbo-widget-bookdets-history-descr-wrap');
1410 descr_wrapper.find('.vbo-widget-bookdets-history-descr-txt').hide();
1411 descr_wrapper.find('.vbo-widget-bookdets-history-descr-html').show();
1412 }
1413
1414 /**
1415 * Displays all booking history records.
1416 */
1417 function vboWidgetBookDetsHistoryAll(wrapper) {
1418 var widget_instance = jQuery('#' + wrapper);
1419 if (!widget_instance.length) {
1420 return false;
1421 }
1422
1423 // show all records
1424 widget_instance.find('.vbo-widget-bookdets-history-record').show();
1425
1426 // hide button
1427 widget_instance.find('.vbo-widget-bookdets-history-showall').hide('.');
1428 }
1429
1430 /**
1431 * Display all channel manager notifications.
1432 */
1433 function vboWidgetBookDetsCmnotifsAll(wrapper) {
1434 var widget_instance = jQuery('#' + wrapper);
1435 if (!widget_instance.length) {
1436 return false;
1437 }
1438
1439 // show all records
1440 widget_instance.find('.vbo-widget-bookdets-cmnotifs-record').show();
1441
1442 // hide button
1443 widget_instance.find('.vbo-widget-bookdets-cmnotifs-showall').hide('.');
1444 }
1445
1446 /**
1447 * Copies to clipboard the Booking.com RUID response codes, if any.
1448 */
1449 function vboWidgetBookDetsCopyRuids(btn) {
1450 var tarea = btn.closest('.vbo-widget-bookdets-cm-updresult').querySelector('.vbo-textarea-copyable');
1451 VBOCore.copyToClipboard(tarea).then((success) => {
1452 jQuery(btn).find('.vbo-tooltip').attr('data-tooltiptext', Joomla.JText._('VBO_COPIED') + '!');
1453 }).catch((err) => {
1454 alert('Could not copy the logs');
1455 });
1456 }
1457
1458 /**
1459 * Toggles the overbooking status-type.
1460 */
1461 function vboWidgetBookDetsToggleOverbooking(wrapper) {
1462 var widget_instance = jQuery('#' + wrapper);
1463 if (!widget_instance.length) {
1464 return false;
1465 }
1466
1467 var bid = widget_instance.find('[data-bookingid]').attr('data-bookingid');
1468
1469 if (confirm(Joomla.JText._('VBO_CONF_RM_OVERBOOKING_FLAG'))) {
1470 VBOCore.doAjax(
1471 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.delete_type_flag'); ?>",
1472 {
1473 bid: bid,
1474 flag: 'overbooking',
1475 },
1476 (success) => {
1477 // remove "overbooking" label
1478 let parent_label = widget_instance.find('.vbo-label-overbooking').parent('.label');
1479 widget_instance.find('.vbo-label-overbooking').remove();
1480 parent_label.text(parent_label.text().replace(' / ', ''));
1481
1482 // turn flag on for the booking modified
1483 vbo_widget_book_dets_last_mod_bid = bid;
1484 },
1485 (error) => {
1486 alert(error.responseText);
1487 }
1488 );
1489 }
1490 }
1491
1492 </script>
1493 <?php
1494 }
1495 ?>
1496
1497 <script type="text/javascript">
1498
1499 // holds the lastly modified booking ID
1500 var vbo_widget_book_dets_last_mod_bid = null;
1501
1502 jQuery(function() {
1503
1504 // when document is ready, load contents for this widget's instance
1505 vboWidgetBookDetsLoad('<?php echo $wrapper_id; ?>', <?php echo json_encode($this->getOptions()); ?>);
1506
1507 // register keyup event for auto-submit
1508 let input_search = document.querySelector('#<?php echo $wrapper_id; ?> input[name="booking_key"]');
1509 if (input_search) {
1510 input_search.addEventListener('keyup', (e) => {
1511 if (e.key === 'Enter') {
1512 return vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
1513 }
1514 if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
1515 // match a valid booking ID to increase or decrease
1516 let search_val = input_search.value;
1517 let rgx_id = new RegExp(/^(id:)?\s?[0-9]+$/);
1518 if (search_val && search_val.match(rgx_id)) {
1519 let rgx_num = new RegExp(/[^0-9]/g);
1520 let raw_idn = parseInt(search_val.replace(rgx_num, ''));
1521 if (!isNaN(raw_idn)) {
1522 // replace value and start search
1523 input_search.value = search_val.replace(raw_idn, (e.key === 'ArrowUp' ? (raw_idn + 1) : (raw_idn - 1)));
1524 return vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
1525 }
1526 }
1527 }
1528 });
1529 }
1530
1531 // render context menu
1532 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-bookdets-searchtype').vboContextMenu({
1533 placement: 'bottom-left',
1534 buttons: [
1535 {
1536 icon: '<?php echo VikBookingIcons::i('search'); ?>',
1537 text: Joomla.JText._('VBDASHUPRESONE'),
1538 separator: false,
1539 action: (root, config) => {
1540 vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'id');
1541 },
1542 },
1543 {
1544 icon: '<?php echo VikBookingIcons::i('search'); ?>',
1545 text: Joomla.JText._('VBDASHUPRESONE') + ' (' + Joomla.JText._('VBOCHANNEL') + ')',
1546 separator: false,
1547 action: (root, config) => {
1548 vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'otaid');
1549 },
1550 },
1551 {
1552 icon: '<?php echo VikBookingIcons::i('user-tag'); ?>',
1553 text: Joomla.JText._('VBCOUPON'),
1554 separator: false,
1555 action: (root, config) => {
1556 vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'coupon');
1557 },
1558 },
1559 {
1560 icon: '<?php echo VikBookingIcons::i('user'); ?>',
1561 text: Joomla.JText._('VBCUSTOMERNOMINATIVE'),
1562 separator: false,
1563 action: (root, config) => {
1564 vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'name');
1565 },
1566 },
1567 ],
1568 });
1569
1570 // subscribe to the multitask-panel-close event to emit the event for the lastly modified booking ID
1571 document.addEventListener(VBOCore.multitask_close_event, function() {
1572 if (vbo_widget_book_dets_last_mod_bid) {
1573 // emit the event with data for anyone who is listening to it
1574 VBOCore.emitEvent('vbo_booking_modified', {
1575 bid: vbo_widget_book_dets_last_mod_bid
1576 });
1577 }
1578 });
1579
1580 <?php
1581 if ($is_modal_rendering) {
1582 // focus search input field & register to the event emitted when reminders have changed
1583 ?>
1584 setTimeout(() => {
1585 jQuery('#<?php echo $wrapper_id; ?>').find('input[name="booking_key"]').focus();
1586 }, 400);
1587
1588 var vbo_widget_bookdets_watch_reminders_fn = (e) => {
1589 if (!e || !e.detail || !e.detail.hasOwnProperty('bid') || !e.detail['bid']) {
1590 return;
1591 }
1592 let booking_element = jQuery('#<?php echo $wrapper_id; ?>').find('[data-bookingid]').first();
1593 if (booking_element.length && booking_element.attr('data-bookingid') == e.detail['bid']) {
1594 // reload current booking details
1595 vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
1596 }
1597 };
1598
1599 document.addEventListener('vbo_reminders_changed', vbo_widget_bookdets_watch_reminders_fn);
1600
1601 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', (e) => {
1602 document.removeEventListener('vbo_reminders_changed', vbo_widget_bookdets_watch_reminders_fn);
1603 });
1604
1605 // subscribe to the modal-dismissed event to emit the event for the lastly modified booking ID
1606 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', function() {
1607 if (vbo_widget_book_dets_last_mod_bid) {
1608 // emit the event with data for anyone who is listening to it
1609 VBOCore.emitEvent('vbo_booking_modified', {
1610 bid: vbo_widget_book_dets_last_mod_bid
1611 });
1612 }
1613 });
1614 <?php
1615 }
1616 ?>
1617
1618 });
1619
1620 </script>
1621
1622 <?php
1623 }
1624
1625 /**
1626 * Loads a list of Channel Manager notifications and channels for the given booking.
1627 *
1628 * @param array $booking the current booking record.
1629 *
1630 * @return array list of notifications and channels involved.
1631 */
1632 protected function loadChannelManagerNotifications(array $booking)
1633 {
1634 $dbo = JFactory::getDbo();
1635
1636 $channels = [];
1637
1638 $q = $dbo->getQuery(true)
1639 ->select('*')
1640 ->from($dbo->qn('#__vikchannelmanager_notifications'))
1641 ->where($dbo->qn('from') . ' = ' . $dbo->q('VCM'))
1642 ->where($dbo->qn('idordervb') . ' = ' . (int)$booking['id'])
1643 ->order($dbo->qn('ts') . ' DESC');
1644
1645 $dbo->setQuery($q);
1646 $parents = $dbo->loadAssocList();
1647
1648 foreach ($parents as $k => $parent) {
1649 $q = $dbo->getQuery(true)
1650 ->select($dbo->qn(['type', 'cont', 'channel']))
1651 ->from($dbo->qn('#__vikchannelmanager_notification_child'))
1652 ->where($dbo->qn('id_parent') . ' = ' . (int)$parent['id'])
1653 ->order($dbo->qn('id') . ' ASC');
1654
1655 $dbo->setQuery($q);
1656 $children = $dbo->loadAssocList();
1657
1658 // set children notifications by grouping them under each channel
1659 $parents[$k]['children'] = [];
1660
1661 // fetch the details for each channel involved
1662 foreach ($children as $ind => $child) {
1663 if (empty($child['channel'])) {
1664 continue;
1665 }
1666
1667 $channel = VikChannelManager::getChannel($child['channel']);
1668 if (!$channel) {
1669 // we don't want to list a notification with no channel details
1670 unset($children[$ind]);
1671 continue;
1672 }
1673
1674 // set channel details
1675 $channels[$child['channel']] = $channel;
1676
1677 if (!isset($parents[$k]['children'][$child['channel']])) {
1678 // open channel container
1679 $parents[$k]['children'][$child['channel']] = [];
1680 }
1681
1682 // push channel notification details
1683 $parents[$k]['children'][$child['channel']][] = $child;
1684 }
1685
1686 if (!$children) {
1687 // we don't want a notification with no children (channels notified)
1688 unset($parents[$k]);
1689 continue;
1690 }
1691 }
1692
1693 if (!$channels) {
1694 // unset all notifications in case of no channel details
1695 $parents = [];
1696 }
1697
1698 return [array_values($parents), $channels];
1699 }
1700 }
1701