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

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

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