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

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