PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.5
VikBooking Hotel Booking Engine & PMS v1.6.5
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 / guest_messages.php

guest_messages.php in VikBooking Hotel Booking Engine & PMS 1.6.5, at admin/helpers/widgets/guest_messages.php

966 lines 32.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. 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 "guest messages".
15 *
16 * @since 1.16.0 (J) - 1.6.0 (WP)
17 */
18 class VikBookingAdminWidgetGuestMessages extends VikBookingAdminWidget
19 {
20 /**
21 * The instance counter of this widget.
22 *
23 * @var int
24 */
25 protected static $instance_counter = -1;
26
27 /**
28 * Number of messages per page. Should be an even number.
29 *
30 * @var int
31 */
32 protected $messages_per_page = 6;
33
34 /**
35 * Today Y-m-d string
36 *
37 * @var string
38 */
39 protected $today_ymd = null;
40
41 /**
42 * The path to the VCM lib to see if it's available.
43 *
44 * @var string
45 */
46 protected $vcm_lib_path = '';
47
48 /**
49 * Tells whether VCM is installed and updated.
50 *
51 * @var bool
52 */
53 protected $vcm_exists = true;
54
55 /**
56 * Class constructor will define the widget name and identifier.
57 */
58 public function __construct()
59 {
60 // call parent constructor
61 parent::__construct();
62
63 $this->widgetName = JText::translate('VBO_W_GUESTMESSAGES_TITLE');
64 $this->widgetDescr = JText::translate('VBO_W_GUESTMESSAGES_DESCR');
65 $this->widgetId = basename(__FILE__, '.php');
66
67 // define widget and icon and style name
68 $this->widgetIcon = '<i class="' . VikBookingIcons::i('comment-dots') . '"></i>';
69 $this->widgetStyleName = 'light-orange';
70
71 // today Y-m-d date
72 $this->today_ymd = date('Y-m-d');
73
74 // the path to the VCM library
75 $this->vcm_lib_path = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php';
76
77 // whether VCM is available
78 if (is_file($this->vcm_lib_path)) {
79 if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getLatestFromGuests')) {
80 // VCM is outdated
81 $this->vcm_exists = false;
82 }
83
84 // attempt to require the chat handler
85 try {
86 VikBooking::getVcmChatInstance($oid = 0, $channel = null);
87 } catch (Exception $e) {
88 // do nothing
89 }
90
91 // make sure VCM is up to date for this widget
92 if (!class_exists('VCMChatHandler') || !method_exists('VCMChatHandler', 'loadChatAssets')) {
93 // VCM is outdated (>= 1.8.11 required)
94 $this->vcm_exists = false;
95 }
96 } else {
97 $this->vcm_exists = false;
98 }
99
100 // avoid queries on certain pages, as VCM may not have been activated yet
101 if (VBOPlatformDetection::isWordPress() && $this->vcm_exists) {
102 global $pagenow;
103 if (isset($pagenow) && in_array($pagenow, ['update.php', 'plugins.php', 'plugin-install.php'])) {
104 $this->vcm_exists = false;
105 }
106 }
107 }
108
109 /**
110 * Preload the necessary CSS/JS assets from VCM.
111 *
112 * @return void
113 */
114 public function preload()
115 {
116 if ($this->vcm_exists) {
117 // load chat assets from VCM
118 VCMChatHandler::loadChatAssets();
119
120 // additional language defs
121 JText::script('VBO_NO_REPLY_NEEDED');
122 JText::script('VBO_WANT_PROCEED');
123 }
124 }
125
126 /**
127 * Custom method for this widget only to load the latest guest messages.
128 * The method is called by the admin controller through an AJAX request.
129 * The visibility should be public, it should not exit the process, and
130 * an associative array is returned thanks to the request value "return":1.
131 *
132 * It's the actual rendering of the widget which also allows navigation.
133 */
134 public function loadMessages()
135 {
136 $bid_convo = VikRequest::getString('bid_convo', '', 'request');
137 $offset = VikRequest::getInt('offset', 0, 'request');
138 $length = VikRequest::getInt('length', $this->messages_per_page, 'request');
139 $wrapper = VikRequest::getString('wrapper', '', 'request');
140
141 if (!$this->vcm_exists) {
142 VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
143 }
144
145 // load latest messages
146 try {
147 $latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], $offset, $length);
148 } catch (Exception $e) {
149 // do nothing
150 $latest_messages = [];
151 }
152
153 // the multitask data and notifications can request a specific conversation to be opened
154 $bubble_convo = null;
155 if ($bid_convo) {
156 // make sure the requested booking ID was fetched from the most recent guest messages
157 foreach ($latest_messages as $gmessage) {
158 if ($bid_convo == $gmessage->idorder) {
159 // specific conversation to bubble found
160 $bubble_convo = $bid_convo;
161 break;
162 }
163 if ($bid_convo == $gmessage->idorderota && strcasecmp((string)$gmessage->channel, 'vikbooking')) {
164 // specific OTA conversation to bubble found
165 $bubble_convo = $bid_convo;
166 break;
167 }
168 }
169 if (!$bubble_convo && class_exists('VCMChatMessaging')) {
170 // updated VCM versions will allow us to fetch one conversation by booking ID
171 $booking_messages = VCMChatMessaging::getInstance()->loadBookingGuestThreads($bid_convo);
172 if ($booking_messages) {
173 // append the requested conversation so that it will bubble
174 $latest_messages = array_merge($latest_messages, $booking_messages);
175 // turn flag on
176 $bubble_convo = $bid_convo;
177 }
178 }
179 }
180
181 // current year Y and timestamp
182 $current_y = date('Y');
183 $current_ts = time();
184
185 // start output buffering
186 ob_start();
187
188 foreach ($latest_messages as $gmessage) {
189 $gmessage_content = $gmessage->content;
190 if (empty($gmessage_content)) {
191 $gmessage_content = '.....';
192 } elseif (strlen($gmessage_content) > 90) {
193 if (function_exists('mb_substr')) {
194 $gmessage_content = mb_substr($gmessage_content, 0, 90, 'UTF-8');
195 } else {
196 $gmessage_content = substr($gmessage_content, 0, 90);
197 }
198 $gmessage_content .= '...';
199 }
200
201 ?>
202 <div class="vbo-dashboard-guest-activity vbo-w-guestmessages-message<?php echo empty($gmessage->read_dt) ? ' vbo-w-guestmessages-message-new' : ''; ?>" data-idorder="<?php echo $gmessage->idorder; ?>" data-idthread="<?php echo !empty($gmessage->id_thread) ? $gmessage->id_thread : ''; ?>" data-idmessage="<?php echo !empty($gmessage->id_message) ? $gmessage->id_message : ''; ?>" onclick="vboWidgetGuestMessagesOpenChat('<?php echo $gmessage->idorder; ?>');">
203 <div class="vbo-dashboard-guest-activity-avatar">
204 <?php
205 if (!empty($gmessage->guest_avatar)) {
206 // highest priority goes to the profile picture, not always available
207 ?>
208 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->guest_avatar; ?>" />
209 <?php
210 } elseif (!empty($gmessage->pic)) {
211 // customer profile picture is not the same as the photo avatar
212 ?>
213 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo strpos($gmessage->pic, 'http') === 0 ? $gmessage->pic : VBO_SITE_URI . 'resources/uploads/' . $gmessage->pic; ?>" />
214 <?php
215 } elseif (!empty($gmessage->channel_logo)) {
216 // channel logo goes as second option
217 ?>
218 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->channel_logo; ?>" />
219 <?php
220 } else {
221 // we use an icon as fallback
222 VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon');
223 }
224 ?>
225 </div>
226 <div class="vbo-dashboard-guest-activity-content">
227 <div class="vbo-dashboard-guest-activity-content-head">
228 <div class="vbo-dashboard-guest-activity-content-info-details">
229 <h4 class="vbo-w-guestmessages-message-gtitle"><?php
230 echo $gmessage->first_name . (!empty($gmessage->last_name) ? ' ' . $gmessage->last_name : '');
231 if (empty($gmessage->read_dt)) {
232 // print also an icon to inform that the message was not read
233 echo ' ';
234 VikBookingIcons::e('exclamation-circle', 'message-new');
235 }
236 ?></h4>
237 <div class="vbo-dashboard-guest-activity-content-info-icon">
238 <?php
239 if (!empty($gmessage->b_status)) {
240 switch ($gmessage->b_status) {
241 case 'standby':
242 $badge_class = 'badge-warning';
243 $badge_text = JText::translate('VBSTANDBY');
244 break;
245 case 'cancelled':
246 $badge_class = 'badge-danger';
247 $badge_text = JText::translate('VBCANCELLED');
248 break;
249 default:
250 $badge_class = 'badge-success';
251 $badge_text = JText::translate('VBCONFIRMED');
252 if (!empty($gmessage->b_checkout) && $gmessage->b_checkout < $current_ts) {
253 $badge_text = JText::translate('VBOCHECKEDSTATUSOUT');
254 }
255 break;
256 }
257 ?>
258 <span class="badge <?php echo $badge_class; ?>"><?php echo $badge_text; ?></span>
259 <?php
260 }
261 if (!empty($gmessage->b_checkin)) {
262 $stay_info_in = getdate($gmessage->b_checkin);
263 $stay_info_out = getdate($gmessage->b_checkout);
264 $str_checkin = date('d', $gmessage->b_checkin);
265 $str_checkin .= $stay_info_in['mon'] != $stay_info_out['mon'] ? ' ' . VikBooking::sayMonth($stay_info_in['mon'], $short = true) : '';
266 $str_checkout = date('d', $gmessage->b_checkout) . ' ' . VikBooking::sayMonth($stay_info_in['mon'], $short = true);
267 if ($stay_info_in['year'] != $stay_info_out['year'] || $stay_info_in['year'] != $current_y || $stay_info_out['year'] != $current_y) {
268 $str_checkout .= ' ' . $stay_info_in['year'];
269 }
270 ?>
271 <span class="vbo-w-guestmessages-message-staydates">
272 <span class="vbo-w-guestmessages-message-staydates-in"><?php echo $str_checkin; ?></span>
273 <span class="vbo-w-guestmessages-message-staydates-sep">-</span>
274 <span class="vbo-w-guestmessages-message-staydates-out"><?php echo $str_checkout; ?></span>
275 </span>
276 <?php
277 }
278 ?>
279 </div>
280 </div>
281 <div class="vbo-dashboard-guest-activity-content-info-date">
282 <?php
283 $gmessage_ts = strtotime($gmessage->last_updated);
284 ?>
285 <span><?php echo date('H:i', $gmessage_ts); ?></span>
286 <?php
287 if (date('Y-m-d', $gmessage_ts) != $this->today_ymd) {
288 // format and print the date
289 ?>
290 <span><?php echo date(str_replace('/', $this->datesep, $this->df), $gmessage_ts); ?></span>
291 <?php
292 } else {
293 // print "today"
294 ?>
295 <span><?php echo JText::translate('VBTODAY'); ?></span>
296 <?php
297 }
298 ?>
299 </div>
300 </div>
301 <div class="vbo-dashboard-guest-activity-content-info-msg">
302 <p><?php echo $gmessage_content; ?></p>
303 </div>
304 </div>
305 </div>
306 <?php
307 }
308
309 // append navigation
310 ?>
311 <div class="vbo-guestactivitywidget-commands">
312 <div class="vbo-guestactivitywidget-commands-main">
313 <?php
314 if ($offset > 0) {
315 // show backward navigation button
316 ?>
317 <div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-prev">
318 <span class="vbo-guestactivitywidget-prev" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
319 </div>
320 <?php
321 }
322 ?>
323 <div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-next">
324 <span class="vbo-guestactivitywidget-next" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
325 </div>
326 </div>
327 </div>
328 <?php
329
330 // check if we should bubble a specific conversation
331 if ($bubble_convo) {
332 ?>
333 <script type="text/javascript">
334 setTimeout(() => {
335 vboWidgetGuestMessagesOpenChat('<?php echo $bubble_convo; ?>');
336 }, 300);
337 </script>
338 <?php
339 }
340
341 // append the total number of messages displayed, the current offset and the latest message datetime
342 $tot_messages = count($latest_messages);
343 $latest_datetime = $tot_messages > 0 && $offset === 0 ? $latest_messages[0]->last_updated : null;
344
345 // get the HTML buffer
346 $html_content = ob_get_contents();
347 ob_end_clean();
348
349 // return an associative array of values
350 return [
351 'html' => $html_content,
352 'tot_messages' => $tot_messages,
353 'offset' => ($offset + $length),
354 'latest_dt' => $latest_datetime,
355 ];
356 }
357
358 /**
359 * Custom method for this widget only to watch the latest guest messages.
360 * The method is called by the admin controller through an AJAX request.
361 * The visibility should be public, it should not exit the process, and
362 * any content sent to output will be returned to the AJAX response.
363 *
364 * Outputs the new number of messages found from the latest datetime.
365 */
366 public function watchMessages()
367 {
368 $latest_dt = VikRequest::getString('latest_dt', '', 'request');
369 if (empty($latest_dt)) {
370 echo '0';
371 return;
372 }
373
374 if (!$this->vcm_exists) {
375 VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
376 }
377
378 // load the latest guest message (one is sufficient)
379 $latest_messages = [];
380 try {
381 $latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], 0, 1);
382 } catch (Exception $e) {
383 // do nothing
384 }
385
386 if (!count($latest_messages) || $latest_messages[0]->last_updated == $latest_dt) {
387 // no newest messages found
388 echo '0';
389 return;
390 }
391
392 // print 1 to indicate that new messages should be reloaded
393 echo '1';
394 }
395
396 /**
397 * Custom method for this widget only to render the chat of a booking.
398 * The method is called by the admin controller through an AJAX request.
399 * The visibility should be public, it should not exit the process, and
400 * no values should be returned, as the response must be sent to output
401 * in case the JS/CSS assets will be echoed within the response.
402 *
403 * Returns the necessary HTML code to render the chat.
404 */
405 public function renderChat()
406 {
407 $bid = VikRequest::getInt('bid', 0, 'request');
408
409 $booking = VikBooking::getBookingInfoFromID($bid);
410
411 if (!$booking) {
412 VBOHttpDocument::getInstance()->close(404, 'Could not find booking');
413 }
414
415 // initialize chat instance by getting the proper channel name
416 if (empty($booking['channel'])) {
417 // front-end reservation chat handler
418 $chat_channel = 'vikbooking';
419 } else {
420 $channelparts = explode('_', $booking['channel']);
421 // check if this is a meta search channel
422 $is_meta_search = false;
423 if (preg_match("/(customer).*[0-9]$/", $channelparts[0]) || !strcasecmp($channelparts[0], 'googlehotel') || !strcasecmp($channelparts[0], 'trivago')) {
424 $is_meta_search = empty($booking['idorderota']);
425 }
426 if ($is_meta_search) {
427 // customer of type sales channel should use front-end reservation chat handler
428 $chat_channel = 'vikbooking';
429 } else {
430 // let the getInstance method validate the channel chat handler
431 $chat_channel = $booking['channel'];
432 }
433 }
434 $messaging = VikBooking::getVcmChatInstance($booking['id'], $chat_channel);
435
436 if (is_null($messaging)) {
437 VBOHttpDocument::getInstance()->close(500, 'Could not render chat');
438 }
439
440 // send content to output
441 echo $messaging->renderChat([
442 'hideThreads' => 1,
443 ], $load_assets = false);
444 }
445
446 /**
447 * Custom method for this widget only to update the thread of a booking.
448 * The method is called by the admin controller through an AJAX request.
449 * The visibility should be public, it should not exit the process, and
450 * no values should be returned, as the response must be sent to output.
451 *
452 * Returns a successful string or throws an error.
453 */
454 public function setNoReplyNeededThread()
455 {
456 $bid = VikRequest::getInt('bid', 0, 'request');
457 $id_thread = VikRequest::getInt('id_thread', 0, 'request');
458
459 $booking = VikBooking::getBookingInfoFromID($bid);
460
461 if (!$booking || empty($id_thread)) {
462 VBOHttpDocument::getInstance()->close(404, 'Could not find booking thread');
463 }
464
465 // build thread object for update
466 $thread = new stdClass;
467 $thread->id = $id_thread;
468 $thread->idorder = $bid;
469 $thread->no_reply_needed = 1;
470
471 $dbo = JFactory::getDbo();
472
473 if (!$dbo->updateObject('#__vikchannelmanager_threads', $thread, ['id', 'idorder'])) {
474 VBOHttpDocument::getInstance()->close(500, 'Could not update thread');
475 }
476
477 echo '1';
478 }
479
480 public function render(VBOMultitaskData $data = null)
481 {
482 // increase widget's instance counter
483 static::$instance_counter++;
484
485 // check whether the widget is being rendered via AJAX when adding it through the customizer
486 $is_ajax = $this->isAjaxRendering();
487
488 // generate a unique ID for the guest messages wrapper instance
489 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
490 $wrapper_id = 'vbo-widget-guest-messages-' . $wrapper_instance;
491
492 // this widget will work only if VCM is available and updated, and if permissions are met
493 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
494 if (!$this->vcm_exists || !$vbo_auth_bookings) {
495 return;
496 }
497
498 // multitask data event identifier for clearing intervals
499 $js_intvals_id = '';
500 $bid_convo = 0;
501 if ($data && $data->isModalRendering()) {
502 // access Multitask data
503 $js_intvals_id = $data->getModalJsIdentifier();
504
505 // check if a specific conversation should be opened
506 if ($data->get('id_message', 0)) {
507 $bid_convo = $data->getBookingId();
508 } else {
509 $bid_convo = $this->options()->fetchBookingId();
510 }
511 }
512
513 ?>
514 <div class="vbo-admin-widget-wrapper">
515 <div class="vbo-admin-widget-head">
516 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
517 </div>
518 <div id="<?php echo $wrapper_id; ?>" class="vbo-dashboard-guests-latest" data-offset="0" data-length="<?php echo $this->messages_per_page; ?>" data-eventsid="<?php echo $js_intvals_id; ?>" data-latestdt="">
519 <div class="vbo-dashboard-guest-messages-inner">
520 <div class="vbo-dashboard-guest-messages-list">
521 <?php
522 for ($i = 0; $i < $this->messages_per_page; $i++) {
523 ?>
524 <div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">
525 <div class="vbo-dashboard-guest-activity-avatar">
526 <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>
527 </div>
528 <div class="vbo-dashboard-guest-activity-content">
529 <div class="vbo-dashboard-guest-activity-content-head">
530 <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>
531 </div>
532 <div class="vbo-dashboard-guest-activity-content-subhead">
533 <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>
534 </div>
535 <div class="vbo-dashboard-guest-activity-content-info-msg">
536 <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>
537 </div>
538 </div>
539 </div>
540 <?php
541 }
542 ?>
543 </div>
544 </div>
545 </div>
546 </div>
547
548 <?php
549 if (static::$instance_counter === 0 || $is_ajax) {
550 // HTML helper tag for URL routing and some JS functions should be loaded once per widget instance
551 $admin_file_base = VBOPlatformDetection::isWordPress() ? 'admin.php' : 'index.php';
552 ?>
553 <a class="vbo-widget-guest-messages-basenavuri" href="<?php echo $admin_file_base; ?>?option=com_vikbooking&task=editorder&cid[]=%d" style="display: none;"></a>
554
555 <script type="text/javascript">
556
557 /**
558 * Open the chat for the clicked booking guest message
559 */
560 function vboWidgetGuestMessagesOpenChat(id) {
561 // clicked message
562 var message_el = jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').first();
563
564 if (message_el.hasClass('vbo-w-guestmessages-message-new')) {
565 // get rid of the "new/unread" status
566 message_el.removeClass('vbo-w-guestmessages-message-new');
567 if (message_el.find('i.message-new').length) {
568 message_el.find('i.message-new').remove();
569 }
570 }
571
572 // modal events unique id to avoid conflicts
573 var eventsid = message_el.closest('.vbo-dashboard-guests-latest').attr('data-eventsid') || (Math.floor(Math.random() * 100000));
574
575 // define unique modal event names to avoid conflicts
576 var modal_dismiss_event = 'dismiss-modal-wguestmessages-chat' + eventsid;
577 var modal_loading_event = 'loading-modal-wguestmessages-chat' + eventsid;
578
579 // check for multiple instances of this widget, maybe because of clicked notifications while another instance was displayed
580 if (jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').length > 1) {
581 // multiple instances found
582 if (message_el.closest('.vbo-dashboard-guests-latest').attr('data-eventsid')) {
583 // dismiss the previous modal and keep using the same event id to ensure a de-registration of the modal events
584 VBOCore.emitEvent(modal_dismiss_event);
585 } else {
586 // fallback to using a random events id to avoid conflicts
587 eventsid = Math.floor(Math.random() * 100000);
588 }
589 }
590
591 // base navigation URI to booking details
592 var booking_base_uri = jQuery('.vbo-widget-guest-messages-basenavuri').first().attr('href');
593
594 // build modal content
595 var chat_head_title = jQuery('<span></span>');
596 var chat_head_title_wrap = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info');
597 var chat_head_title_top = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-customer');
598 var chat_head_title_bot = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-booking');
599
600 var chat_head_title_img = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestavatar');
601 var chat_head_title_txt = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestname');
602 var chat_head_title_bid = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestbid');
603 chat_head_title_bid.append('<a class="badge badge-info" href="' + booking_base_uri.replace('%d', id) + '" target="_blank">' + id + '</a>');
604
605 var guest_avatar = message_el.find('.vbo-dashboard-guest-activity-avatar img');
606 var guest_name = message_el.find('.vbo-w-guestmessages-message-gtitle').text();
607 chat_head_title_txt.text(guest_name);
608 if (guest_avatar && guest_avatar.length) {
609 chat_head_title_img.append(guest_avatar.clone());
610 chat_head_title.append(chat_head_title_img);
611 }
612 chat_head_title_top.append(chat_head_title_txt);
613 chat_head_title_top.append(chat_head_title_bid);
614
615 chat_head_title_bot.append(message_el.find('.vbo-dashboard-guest-activity-content-info-icon').html());
616
617 // register callback for no-reply-needed click
618 var no_reply_needed_el = jQuery('<a></a>').addClass('label').attr('href', 'JavaScript: void(0);').text(Joomla.JText._('VBO_NO_REPLY_NEEDED'));
619 no_reply_needed_el.on('click', function() {
620 if (confirm(Joomla.JText._('VBO_WANT_PROCEED'))) {
621 var id_thread = message_el.attr('data-idthread');
622 if (!id_thread || !id_thread.length) {
623 return false;
624 }
625
626 // perform the request to flag the thread as no-reply-needed
627 var call_method = 'setNoReplyNeededThread';
628
629 VBOCore.doAjax(
630 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
631 {
632 widget_id: "<?php echo $this->getIdentifier(); ?>",
633 call: call_method,
634 bid: id,
635 id_thread: id_thread,
636 tmpl: "component"
637 },
638 (response) => {
639 try {
640 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
641 if (!obj_res.hasOwnProperty(call_method)) {
642 console.error('Unexpected JSON response', obj_res);
643 return false;
644 }
645
646 // emit the event to close (dismiss) the modal
647 VBOCore.emitEvent(modal_dismiss_event);
648
649 // reload the widget
650 vboWidgetGuestMessagesLoad(message_el.closest('.vbo-dashboard-guests-latest').attr('id'));
651 } catch(err) {
652 console.error('could not parse JSON response', err, response);
653 }
654 },
655 (error) => {
656 console.error(error);
657 }
658 );
659 }
660 });
661 chat_head_title_bot.append(no_reply_needed_el);
662
663 chat_head_title_wrap.append(chat_head_title_top);
664 chat_head_title_wrap.append(chat_head_title_bot);
665
666 chat_head_title.append(chat_head_title_wrap);
667
668 // display modal
669 var chat_modal_body = VBOCore.displayModal({
670 suffix: 'wguestmessages-chat',
671 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
672 title: chat_head_title,
673 dismiss_event: modal_dismiss_event,
674 onDismiss: () => {
675 if (typeof VCMChat !== 'undefined') {
676 VCMChat.getInstance().destroy();
677 }
678 },
679 loading_event: modal_loading_event,
680 });
681
682 // start loading
683 VBOCore.emitEvent(modal_loading_event);
684
685 // perform the request to render the chat in the apposite modal wrapper
686 var call_method = 'renderChat';
687
688 VBOCore.doAjax(
689 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
690 {
691 widget_id: "<?php echo $this->getIdentifier(); ?>",
692 call: call_method,
693 bid: id,
694 tmpl: "component"
695 },
696 (response) => {
697 // stop loading
698 VBOCore.emitEvent(modal_loading_event);
699
700 try {
701 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
702 if (!obj_res.hasOwnProperty(call_method)) {
703 console.error('Unexpected JSON response', obj_res);
704 return false;
705 }
706
707 // append HTML code to render the chat
708 chat_modal_body.html(obj_res[call_method]);
709
710 // register scroll to bottom with a small delay
711 setTimeout(() => {
712 if (typeof VCMChat !== 'undefined') {
713 VCMChat.getInstance().scrollToBottom();
714 }
715 }, 150);
716 } catch(err) {
717 console.error('could not parse JSON response', err, response);
718 }
719 },
720 (error) => {
721 // stop loading
722 VBOCore.emitEvent(modal_loading_event);
723 // display error
724 console.error(error);
725 alert(error.responseText);
726 }
727 );
728 }
729
730 /**
731 * Display the loading skeletons.
732 */
733 function vboWidgetGuestMessagesSkeletons(wrapper) {
734 var widget_instance = jQuery('#' + wrapper);
735 if (!widget_instance.length) {
736 return false;
737 }
738 widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
739 for (var i = 0; i < <?php echo $this->messages_per_page; ?>; i++) {
740 var skeleton = '';
741 skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
742 skeleton += ' <div class="vbo-dashboard-guest-activity-avatar">';
743 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
744 skeleton += ' </div>';
745 skeleton += ' <div class="vbo-dashboard-guest-activity-content">';
746 skeleton += ' <div class="vbo-dashboard-guest-activity-content-head">';
747 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
748 skeleton += ' </div>';
749 skeleton += ' <div class="vbo-dashboard-guest-activity-content-subhead">';
750 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
751 skeleton += ' </div>';
752 skeleton += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
753 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
754 skeleton += ' </div>';
755 skeleton += ' </div>';
756 skeleton += '</div>';
757 // append skeleton
758 jQuery(skeleton).appendTo(widget_instance.find('.vbo-dashboard-guest-messages-list'));
759 }
760 }
761
762 /**
763 * Perform the request to load the latest messages.
764 */
765 function vboWidgetGuestMessagesLoad(wrapper, bid_convo) {
766 var widget_instance = jQuery('#' + wrapper);
767 if (!widget_instance.length) {
768 return false;
769 }
770 var current_offset = parseInt(widget_instance.attr('data-offset'));
771 var length_per_page = parseInt(widget_instance.attr('data-length'));
772
773 // the widget method to call
774 var call_method = 'loadMessages';
775
776 // make a request to load the messages
777 VBOCore.doAjax(
778 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
779 {
780 widget_id: "<?php echo $this->getIdentifier(); ?>",
781 call: call_method,
782 return: 1,
783 bid_convo: bid_convo,
784 offset: current_offset,
785 length: length_per_page,
786 wrapper: wrapper,
787 tmpl: "component"
788 },
789 (response) => {
790 try {
791 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
792 if (!obj_res.hasOwnProperty(call_method)) {
793 console.error('Unexpected JSON response', obj_res);
794 return false;
795 }
796
797 // replace HTML with new messages
798 widget_instance.find('.vbo-dashboard-guest-messages-list').html(obj_res[call_method]['html']);
799
800 // check if latest datetime is set
801 if (obj_res[call_method]['latest_dt']) {
802 widget_instance.attr('data-latestdt', obj_res[call_method]['latest_dt']);
803 }
804
805 // check results
806 if (!isNaN(obj_res[call_method]['tot_messages']) && parseInt(obj_res[call_method]['tot_messages']) < 1) {
807 // no results can indicate the offset is invalid or too high
808 if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
809 // reset offset to 0
810 widget_instance.attr('data-offset', 0);
811 // show loading skeletons
812 vboWidgetGuestMessagesSkeletons(wrapper);
813 // reload the first page
814 vboWidgetGuestMessagesLoad(wrapper);
815 }
816 }
817 } catch(err) {
818 console.error('could not parse JSON response', err, response);
819 }
820 },
821 (error) => {
822 // remove the skeleton loading
823 widget_instance.find('.vbo-dashboard-guest-messages-list').find('.vbo-dashboard-guest-activity-skeleton').remove();
824 console.error(error);
825 }
826 );
827 }
828
829 /**
830 * Navigate between the various pages of the messages.
831 */
832 function vboWidgetGuestMessagesNavigate(wrapper, direction) {
833 var widget_instance = jQuery('#' + wrapper);
834 if (!widget_instance.length) {
835 return false;
836 }
837
838 // current offset
839 var current_offset = parseInt(widget_instance.attr('data-offset'));
840
841 // steps per type
842 var steps = <?php echo $this->messages_per_page; ?>;
843
844 // show loading skeletons
845 vboWidgetGuestMessagesSkeletons(wrapper);
846
847 // check direction and update offset for next nav
848 if (direction > 0) {
849 // navigate forward
850 widget_instance.attr('data-offset', (current_offset + steps));
851 } else {
852 // navigate backward
853 var new_offset = current_offset - steps;
854 new_offset = new_offset >= 0 ? new_offset : 0;
855 widget_instance.attr('data-offset', new_offset);
856 }
857
858 // launch navigation
859 vboWidgetGuestMessagesLoad(wrapper);
860 }
861
862 /**
863 * Watch periodically if there are new messages to be displayed.
864 */
865 function vboWidgetGuestMessagesWatch(wrapper) {
866 var widget_instance = jQuery('#' + wrapper);
867 if (!widget_instance.length) {
868 return false;
869 }
870
871 var latest_dt = widget_instance.attr('data-latestdt');
872 if (!latest_dt || !latest_dt.length) {
873 return false;
874 }
875
876 // the widget method to call
877 var call_method = 'watchMessages';
878
879 // make a request to watch the messages
880 VBOCore.doAjax(
881 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
882 {
883 widget_id: "<?php echo $this->getIdentifier(); ?>",
884 call: call_method,
885 latest_dt: latest_dt,
886 tmpl: "component"
887 },
888 (response) => {
889 try {
890 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
891 if (!obj_res.hasOwnProperty(call_method)) {
892 console.error('Unexpected JSON response', obj_res);
893 return false;
894 }
895 // response will contain the number of new messages
896 if (isNaN(obj_res[call_method]) || parseInt(obj_res[call_method]) < 1) {
897 // do nothing
898 return;
899 }
900 // new messages found, reset the offset and re-load the first page
901 widget_instance.attr('data-offset', 0);
902 // show loading skeletons
903 vboWidgetGuestMessagesSkeletons(wrapper);
904 // reload the first page
905 vboWidgetGuestMessagesLoad(wrapper);
906 } catch(err) {
907 console.error('could not parse JSON response', err, response);
908 }
909 },
910 (error) => {
911 // do nothing
912 console.error(error);
913 }
914 );
915 }
916
917 </script>
918 <?php
919 }
920 ?>
921
922 <script type="text/javascript">
923
924 jQuery(function() {
925
926 // when document is ready, load latest messages for this widget's instance
927 vboWidgetGuestMessagesLoad('<?php echo $wrapper_id; ?>', '<?php echo $bid_convo; ?>');
928
929 // make sure we've got no other chat instances on the same page (editorder)
930 if (jQuery('#vbmessagingdiv').length) {
931 if (typeof VCMChat !== 'undefined') {
932 VCMChat.getInstance().destroy();
933 }
934 jQuery('#vbmessagingdiv').html('');
935 }
936
937 // set interval for loading new messages automatically
938 var watch_intv = setInterval(function() {
939 vboWidgetGuestMessagesWatch('<?php echo $wrapper_id; ?>');
940 }, 60000);
941
942 <?php
943 if ($js_intvals_id) {
944 // widget can be dismissed through the modal
945 ?>
946 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_intvals_id; ?>', (e) => {
947 // clear interval for notifications
948 clearInterval(watch_intv);
949
950 if (jQuery('#vbmessagingdiv').length) {
951 // reload the page for the previously removed chat in the editorder page
952 location.reload();
953 }
954 });
955 <?php
956 }
957 ?>
958
959 });
960
961 </script>
962
963 <?php
964 }
965 }
966