PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.7
VikBooking Hotel Booking Engine & PMS v1.8.7
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.8.7, at admin/helpers/widgets/booking_details.php

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