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

1,432 lines 51.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - 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 // datepicker calendar
121 $this->vbo_app->loadDatePicker();
122
123 // additional language defs
124 JText::script('VBO_NO_REPLY_NEEDED');
125 JText::script('VBO_WANT_PROCEED');
126 JText::script('VBOSIGNATURECLEAR');
127 JText::script('VBODASHSEARCHKEYS');
128 }
129 }
130
131 /**
132 * Custom method for this widget only to load the latest guest messages.
133 * The method is called by the admin controller through an AJAX request.
134 * The visibility should be public, it should not exit the process, and
135 * an associative array is returned thanks to the request value "return":1.
136 *
137 * It's the actual rendering of the widget which also allows navigation.
138 */
139 public function loadMessages()
140 {
141 $input = JFactory::getApplication()->input;
142
143 $bid_convo = $input->getString('bid_convo', '');
144 $filters = $input->get('filters', [], 'array');
145 $offset = $input->getInt('offset', 0);
146 $length = $input->getInt('length', $this->messages_per_page);
147 $wrapper = $input->getString('wrapper', '');
148
149 if (!$this->vcm_exists) {
150 VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
151 }
152
153 // build search filters
154 $search_filters = [
155 'guest_name' => $filters['guest_name'] ?? '',
156 'message' => $filters['message'] ?? '',
157 'sender' => $filters['sender'] ?? '',
158 'fromdt' => $filters['fromdt'] ?? '',
159 'todt' => $filters['todt'] ?? '',
160 ];
161
162 // filter out empty search filter values
163 $search_filters = array_filter($search_filters);
164
165 if (!empty($search_filters['fromdt'])) {
166 // convert the date string from local format to military
167 $search_filters['fromdt'] = date('Y-m-d H:i:s', VikBooking::getDateTimestamp($search_filters['fromdt'], 0, 0, 0));
168 // convert date from local timezone to UTC
169 $search_filters['fromdt'] = JFactory::getDate($search_filters['fromdt'], date_default_timezone_get())->format('Y-m-d H:i:s');
170 }
171
172 if (!empty($search_filters['todt'])) {
173 // convert the date string from local format to military
174 $search_filters['todt'] = date('Y-m-d H:i:s', VikBooking::getDateTimestamp($search_filters['todt'], 23, 59, 59));
175 // convert date from local timezone to UTC
176 $search_filters['todt'] = JFactory::getDate($search_filters['todt'], date_default_timezone_get())->format('Y-m-d H:i:s');
177 }
178
179 // initiate the chat messaging object
180 $chat_messaging = class_exists('VCMChatMessaging') ? VCMChatMessaging::getInstance() : null;
181
182 // load latest messages
183 $latest_messages = [];
184
185 try {
186 /**
187 * Search filters require an updated VCM version.
188 *
189 * @since 1.16.9 (J) - 1.6.9 (WP)
190 * @requires VCM >= 1.8.27
191 */
192 if ($search_filters && $chat_messaging && method_exists($chat_messaging, 'searchMessages')) {
193 // search for specific messages with the specified search filters
194 $latest_messages = $chat_messaging->searchMessages($search_filters, $offset, $length);
195 } else {
196 // regular loading of the latest guest messages with no search filters
197 $latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], $offset, $length);
198 }
199 } catch (Exception $e) {
200 // do nothing
201 }
202
203 // the multitask data and notifications can request a specific conversation to be opened
204 $bubble_convo = null;
205 if ($bid_convo) {
206 // make sure the requested booking ID was fetched from the most recent guest messages
207 foreach ($latest_messages as $gmessage) {
208 if ($bid_convo == $gmessage->idorder) {
209 // specific conversation to bubble found
210 $bubble_convo = $bid_convo;
211 break;
212 }
213 if ($bid_convo == $gmessage->idorderota && strcasecmp((string)$gmessage->channel, 'vikbooking')) {
214 // specific OTA conversation to bubble found
215 $bubble_convo = $bid_convo;
216 break;
217 }
218 }
219 if (!$bubble_convo && $chat_messaging) {
220 // updated VCM versions will allow us to fetch one conversation by booking ID
221 $booking_messages = $chat_messaging->loadBookingGuestThreads($bid_convo);
222 if ($booking_messages) {
223 // append the requested conversation so that it will bubble
224 $latest_messages = array_merge($latest_messages, $booking_messages);
225 // turn flag on
226 $bubble_convo = $bid_convo;
227 }
228 }
229 }
230
231 // current year Y and timestamp
232 $current_y = date('Y');
233 $current_ts = time();
234
235 // start output buffering
236 ob_start();
237
238 if (!$latest_messages) {
239 ?>
240 <p class="info"><?php echo JText::translate('VBO_NO_RECORDS_FOUND'); ?></p>
241 <?php
242 }
243
244 foreach ($latest_messages as $gmessage) {
245 $gmessage_content = $gmessage->content;
246 if (empty($gmessage_content)) {
247 $gmessage_content = '.....';
248 } elseif (strlen($gmessage_content) > 90) {
249 if (function_exists('mb_substr')) {
250 $gmessage_content = mb_substr($gmessage_content, 0, 90, 'UTF-8');
251 } else {
252 $gmessage_content = substr($gmessage_content, 0, 90);
253 }
254 $gmessage_content .= '...';
255 }
256
257 ?>
258 <div
259 class="vbo-dashboard-guest-activity vbo-w-guestmessages-message<?php echo empty($gmessage->read_dt) && !strcasecmp($gmessage->sender_type, 'guest') ? ' vbo-w-guestmessages-message-new' : ''; ?>"
260 data-idorder="<?php echo $gmessage->idorder; ?>"
261 data-idthread="<?php echo !empty($gmessage->id_thread) ? $gmessage->id_thread : ''; ?>"
262 data-idmessage="<?php echo !empty($gmessage->id_message) ? $gmessage->id_message : ''; ?>"
263 data-noreply-needed="<?php echo $gmessage->no_reply_needed ?: 0; ?>"
264 onclick="vboWidgetGuestMessagesOpenChat('<?php echo $gmessage->idorder; ?>');"
265 >
266 <div class="vbo-dashboard-guest-activity-avatar">
267 <?php
268 if (!empty($gmessage->guest_avatar)) {
269 // highest priority goes to the profile picture, not always available
270 ?>
271 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->guest_avatar; ?>" />
272 <?php
273 } elseif (!empty($gmessage->pic)) {
274 // customer profile picture is not the same as the photo avatar
275 ?>
276 <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; ?>" />
277 <?php
278 } elseif (!empty($gmessage->channel_logo)) {
279 // channel logo goes as second option
280 ?>
281 <img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->channel_logo; ?>" />
282 <?php
283 } else {
284 // we use an icon as fallback
285 VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon');
286 }
287 ?>
288 </div>
289 <div class="vbo-dashboard-guest-activity-content">
290 <div class="vbo-dashboard-guest-activity-content-head">
291 <div class="vbo-dashboard-guest-activity-content-info-details">
292 <h4 class="vbo-w-guestmessages-message-gtitle"><span><?php
293 if (!$gmessage->first_name && !$gmessage->last_name) {
294 echo JText::translate('VBO_GUEST');
295 } else {
296 echo $gmessage->first_name . (!empty($gmessage->last_name) ? ' ' . $gmessage->last_name : '');
297 }
298 ?></span><?php
299 if (empty($gmessage->read_dt) && !strcasecmp($gmessage->sender_type, 'guest')) {
300 // print also an icon to inform that the message was not read
301 echo ' ';
302 VikBookingIcons::e('envelope', 'message-new');
303 } elseif (($gmessage->replied ?? 1) == 0 && !strcasecmp($gmessage->sender_type, 'guest')) {
304 /**
305 * Display a label to show that the message was not replied.
306 *
307 * @since 1.16.9 (J) - 1.6.9 (WP)
308 *
309 * @requires VCM >= 1.8.27
310 */
311 echo ' <span class="label label-small message-unreplied">';
312 VikBookingIcons::e('comments', 'message-reply');
313 echo ' ' . JText::translate('VBO_REPLY') . '</span>';
314 }
315 ?></h4>
316 <div class="vbo-dashboard-guest-activity-content-info-icon">
317 <?php
318 if (!empty($gmessage->b_status)) {
319 switch ($gmessage->b_status) {
320 case 'standby':
321 $badge_class = 'badge-warning';
322 $badge_text = JText::translate('VBSTANDBY');
323 break;
324 case 'cancelled':
325 $badge_class = 'badge-danger';
326 $badge_text = JText::translate('VBCANCELLED');
327 break;
328 default:
329 $badge_class = 'badge-success';
330 $badge_text = JText::translate('VBCONFIRMED');
331 if (!empty($gmessage->b_checkout) && $gmessage->b_checkout < $current_ts) {
332 $badge_text = JText::translate('VBOCHECKEDSTATUSOUT');
333 }
334 break;
335 }
336 ?>
337 <span class="badge <?php echo $badge_class; ?>"><?php echo $badge_text; ?></span>
338 <?php
339 }
340 if (!empty($gmessage->b_checkin)) {
341 $stay_info_in = getdate($gmessage->b_checkin);
342 $stay_info_out = getdate($gmessage->b_checkout);
343 $str_checkin = date('d', $gmessage->b_checkin);
344 $str_checkin .= $stay_info_in['mon'] != $stay_info_out['mon'] ? ' ' . VikBooking::sayMonth($stay_info_in['mon'], $short = true) : '';
345 $str_checkout = date('d', $gmessage->b_checkout) . ' ' . VikBooking::sayMonth($stay_info_out['mon'], $short = true);
346 if ($stay_info_in['year'] != $stay_info_out['year'] || $stay_info_in['year'] != $current_y || $stay_info_out['year'] != $current_y) {
347 $str_checkout .= ' ' . $stay_info_in['year'];
348 }
349 ?>
350 <span class="vbo-w-guestmessages-message-staydates">
351 <span class="vbo-w-guestmessages-message-staydates-in"><?php echo $str_checkin; ?></span>
352 <span class="vbo-w-guestmessages-message-staydates-sep">-</span>
353 <span class="vbo-w-guestmessages-message-staydates-out"><?php echo $str_checkout; ?></span>
354 </span>
355 <?php
356 }
357 ?>
358 </div>
359 </div>
360 <div class="vbo-dashboard-guest-activity-content-info-date">
361 <span><?php echo JHtml::fetch('date', $gmessage->last_updated, 'H:i'); ?></span>
362 <?php
363 if (JHtml::fetch('date', $gmessage->last_updated, 'Y-m-d') != $this->today_ymd) {
364 // format and print the date
365 ?>
366 <span><?php echo JHtml::fetch('date', $gmessage->last_updated, str_replace('/', $this->datesep, $this->df)); ?></span>
367 <?php
368 } else {
369 // print "today"
370 ?>
371 <span><?php echo JText::translate('VBTODAY'); ?></span>
372 <?php
373 }
374 ?>
375 </div>
376 </div>
377 <div class="vbo-dashboard-guest-activity-content-info-msg">
378 <p><?php echo $gmessage_content; ?></p>
379 </div>
380 </div>
381 </div>
382 <?php
383 }
384
385 // append navigation
386 ?>
387 <div class="vbo-guestactivitywidget-commands">
388 <div class="vbo-guestactivitywidget-commands-main">
389 <?php
390 if ($offset > 0) {
391 // show backward navigation button
392 ?>
393 <div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-prev">
394 <span class="vbo-guestactivitywidget-prev" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
395 </div>
396 <?php
397 }
398 if ($latest_messages) {
399 // count current page number
400 $page_number = 1;
401 if ($offset > 0) {
402 $page_number = floor($offset / $length) + 1;
403 $page_number = $page_number > 0 ? $page_number : 1;
404 }
405 ?>
406 <div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-page">
407 <span class="vbo-guestactivitywidget-page"><?php echo JText::sprintf('VBO_PAGE_NUMBER', $page_number); ?></span>
408 </div>
409 <div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-next">
410 <span class="vbo-guestactivitywidget-next" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
411 </div>
412 <?php
413 }
414 ?>
415 </div>
416 </div>
417 <?php
418
419 // check if we should bubble a specific conversation
420 if ($bubble_convo) {
421 ?>
422 <script type="text/javascript">
423 setTimeout(() => {
424 vboWidgetGuestMessagesOpenChat('<?php echo $bubble_convo; ?>');
425 }, 300);
426 </script>
427 <?php
428 }
429
430 // append the total number of messages displayed, the current offset and the latest message datetime
431 $tot_messages = count($latest_messages);
432 $latest_datetime = !$search_filters && $tot_messages > 0 && $offset === 0 ? $latest_messages[0]->last_updated : null;
433
434 // get the HTML buffer
435 $html_content = ob_get_contents();
436 ob_end_clean();
437
438 // return an associative array of values
439 return [
440 'html' => $html_content,
441 'tot_messages' => $tot_messages,
442 'offset' => ($offset + $length),
443 'latest_dt' => $latest_datetime,
444 ];
445 }
446
447 /**
448 * Custom method for this widget only to watch the latest guest messages.
449 * The method is called by the admin controller through an AJAX request.
450 * The visibility should be public, it should not exit the process, and
451 * any content sent to output will be returned to the AJAX response.
452 *
453 * Outputs the new number of messages found from the latest datetime.
454 */
455 public function watchMessages()
456 {
457 $latest_dt = VikRequest::getString('latest_dt', '', 'request');
458 if (empty($latest_dt)) {
459 echo '0';
460 return;
461 }
462
463 if (!$this->vcm_exists) {
464 VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
465 }
466
467 // load the latest guest message (one is sufficient)
468 $latest_messages = [];
469 try {
470 $latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], 0, 1);
471 } catch (Exception $e) {
472 // do nothing
473 }
474
475 if (!count($latest_messages) || $latest_messages[0]->last_updated == $latest_dt) {
476 // no newest messages found
477 echo '0';
478 return;
479 }
480
481 // print 1 to indicate that new messages should be reloaded
482 echo '1';
483 }
484
485 /**
486 * Custom method for this widget only to render the chat of a booking.
487 * The method is called by the admin controller through an AJAX request.
488 * The visibility should be public, it should not exit the process, and
489 * no values should be returned, as the response must be sent to output
490 * in case the JS/CSS assets will be echoed within the response.
491 *
492 * Returns the necessary HTML code to render the chat.
493 */
494 public function renderChat()
495 {
496 $bid = VikRequest::getInt('bid', 0, 'request');
497
498 $booking = VikBooking::getBookingInfoFromID($bid);
499
500 if (!$booking) {
501 VBOHttpDocument::getInstance()->close(404, 'Could not find booking');
502 }
503
504 // initialize chat instance by getting the proper channel name
505 if (empty($booking['channel'])) {
506 // front-end reservation chat handler
507 $chat_channel = 'vikbooking';
508 } else {
509 $channelparts = explode('_', $booking['channel']);
510 // check if this is a meta search channel
511 $is_meta_search = false;
512 if (preg_match("/(customer).*[0-9]$/", $channelparts[0]) || !strcasecmp($channelparts[0], 'googlehotel') || !strcasecmp($channelparts[0], 'trivago')) {
513 $is_meta_search = empty($booking['idorderota']);
514 }
515 if ($is_meta_search) {
516 // customer of type sales channel should use front-end reservation chat handler
517 $chat_channel = 'vikbooking';
518 } else {
519 // let the getInstance method validate the channel chat handler
520 $chat_channel = $booking['channel'];
521 }
522 }
523 $messaging = VikBooking::getVcmChatInstance($booking['id'], $chat_channel);
524
525 if (is_null($messaging)) {
526 VBOHttpDocument::getInstance()->close(500, 'Could not render chat');
527 }
528
529 // send content to output
530 echo $messaging->renderChat([
531 'hideThreads' => 1,
532 ], $load_assets = false);
533 }
534
535 /**
536 * Custom method for this widget only to update the thread of a booking.
537 * The method is called by the admin controller through an AJAX request.
538 * The visibility should be public, it should not exit the process, and
539 * no values should be returned, as the response must be sent to output.
540 *
541 * Returns a successful string or throws an error.
542 */
543 public function setNoReplyNeededThread()
544 {
545 $bid = VikRequest::getInt('bid', 0, 'request');
546 $id_thread = VikRequest::getInt('id_thread', 0, 'request');
547 $status = VikRequest::getInt('status', 0, 'request');
548
549 $booking = VikBooking::getBookingInfoFromID($bid);
550
551 if (!$booking || empty($id_thread)) {
552 VBOHttpDocument::getInstance()->close(404, 'Could not find booking thread');
553 }
554
555 // build thread object for update
556 $thread = new stdClass;
557 $thread->id = $id_thread;
558 $thread->idorder = $bid;
559 $thread->no_reply_needed = !$status ? 1 : 0;
560
561 $dbo = JFactory::getDbo();
562
563 if (!$dbo->updateObject('#__vikchannelmanager_threads', $thread, ['id', 'idorder'])) {
564 VBOHttpDocument::getInstance()->close(500, 'Could not update thread');
565 }
566
567 echo '1';
568 }
569
570 /**
571 * Custom method for this widget only to load the listing details for the booking.
572 * The method is called by the admin controller through an AJAX request.
573 * The visibility should be public, it should not exit the process, and
574 * an associative array is returned thanks to the request value "return":1.
575 *
576 * Needed to provide additional information to the host about the booked listing(s).
577 */
578 public function loadListingDetails()
579 {
580 $bid = VikRequest::getInt('bid', 0, 'request');
581
582 $booking_rooms = VikBooking::loadOrdersRoomsData($bid);
583
584 if (!$this->vcm_exists || !$booking_rooms) {
585 VBOHttpDocument::getInstance()->close(500, 'Could not obtain the listing information');
586 }
587
588 return [
589 'listings' => array_column($booking_rooms, 'room_name'),
590 ];
591 }
592
593 public function render(VBOMultitaskData $data = null)
594 {
595 // increase widget's instance counter
596 static::$instance_counter++;
597
598 // check whether the widget is being rendered via AJAX when adding it through the customizer
599 $is_ajax = $this->isAjaxRendering();
600
601 // generate a unique ID for the guest messages wrapper instance
602 $wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
603 $wrapper_id = 'vbo-widget-guest-messages-' . $wrapper_instance;
604
605 // this widget will work only if VCM is available and updated, and if permissions are met
606 $vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
607 if (!$this->vcm_exists || !$vbo_auth_bookings) {
608 return;
609 }
610
611 // multitask data event identifier for clearing intervals
612 $js_intvals_id = '';
613 $bid_convo = 0;
614 $contains_filter = '';
615 if ($data && $data->isModalRendering()) {
616 // access Multitask data
617 $js_intvals_id = $data->getModalJsIdentifier();
618
619 // check if a specific conversation should be opened
620 if ($data->get('id_message', 0)) {
621 $bid_convo = $data->getBookingId();
622 } else {
623 $bid_convo = $this->options()->fetchBookingId();
624 }
625
626 // check if a specific filter was set for searching guest messages
627 $contains_filter = (string) $this->options()->get('message_contains', '');
628 }
629
630 ?>
631 <div class="vbo-admin-widget-wrapper">
632 <div class="vbo-admin-widget-head">
633 <div class="vbo-admin-widget-head-inline">
634 <h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
635 <div class="vbo-admin-widget-head-commands">
636 <div class="vbo-reportwidget-commands">
637 <div class="vbo-reportwidget-commands-main">
638 <div class="vbo-reportwidget-command-dates">
639 <div id="<?php echo $wrapper_id; ?>-filters" class="vbo-reportwidget-period-name" style="<?php echo !$contains_filter ? 'display: none;' : ''; ?>"><?php VikBookingIcons::e('search'); ?> <?php echo JText::translate('VBO_FILTERS_APPLIED'); ?></div>
640 </div>
641 </div>
642 <div class="vbo-reportwidget-command-dots">
643 <span class="vbo-widget-command-togglefilters vbo-widget-guest-messages-togglefilters" onclick="vboWidgetGuestMessagesOpenSettings('<?php echo $wrapper_id; ?>');">
644 <?php VikBookingIcons::e('ellipsis-v'); ?>
645 </span>
646 </div>
647 </div>
648 </div>
649 </div>
650 </div>
651 <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="">
652 <div class="vbo-dashboard-guest-messages-inner">
653 <div class="vbo-dashboard-guest-messages-list">
654 <?php
655 for ($i = 0; $i < $this->messages_per_page; $i++) {
656 ?>
657 <div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">
658 <div class="vbo-dashboard-guest-activity-avatar">
659 <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>
660 </div>
661 <div class="vbo-dashboard-guest-activity-content">
662 <div class="vbo-dashboard-guest-activity-content-head">
663 <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>
664 </div>
665 <div class="vbo-dashboard-guest-activity-content-subhead">
666 <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>
667 </div>
668 <div class="vbo-dashboard-guest-activity-content-info-msg">
669 <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>
670 </div>
671 </div>
672 </div>
673 <?php
674 }
675 ?>
676 </div>
677 </div>
678 <div class="vbo-widget-guest-messages-filters-hidden" style="display: none;">
679 <div class="vbo-widget-guest-messages-filters-wrap">
680 <div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
681 <div class="vbo-params-wrap">
682 <div class="vbo-params-container">
683
684 <div class="vbo-param-container">
685 <div class="vbo-param-label"><?php echo JText::translate('VBCUSTOMERNOMINATIVE'); ?></div>
686 <div class="vbo-param-setting">
687 <input type="text" class="vbo-widget-guest-messages-guestname" value="" autocomplete="off" />
688 <span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_FIRST_NAME_ACCURATE_HELP'); ?></span>
689 </div>
690 </div>
691
692 <div class="vbo-param-container">
693 <div class="vbo-param-label"><?php echo JText::translate('VBSENDEMAILCUSTCONT'); ?></div>
694 <div class="vbo-param-setting">
695 <input type="text" class="vbo-widget-guest-messages-messcontains" value="<?php echo JHtml::fetch('esc_attr', $contains_filter); ?>" autocomplete="off" />
696 <span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_MESSAGE_CONTAINS_HELP'); ?></span>
697 </div>
698 </div>
699
700 <div class="vbo-param-container">
701 <div class="vbo-param-label"><?php echo JText::translate('VBO_SENDER'); ?></div>
702 <div class="vbo-param-setting">
703 <div class="vbo-widget-guest-messages-multistate">
704 <?php
705 echo $this->vbo_app->multiStateToggleSwitchField(
706 'sender' . $wrapper_instance,
707 '',
708 [
709 'guest',
710 'hotel',
711 ],
712 [
713 [
714 'value' => JText::translate('VBO_GUEST'),
715 ],
716 [
717 'value' => 'Hotel',
718 ],
719 ],
720 [
721 [
722 'label_class' => 'vik-multiswitch-text vik-multiswitch-radiobtn-guest',
723 'input' => [
724 'class' => 'vbo-widget-guest-messages-filter-sender',
725 ],
726 ],
727 [
728 'label_class' => 'vik-multiswitch-text vik-multiswitch-radiobtn-hotel',
729 'input' => [
730 'class' => 'vbo-widget-guest-messages-filter-sender',
731 ],
732 ],
733 ],
734 [
735 'class' => 'vik-multiswitch-noanimation',
736 ]
737 );
738 ?>
739 </div>
740 </div>
741 </div>
742
743 <div class="vbo-param-container">
744 <div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDFROMRANGE'); ?></div>
745 <div class="vbo-param-setting">
746 <div class="vbo-field-calendar">
747 <div class="input-append">
748 <input type="text" class="vbo-widget-guest-messages-fromdt" value="" autocomplete="off" />
749 <button type="button" class="btn btn-secondary vbo-widget-guest-messages-fromdt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
750 </div>
751 </div>
752 <span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_FROM_DT_HELP'); ?></span>
753 </div>
754 </div>
755
756 <div class="vbo-param-container">
757 <div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDTORANGE'); ?></div>
758 <div class="vbo-param-setting">
759 <div class="vbo-field-calendar">
760 <div class="input-append">
761 <input type="text" class="vbo-widget-guest-messages-todt" value="" autocomplete="off" />
762 <button type="button" class="btn btn-secondary vbo-widget-guest-messages-todt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
763 </div>
764 </div>
765 <span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_TO_DT_HELP'); ?></span>
766 </div>
767 </div>
768
769 </div>
770 </div>
771 </div>
772 </div>
773 </div>
774 </div>
775 </div>
776
777 <?php
778 if (static::$instance_counter === 0 || $is_ajax) {
779 // HTML helper tag for URL routing and some JS functions should be loaded once per widget instance
780 $admin_file_base = VBOPlatformDetection::isWordPress() ? 'admin.php' : 'index.php';
781 ?>
782
783 <script type="text/javascript">
784
785 /**
786 * Open the settings to search/filter the guest messages.
787 */
788 function vboWidgetGuestMessagesOpenSettings(wrapper) {
789 var widget_instance = jQuery('#' + wrapper);
790 if (!widget_instance.length) {
791 return false;
792 }
793
794 // define unique modal event name to avoid conflicts
795 var eventsid = widget_instance.attr('data-eventsid') || (Math.floor(Math.random() * 100000));
796 var modal_dismiss_event = 'dismiss-modal-wguestmessages-search' + eventsid;
797
798 // the hidden container of the search input fields
799 var search_elements = widget_instance.find('.vbo-widget-guest-messages-filters-wrap');
800
801 // build the button element to apply the search filters
802 var apply_search_btn = document.createElement('button');
803 apply_search_btn.setAttribute('type', 'button');
804 apply_search_btn.classList.add('btn', 'btn-success');
805 apply_search_btn.append(document.createTextNode(Joomla.JText._('VBODASHSEARCHKEYS')));
806 apply_search_btn.addEventListener('click', () => {
807 VBOCore.emitEvent(modal_dismiss_event, JSON.stringify({applyfilters: 1, wrapper: wrapper}));
808 });
809
810 // build the button element to clear the search filters
811 var clear_search_btn = document.createElement('button');
812 clear_search_btn.setAttribute('type', 'button');
813 clear_search_btn.classList.add('btn');
814 clear_search_btn.append(document.createTextNode(Joomla.JText._('VBOSIGNATURECLEAR')));
815 clear_search_btn.addEventListener('click', () => {
816 VBOCore.emitEvent(modal_dismiss_event, JSON.stringify({clearfilters: 1, wrapper: wrapper}));
817 });
818
819 var search_modal_body = VBOCore.displayModal({
820 suffix: 'wguestmessages-search',
821 extra_class: 'vbo-modal-rounded vbo-modal-tall',
822 title: '<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_W_GUESTMESSAGES_TITLE')); ?> - ' + Joomla.JText._('VBODASHSEARCHKEYS'),
823 body_prepend: true,
824 draggable: true,
825 footer_left: clear_search_btn,
826 footer_right: apply_search_btn,
827 dismiss_event: modal_dismiss_event,
828 onDismiss: (e) => {
829 // always move back the search input fields
830 search_elements.appendTo(widget_instance.find('.vbo-widget-guest-messages-filters-hidden'));
831
832 if (!e || !e.detail) {
833 // no event data received, maybe the modal was simply dismissed
834 jQuery('#' + wrapper + '-filters').hide();
835 return;
836 }
837
838 // parse data received within the dismiss event
839 try {
840 let commands = JSON.parse(e.detail);
841
842 if (!commands['wrapper']) {
843 return;
844 }
845
846 if (commands['applyfilters']) {
847 // display filters applied label
848 jQuery('#' + wrapper + '-filters').show();
849 // reset offset to 0
850 widget_instance.attr('data-offset', 0);
851 // show loading skeletons
852 vboWidgetGuestMessagesSkeletons(commands['wrapper']);
853 // reload guest messages for this widget's instance with filters set
854 vboWidgetGuestMessagesLoad(commands['wrapper'], null);
855 }
856
857 if (commands['clearfilters']) {
858 // clear filters
859 widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="text"]').val('');
860 widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="radio"]').prop('checked', false);
861 // hide filters applied label
862 jQuery('#' + wrapper + '-filters').hide();
863 // reset offset to 0
864 widget_instance.attr('data-offset', 0);
865 // show loading skeletons
866 vboWidgetGuestMessagesSkeletons(commands['wrapper']);
867 // reload guest messages for this widget's instance with filters cleared
868 vboWidgetGuestMessagesLoad(commands['wrapper'], null);
869 }
870 } catch(e) {
871 // abort
872 return;
873 }
874 },
875 });
876
877 // move the search filter fields to the modal body
878 search_elements.appendTo(search_modal_body);
879 }
880
881 /**
882 * Open the chat for the clicked booking guest message
883 */
884 function vboWidgetGuestMessagesOpenChat(id) {
885 // clicked message
886 var message_el = jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').first();
887
888 if (message_el.hasClass('vbo-w-guestmessages-message-new')) {
889 // get rid of the "new/unread" status
890 message_el.removeClass('vbo-w-guestmessages-message-new');
891 if (message_el.find('i.message-new').length) {
892 message_el.find('i.message-new').remove();
893 }
894 }
895
896 // modal events unique id to avoid conflicts
897 var eventsid = message_el.closest('.vbo-dashboard-guests-latest').attr('data-eventsid') || (Math.floor(Math.random() * 100000));
898
899 // define unique modal event names to avoid conflicts
900 var modal_dismiss_event = 'dismiss-modal-wguestmessages-chat' + eventsid;
901 var modal_loading_event = 'loading-modal-wguestmessages-chat' + eventsid;
902
903 // check for multiple instances of this widget, maybe because of clicked notifications while another instance was displayed
904 if (jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').length > 1) {
905 // multiple instances found
906 if (message_el.closest('.vbo-dashboard-guests-latest').attr('data-eventsid')) {
907 // dismiss the previous modal and keep using the same event id to ensure a de-registration of the modal events
908 VBOCore.emitEvent(modal_dismiss_event);
909 } else {
910 // fallback to using a random events id to avoid conflicts
911 eventsid = Math.floor(Math.random() * 100000);
912 }
913 }
914
915 // build modal content
916 var chat_head_title = jQuery('<span></span>');
917 var chat_head_title_wrap = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info');
918 var chat_head_title_top = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-customer');
919 var chat_head_title_bot = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-booking');
920
921 var chat_head_title_img = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestavatar');
922 var chat_head_title_txt = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestname');
923 var chat_head_title_bid = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestbid');
924 var chat_head_open_bid = jQuery('<a></a>').addClass('badge badge-info').attr('href', 'JavaScript: void(0);').text(id).on('click', () => {
925 VBOCore.handleDisplayWidgetNotification({
926 widget_id: 'booking_details'
927 }, {
928 bid: id,
929 modal_options: {
930 suffix: 'widget_modal_inner_booking_details',
931 }
932 });
933 });
934 chat_head_title_bid.append(chat_head_open_bid);
935
936 var guest_avatar = message_el.find('.vbo-dashboard-guest-activity-avatar img');
937 var guest_name = message_el.find('.vbo-w-guestmessages-message-gtitle').find('span').first().text();
938 chat_head_title_txt.text(guest_name);
939 if (guest_avatar && guest_avatar.length) {
940 chat_head_title_img.append(guest_avatar.clone());
941 chat_head_title.append(chat_head_title_img);
942 }
943 chat_head_title_top.append(chat_head_title_txt);
944 chat_head_title_top.append(chat_head_title_bid);
945
946 chat_head_title_bot.append(message_el.find('.vbo-dashboard-guest-activity-content-info-icon').html());
947
948 // register callback for no-reply-needed click
949 var no_reply_needed_el = jQuery('<a></a>').addClass('label').attr('href', 'JavaScript: void(0);').text(Joomla.JText._('VBO_NO_REPLY_NEEDED'));
950 if (message_el.attr('data-noreply-needed') == 1) {
951 // this thread was marked as no-reply needed
952 no_reply_needed_el.addClass('label-danger');
953 }
954 no_reply_needed_el.on('click', () => {
955 if (confirm(Joomla.JText._('VBO_WANT_PROCEED'))) {
956 var id_thread = message_el.attr('data-idthread');
957 if (!id_thread || !id_thread.length) {
958 return false;
959 }
960
961 // perform the request to toggle the thread as no-reply-needed
962 var call_method = 'setNoReplyNeededThread';
963
964 VBOCore.doAjax(
965 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
966 {
967 widget_id: "<?php echo $this->getIdentifier(); ?>",
968 call: call_method,
969 bid: id,
970 id_thread: id_thread,
971 status: message_el.attr('data-noreply-needed'),
972 tmpl: "component"
973 },
974 (response) => {
975 try {
976 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
977 if (!obj_res.hasOwnProperty(call_method)) {
978 console.error('Unexpected JSON response', obj_res);
979 return false;
980 }
981
982 // emit the event to close (dismiss) the modal
983 VBOCore.emitEvent(modal_dismiss_event);
984
985 // reload the widget
986 vboWidgetGuestMessagesLoad(message_el.closest('.vbo-dashboard-guests-latest').attr('id'));
987 } catch(err) {
988 console.error('could not parse JSON response', err, response);
989 }
990 },
991 (error) => {
992 console.error(error);
993 }
994 );
995 }
996 });
997
998 // append element that will contain the listing details involved
999 var listing_details_parent_el = jQuery('<span></span>').addClass('vbo-w-guestmessages-message-stayrooms');
1000 var listing_details_el = jQuery('<span></span>').addClass('vbo-w-guestmessages-message-listings').text('...');
1001 listing_details_parent_el.append(listing_details_el);
1002 chat_head_title_bot.append(listing_details_parent_el);
1003
1004 // append no-reply-needed element
1005 chat_head_title_bot.append(no_reply_needed_el);
1006
1007 // append additional elements
1008 chat_head_title_wrap.append(chat_head_title_top);
1009 chat_head_title_wrap.append(chat_head_title_bot);
1010
1011 chat_head_title.append(chat_head_title_wrap);
1012
1013 // display modal
1014 var chat_modal_body = VBOCore.displayModal({
1015 suffix: 'wguestmessages-chat',
1016 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
1017 title: chat_head_title,
1018 draggable: true,
1019 dismiss_event: modal_dismiss_event,
1020 onDismiss: () => {
1021 if (typeof VCMChat !== 'undefined') {
1022 VCMChat.getInstance().destroy();
1023 }
1024 },
1025 loading_event: modal_loading_event,
1026 });
1027
1028 // start loading
1029 VBOCore.emitEvent(modal_loading_event);
1030
1031 // perform the request to render the chat in the apposite modal wrapper
1032 var call_method = 'renderChat';
1033
1034 VBOCore.doAjax(
1035 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1036 {
1037 widget_id: "<?php echo $this->getIdentifier(); ?>",
1038 call: call_method,
1039 bid: id,
1040 tmpl: "component"
1041 },
1042 (response) => {
1043 // stop loading
1044 VBOCore.emitEvent(modal_loading_event);
1045
1046 try {
1047 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1048 if (!obj_res.hasOwnProperty(call_method)) {
1049 console.error('Unexpected JSON response', obj_res);
1050 return false;
1051 }
1052
1053 // append HTML code to render the chat
1054 chat_modal_body.html(obj_res[call_method]);
1055
1056 // register scroll to bottom with a small delay
1057 setTimeout(() => {
1058 if (typeof VCMChat !== 'undefined') {
1059 VCMChat.getInstance().scrollToBottom();
1060 }
1061 }, 150);
1062 } catch(err) {
1063 console.error('could not parse JSON response', err, response);
1064 }
1065 },
1066 (error) => {
1067 // stop loading
1068 VBOCore.emitEvent(modal_loading_event);
1069 // display error
1070 console.error(error);
1071 alert(error.responseText);
1072 }
1073 );
1074
1075 // perform the request to load the listing details involved
1076 VBOCore.doAjax(
1077 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1078 {
1079 widget_id: "<?php echo $this->getIdentifier(); ?>",
1080 call: 'loadListingDetails',
1081 return: 1,
1082 bid: id,
1083 tmpl: "component"
1084 },
1085 (response) => {
1086 try {
1087 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1088 if (!obj_res.hasOwnProperty('loadListingDetails')) {
1089 console.error('Unexpected JSON response', obj_res);
1090 return false;
1091 }
1092
1093 if (!Array.isArray(obj_res['loadListingDetails']['listings']) || !obj_res['loadListingDetails']['listings'].length) {
1094 // remove listing details element
1095 chat_head_title_bot.find('.vbo-w-guestmessages-message-stayrooms').remove();
1096
1097 return false;
1098 }
1099
1100 // set listing details
1101 chat_head_title_bot.find('.vbo-w-guestmessages-message-listings').html('<?php VikBookingIcons::e('home'); ?> ' + obj_res['loadListingDetails']['listings'].join(', '));
1102 } catch(err) {
1103 console.error('could not parse JSON response', err, response);
1104 }
1105 },
1106 (error) => {
1107 // log error
1108 console.error(error);
1109 }
1110 );
1111 }
1112
1113 /**
1114 * Display the loading skeletons.
1115 */
1116 function vboWidgetGuestMessagesSkeletons(wrapper) {
1117 var widget_instance = jQuery('#' + wrapper);
1118 if (!widget_instance.length) {
1119 return false;
1120 }
1121 widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
1122 for (var i = 0; i < <?php echo $this->messages_per_page; ?>; i++) {
1123 var skeleton = '';
1124 skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
1125 skeleton += ' <div class="vbo-dashboard-guest-activity-avatar">';
1126 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
1127 skeleton += ' </div>';
1128 skeleton += ' <div class="vbo-dashboard-guest-activity-content">';
1129 skeleton += ' <div class="vbo-dashboard-guest-activity-content-head">';
1130 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
1131 skeleton += ' </div>';
1132 skeleton += ' <div class="vbo-dashboard-guest-activity-content-subhead">';
1133 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
1134 skeleton += ' </div>';
1135 skeleton += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
1136 skeleton += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
1137 skeleton += ' </div>';
1138 skeleton += ' </div>';
1139 skeleton += '</div>';
1140 // append skeleton
1141 jQuery(skeleton).appendTo(widget_instance.find('.vbo-dashboard-guest-messages-list'));
1142 }
1143 }
1144
1145 /**
1146 * Perform the request to load the latest messages.
1147 */
1148 function vboWidgetGuestMessagesLoad(wrapper, bid_convo) {
1149 var widget_instance = jQuery('#' + wrapper);
1150 if (!widget_instance.length) {
1151 return false;
1152 }
1153
1154 var current_offset = parseInt(widget_instance.attr('data-offset'));
1155 var length_per_page = parseInt(widget_instance.attr('data-length'));
1156
1157 // build search filter values
1158 var filters = {
1159 guest_name: widget_instance.find('input.vbo-widget-guest-messages-guestname').val(),
1160 message: widget_instance.find('input.vbo-widget-guest-messages-messcontains').val(),
1161 sender: widget_instance.find('input.vbo-widget-guest-messages-filter-sender[type="radio"]:checked').val(),
1162 fromdt: widget_instance.find('input.vbo-widget-guest-messages-fromdt').val(),
1163 todt: widget_instance.find('input.vbo-widget-guest-messages-todt').val(),
1164 };
1165
1166 // the widget method to call
1167 var call_method = 'loadMessages';
1168
1169 // make a request to load the messages
1170 VBOCore.doAjax(
1171 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1172 {
1173 widget_id: "<?php echo $this->getIdentifier(); ?>",
1174 call: call_method,
1175 return: 1,
1176 bid_convo: bid_convo,
1177 filters: filters,
1178 offset: current_offset,
1179 length: length_per_page,
1180 wrapper: wrapper,
1181 tmpl: "component"
1182 },
1183 (response) => {
1184 try {
1185 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1186 if (!obj_res.hasOwnProperty(call_method)) {
1187 console.error('Unexpected JSON response', obj_res);
1188 return false;
1189 }
1190
1191 // replace HTML with new messages
1192 widget_instance.find('.vbo-dashboard-guest-messages-list').html(obj_res[call_method]['html']);
1193
1194 // check if latest datetime is set
1195 if (obj_res[call_method]['latest_dt']) {
1196 widget_instance.attr('data-latestdt', obj_res[call_method]['latest_dt']);
1197 }
1198
1199 // check results
1200 if (!isNaN(obj_res[call_method]['tot_messages']) && parseInt(obj_res[call_method]['tot_messages']) < 1) {
1201 // no results can indicate the offset is invalid or too high
1202 if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
1203 // reset offset to 0
1204 widget_instance.attr('data-offset', 0);
1205 // show loading skeletons
1206 vboWidgetGuestMessagesSkeletons(wrapper);
1207 // reload the first page
1208 vboWidgetGuestMessagesLoad(wrapper);
1209 }
1210 } else {
1211 if (bid_convo) {
1212 // emit the event to read all notifications in the current context
1213 VBOCore.emitEvent('vbo-nc-read-notifications', {
1214 criteria: {
1215 group: 'guests',
1216 idorder: bid_convo,
1217 }
1218 });
1219 }
1220 }
1221 } catch(err) {
1222 console.error('could not parse JSON response', err, response);
1223 }
1224 },
1225 (error) => {
1226 // remove the skeleton loading
1227 widget_instance.find('.vbo-dashboard-guest-messages-list').find('.vbo-dashboard-guest-activity-skeleton').remove();
1228 console.error(error);
1229 }
1230 );
1231 }
1232
1233 /**
1234 * Navigate between the various pages of the messages.
1235 */
1236 function vboWidgetGuestMessagesNavigate(wrapper, direction) {
1237 var widget_instance = jQuery('#' + wrapper);
1238 if (!widget_instance.length) {
1239 return false;
1240 }
1241
1242 // current offset
1243 var current_offset = parseInt(widget_instance.attr('data-offset'));
1244
1245 // steps per type
1246 var steps = <?php echo $this->messages_per_page; ?>;
1247
1248 // show loading skeletons
1249 vboWidgetGuestMessagesSkeletons(wrapper);
1250
1251 // check direction and update offset for next nav
1252 if (direction > 0) {
1253 // navigate forward
1254 widget_instance.attr('data-offset', (current_offset + steps));
1255 } else {
1256 // navigate backward
1257 var new_offset = current_offset - steps;
1258 new_offset = new_offset >= 0 ? new_offset : 0;
1259 widget_instance.attr('data-offset', new_offset);
1260 }
1261
1262 // launch navigation
1263 vboWidgetGuestMessagesLoad(wrapper);
1264 }
1265
1266 /**
1267 * Watch periodically if there are new messages to be displayed.
1268 */
1269 function vboWidgetGuestMessagesWatch(wrapper) {
1270 var widget_instance = jQuery('#' + wrapper);
1271 if (!widget_instance.length) {
1272 return false;
1273 }
1274
1275 var latest_dt = widget_instance.attr('data-latestdt');
1276 if (!latest_dt || !latest_dt.length) {
1277 return false;
1278 }
1279
1280 // the widget method to call
1281 var call_method = 'watchMessages';
1282
1283 // make a request to watch the messages
1284 VBOCore.doAjax(
1285 "<?php echo $this->getExecWidgetAjaxUri(); ?>",
1286 {
1287 widget_id: "<?php echo $this->getIdentifier(); ?>",
1288 call: call_method,
1289 latest_dt: latest_dt,
1290 tmpl: "component"
1291 },
1292 (response) => {
1293 try {
1294 var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
1295 if (!obj_res.hasOwnProperty(call_method)) {
1296 console.error('Unexpected JSON response', obj_res);
1297 return false;
1298 }
1299 // response will contain the number of new messages
1300 if (isNaN(obj_res[call_method]) || parseInt(obj_res[call_method]) < 1) {
1301 // do nothing
1302 return;
1303 }
1304 // new messages found, reset the offset and re-load the first page
1305 widget_instance.attr('data-offset', 0);
1306 // show loading skeletons
1307 vboWidgetGuestMessagesSkeletons(wrapper);
1308 // reload the first page
1309 vboWidgetGuestMessagesLoad(wrapper);
1310 } catch(err) {
1311 console.error('could not parse JSON response', err, response);
1312 }
1313 },
1314 (error) => {
1315 // do nothing
1316 console.error(error);
1317 }
1318 );
1319 }
1320
1321 /**
1322 * Subscribe to the event emitted by VCM's chat handler when replying to a guest message.
1323 */
1324 document.addEventListener('vcm-guest-message-replied', (e) => {
1325 if (!e || !e.detail) {
1326 return;
1327 }
1328
1329 // pool of messaging elements to scan
1330 let elements = [];
1331
1332 // gather the supported event detail properties
1333 let idorder = e.detail.hasOwnProperty('idorder') ? e.detail['idorder'] : null;
1334 let idthread = e.detail.hasOwnProperty('idthread') ? e.detail['idthread'] : null;
1335 let idmessage = e.detail.hasOwnProperty('idmessage') ? e.detail['idmessage'] : null;
1336
1337 // check if some eligible elements can be fetched
1338 if (idorder) {
1339 elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idorder="' + idorder + '"]');
1340 } else if (idthread) {
1341 elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idthread="' + idthread + '"]');
1342 } else if (idmessage) {
1343 elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idmessage="' + idmessage + '"]');
1344 }
1345
1346 // scan all the elements from which the "reply" label should be removed, if any
1347 elements.forEach((element) => {
1348 let unreplied = element.querySelector('.message-unreplied');
1349 if (unreplied) {
1350 // remove node stating that the guest message needs a reply
1351 unreplied.remove();
1352 }
1353 });
1354 });
1355
1356 </script>
1357 <?php
1358 }
1359 ?>
1360
1361 <script type="text/javascript">
1362
1363 jQuery(function() {
1364
1365 // when document is ready, load latest messages for this widget's instance
1366 vboWidgetGuestMessagesLoad('<?php echo $wrapper_id; ?>', '<?php echo $bid_convo; ?>');
1367
1368 // make sure we've got no other chat instances on the same page (editorder)
1369 if (jQuery('#vbmessagingdiv').length) {
1370 if (typeof VCMChat !== 'undefined') {
1371 VCMChat.getInstance().destroy();
1372 }
1373 jQuery('#vbmessagingdiv').html('<p class="info"><?php echo JHtml::fetch('esc_attr', JText::translate('VBO_W_GUESTMESSAGES_TITLE')); ?> - <?php echo JHtml::fetch('esc_attr', JText::translate('VBO_MULTITASK_PANEL')); ?></p>');
1374 }
1375
1376 // set interval for loading new messages automatically
1377 var watch_intv = setInterval(function() {
1378 vboWidgetGuestMessagesWatch('<?php echo $wrapper_id; ?>');
1379 }, 60000);
1380
1381 // render datepicker calendar for dates navigation
1382 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-guest-messages-fromdt, .vbo-widget-guest-messages-todt').datepicker({
1383 maxDate: "+1d",
1384 yearRange: "<?php echo (date('Y') - 3); ?>:<?php echo date('Y'); ?>",
1385 changeMonth: true,
1386 changeYear: true,
1387 dateFormat: "<?php echo $this->getDateFormat('jui'); ?>",
1388 onSelect: function(selectedDate) {
1389 if (!selectedDate) {
1390 return;
1391 }
1392 if (jQuery(this).hasClass('vbo-widget-guest-messages-fromdt')) {
1393 let nowstart = jQuery(this).datepicker('getDate');
1394 let nowstartdate = new Date(nowstart.getTime());
1395 jQuery('.vbo-widget-guest-messages-todt').datepicker('option', {minDate: nowstartdate});
1396 }
1397 }
1398 });
1399
1400 // triggering for datepicker calendar icon
1401 jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-guest-messages-fromdt-trigger, .vbo-widget-guest-messages-todt-trigger').click(function() {
1402 var jdp = jQuery(this).parent().find('input.hasDatepicker');
1403 if (jdp.length) {
1404 jdp.focus();
1405 }
1406 });
1407
1408 <?php
1409 if ($js_intvals_id) {
1410 // widget can be dismissed through the modal
1411 ?>
1412 document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_intvals_id; ?>', (e) => {
1413 // clear interval for notifications
1414 clearInterval(watch_intv);
1415
1416 if (jQuery('#vbmessagingdiv').length) {
1417 // reload the page for the previously removed chat in the editorder page
1418 location.reload();
1419 }
1420 });
1421 <?php
1422 }
1423 ?>
1424
1425 });
1426
1427 </script>
1428
1429 <?php
1430 }
1431 }
1432