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

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