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

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