File "guest_messages.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/guest_messages.php
File size: 74.78 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - E4J srl
 * @copyright   Copyright (C) 2025 E4J srl. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 * @link        https://vikwp.com
 */

defined('ABSPATH') or die('No script kiddies please!');

/**
 * Class handler for admin widget "guest messages".
 * 
 * @since 	1.16.0 (J) - 1.6.0 (WP)
 */
class VikBookingAdminWidgetGuestMessages extends VikBookingAdminWidget
{
	/**
	 * The instance counter of this widget.
	 *
	 * @var 	int
	 */
	protected static $instance_counter = -1;

	/**
	 * Number of messages per page. Should be an even number.
	 * 
	 * @var 	int
	 */
	protected $messages_per_page = 6;

	/**
	 * Today Y-m-d string
	 * 
	 * @var 	string
	 */
	protected $today_ymd = null;

	/**
	 * The path to the VCM lib to see if it's available.
	 * 
	 * @var 	string
	 */
	protected $vcm_lib_path = '';

	/**
	 * Tells whether VCM is installed and updated.
	 * 
	 * @var 	bool
	 */
	protected $vcm_exists = true;

	/**
	 * The distance threshold in pixels between the current scroll
	 * position and the end of the list for triggering the loading
	 * of a next page within an infinite scroll mechanism.
	 *
	 * @var 	int
	 * 
	 * @since 	1.17.6 (J) - 1.7.6 (WP)
	 */
	protected $px_distance_threshold = 140;

	/**
	 * Number of minimum messages per page when using the inbox-style (modal only).
	 * 
	 * @var 	int
	 * 
	 * @since 	1.17.6 (J) - 1.7.6 (WP)
	 */
	protected $inbox_messages_per_page = 12;

	/**
	 * Class constructor will define the widget name and identifier.
	 */
	public function __construct()
	{
		// call parent constructor
		parent::__construct();

		$this->widgetName = JText::translate('VBO_W_GUESTMESSAGES_TITLE');
		$this->widgetDescr = JText::translate('VBO_W_GUESTMESSAGES_DESCR');
		$this->widgetId = basename(__FILE__, '.php');

		// define widget and icon and style name
		$this->widgetIcon = '<i class="' . VikBookingIcons::i('comment-dots') . '"></i>';
		$this->widgetStyleName = 'light-orange';

		// load widget's settings
		$this->widgetSettings = $this->loadSettings();
		if (!is_object($this->widgetSettings)) {
			$this->widgetSettings = new stdClass;
		}

		// today Y-m-d date
		$this->today_ymd = date('Y-m-d');

		// the path to the VCM library
		$this->vcm_lib_path = VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php';

		// whether VCM is available
		if (is_file($this->vcm_lib_path)) {
			if (!class_exists('VikChannelManager') || !method_exists('VikChannelManager', 'getLatestFromGuests')) {
				// VCM is outdated
				$this->vcm_exists = false;
			}

			// attempt to require the chat handler
			try {
				VikBooking::getVcmChatInstance($oid = 0, $channel = null);
			} catch (Exception $e) {
				// do nothing
			}

			// make sure VCM is up to date for this widget
			if (!class_exists('VCMChatHandler') || !method_exists('VCMChatHandler', 'loadChatAssets')) {
				// VCM is outdated (>= 1.8.11 required)
				$this->vcm_exists = false;
			}
		} else {
			$this->vcm_exists = false;
		}

		// avoid queries on certain pages, as VCM may not have been activated yet
		if (VBOPlatformDetection::isWordPress() && $this->vcm_exists) {
			global $pagenow;
			if (isset($pagenow) && in_array($pagenow, ['update.php', 'plugins.php', 'plugin-install.php'])) {
				$this->vcm_exists = false;
			}
		}
	}

	/**
	 * Preload the necessary CSS/JS assets from VCM.
	 * 
	 * @return 	void
	 */
	public function preload()
	{
		if ($this->vcm_exists) {
			// load chat assets from VCM
			VCMChatHandler::loadChatAssets();

			// datepicker calendar
			$this->vbo_app->loadDatePicker();

			// additional language defs
			JText::script('VBO_NO_REPLY_NEEDED');
			JText::script('VBO_WANT_PROCEED');
			JText::script('VBOSIGNATURECLEAR');
			JText::script('VBODASHSEARCHKEYS');
		}
	}

	/**
	 * @inheritDoc
	 * 
	 * @since 	1.17.6 (J) - 1.7.6 (WP)
	 */
	public function getWidgetDetails()
	{
		// get common widget details from parent abstract class
		$details = parent::getWidgetDetails();

		// append the modal rendering information
		$details['modal'] = [
			'add_class' => 'vbo-modal-large',
		];

		return $details;
	}

	/**
	 * Custom method for this widget only to load the latest guest messages.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * an associative array is returned thanks to the request value "return":1.
	 * 
	 * It's the actual rendering of the widget which also allows navigation.
	 */
	public function loadMessages()
	{
		$input = JFactory::getApplication()->input;

		$bid_convo = $input->getString('bid_convo', '');
		$filters   = $input->get('filters', [], 'array');
		$offset    = $input->getInt('offset', 0);
		$length    = $input->getInt('length', $this->messages_per_page);
		$wrapper   = $input->getString('wrapper', '');

		if (!$this->vcm_exists) {
			VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
		}

		// check messages custom limit per page
		if ($length > 0 && $length != $this->messages_per_page) {
			// update widget settings
			$this->widgetSettings->limpage = $length;
			$this->updateSettings(json_encode($this->widgetSettings));
		}

		// build search filters
		$search_filters = [
			'guest_name' => $filters['guest_name'] ?? '',
			'message'    => $filters['message'] ?? '',
			'sender'     => $filters['sender'] ?? '',
			'fromdt'     => $filters['fromdt'] ?? '',
			'todt'       => $filters['todt'] ?? '',
			'unread'     => (int) ($filters['unread'] ?? 0),
			'ai_sort'    => (int) ($filters['ai_sort'] ?? 0),
		];

		// filter out empty search filter values
		$search_filters = array_filter($search_filters);

		if (!empty($search_filters['fromdt'])) {
			// convert the date string from local format to military
			$search_filters['fromdt'] = date('Y-m-d H:i:s', VikBooking::getDateTimestamp($search_filters['fromdt'], 0, 0, 0));
			// convert date from local timezone to UTC
			$search_filters['fromdt'] = JFactory::getDate($search_filters['fromdt'], date_default_timezone_get())->format('Y-m-d H:i:s');
		}

		if (!empty($search_filters['todt'])) {
			// convert the date string from local format to military
			$search_filters['todt'] = date('Y-m-d H:i:s', VikBooking::getDateTimestamp($search_filters['todt'], 23, 59, 59));
			// convert date from local timezone to UTC
			$search_filters['todt'] = JFactory::getDate($search_filters['todt'], date_default_timezone_get())->format('Y-m-d H:i:s');
		}

		// initiate the chat messaging object
		$chat_messaging = class_exists('VCMChatMessaging') ? VCMChatMessaging::getInstance() : null;

		// last error description
		$latest_error = '';

		// load latest messages
		$latest_messages = [];

		try {
			/**
			 * Search filters require an updated VCM version.
			 * 
			 * @since 		1.16.9 (J) - 1.6.9 (WP)
			 * @requires 	VCM >= 1.8.27 (1.9.5 "unread", "ai_sort")
			 */
			if ($search_filters && $chat_messaging && method_exists($chat_messaging, 'searchMessages')) {
				// search for specific messages with the specified search filters
				$latest_messages = $chat_messaging->searchMessages($search_filters, $offset, $length);
			} else {
				// regular loading of the latest guest messages with no search filters
				$latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], $offset, $length);
			}
		} catch (Exception $e) {
			// do nothing, but populate the error description
			$latest_error = $e->getMessage();
		}

		// the multitask data and notifications can request a specific conversation to be opened
		$bubble_convo = null;
		if ($bid_convo) {
			// make sure the requested booking ID was fetched from the most recent guest messages
			foreach ($latest_messages as $gmessage) {
				if ($bid_convo == $gmessage->idorder) {
					// specific conversation to bubble found
					$bubble_convo = $bid_convo;
					break;
				}
				if ($bid_convo == $gmessage->idorderota && strcasecmp((string)$gmessage->channel, 'vikbooking')) {
					// specific OTA conversation to bubble found
					$bubble_convo = $bid_convo;
					break;
				}
			}
			if (!$bubble_convo && $chat_messaging) {
				// updated VCM versions will allow us to fetch one conversation by booking ID
				$booking_messages = $chat_messaging->loadBookingGuestThreads($bid_convo);
				if ($booking_messages) {
					// append the requested conversation so that it will bubble
					$latest_messages = array_merge($latest_messages, $booking_messages);
					// turn flag on
					$bubble_convo = $bid_convo;
				}
			}
		}

		// current year Y and timestamp
		$current_y  = date('Y');
		$current_ts = time();

		// start output buffering
		ob_start();

		if ($latest_error) {
			?>
			<p class="err"><?php echo $latest_error; ?></p>
			<?php
		}

		if (!$latest_messages) {
			?>
			<p class="info" data-no-messages="1" style="<?php echo $offset > 0 ? 'text-align: center;' : ''; ?>"><?php echo $offset > 0 ? JText::translate('VBO_NO_MORE_MESSAGES') : JText::translate('VBO_NO_RECORDS_FOUND'); ?></p>
			<?php
		}

		// count total messages
		$tot_messages = count($latest_messages);

		foreach ($latest_messages as $ind => $gmessage) {
			$gmessage_content = $gmessage->content;
			if (empty($gmessage_content)) {
				$gmessage_content = '.....';
			} elseif (strlen($gmessage_content) > 90) {
				if (function_exists('mb_substr')) {
					$gmessage_content = mb_substr($gmessage_content, 0, 90, 'UTF-8');
				} else {
					$gmessage_content = substr($gmessage_content, 0, 90);
				}
				$gmessage_content .= '...';
			}

			// build extra classes for main element
			$wrap_classes = [];
			if ($ind === 0) {
				$wrap_classes[] = 'vbo-w-guestmessages-message-first';
			} elseif ($ind == ($tot_messages - 1)) {
				$wrap_classes[] = 'vbo-w-guestmessages-message-last';
			}
			if (empty($gmessage->read_dt) && !strcasecmp($gmessage->sender_type, 'guest')) {
				$wrap_classes[] = 'vbo-w-guestmessages-message-new';
			}

			?>
			<div
				class="vbo-dashboard-guest-activity vbo-w-guestmessages-message<?php echo $wrap_classes ? ' ' . implode(' ', $wrap_classes) : ''; ?>"
				data-idorder="<?php echo $gmessage->idorder; ?>"
				data-idthread="<?php echo !empty($gmessage->id_thread) ? $gmessage->id_thread : ''; ?>"
				data-idmessage="<?php echo !empty($gmessage->id_message) ? $gmessage->id_message : ''; ?>"
				data-noreply-needed="<?php echo $gmessage->no_reply_needed ?: 0; ?>"
				onclick="vboWidgetGuestMessagesOpenChat('<?php echo $gmessage->idorder; ?>');"
			>
				<div class="vbo-dashboard-guest-activity-avatar">
				<?php
				if (!empty($gmessage->guest_avatar)) {
					// highest priority goes to the profile picture, not always available
					?>
					<img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->guest_avatar; ?>" />
					<?php
				} elseif (!empty($gmessage->pic)) {
					// customer profile picture is not the same as the photo avatar
					?>
					<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; ?>" />
					<?php
				} elseif (!empty($gmessage->channel_logo)) {
					// channel logo goes as second option
					?>
					<img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $gmessage->channel_logo; ?>" />
					<?php
				} else {
					// we use an icon as fallback
					VikBookingIcons::e('user', 'vbo-dashboard-guest-activity-avatar-icon');
				}

				// check for AI priority enum
				if (!empty($gmessage->ai_priority)) {
					// display AI-calculated priority icon and apposite class (expected "high", "medium" or "low")
					$priority_icon  = 'flag';
					$priority_class = preg_replace("/[^a-z]/", '', strtolower((string) $gmessage->ai_priority));
					$priority_value = ucwords((string) $gmessage->ai_priority);
					if (!strcasecmp(trim((string) $gmessage->ai_priority), 'high')) {
						$priority_value = JText::translate('VBO_PRIORITY_HIGH');
					} elseif (!strcasecmp(trim((string) $gmessage->ai_priority), 'medium')) {
						$priority_value = JText::translate('VBO_PRIORITY_MEDIUM');
					} elseif (!strcasecmp(trim((string) $gmessage->ai_priority), 'low')) {
						$priority_value = JText::translate('VBO_PRIORITY_LOW');
					}
					?>
					<div class="vbo-w-guestmessages-message-aipriority">
						<span class="vbo-w-guestmessages-message-aipriority-icn <?php echo $priority_class; ?> vbo-tooltip vbo-tooltip-right" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $priority_value); ?>"><?php VikBookingIcons::e($priority_icon, $priority_class); ?></span>
					</div>
					<?php
				}
				?>
				</div>
				<div class="vbo-dashboard-guest-activity-content">
					<div class="vbo-dashboard-guest-activity-content-head">
						<div class="vbo-dashboard-guest-activity-content-info-details">
							<h4 class="vbo-w-guestmessages-message-gtitle"><span><?php
							if (!$gmessage->first_name && !$gmessage->last_name) {
								echo JText::translate('VBO_GUEST');
							} else {
								echo $gmessage->first_name . (!empty($gmessage->last_name) ? ' ' . $gmessage->last_name : '');
							}
							?></span><?php
							if (empty($gmessage->read_dt) && !strcasecmp($gmessage->sender_type, 'guest')) {
								// print also an icon to inform that the message was not read
								echo ' ';
								VikBookingIcons::e('envelope', 'message-new');
							} elseif (($gmessage->replied ?? 1) == 0 && !strcasecmp($gmessage->sender_type, 'guest')) {
								/**
								 * Display a label to show that the message was not replied.
								 * 
								 * @since 		1.16.9 (J) - 1.6.9 (WP)
								 * 
								 * @requires 	VCM >= 1.8.27
								 */
								echo ' <span class="label label-small message-unreplied">';
								VikBookingIcons::e('comments', 'message-reply');
								echo ' ' . JText::translate('VBO_REPLY') . '</span>';
							}

							/**
							 * Display the AI message category, if available.
							 * 
							 * @since 		1.17.3 (J) - 1.7.3 (WP)
							 * 
							 * @requires 	VCM >= 1.9.5
							 */
							if (!empty($gmessage->ai_category)) {
								echo ' <span class="label label-small message-ai-category">';
								VikBookingIcons::e('tag');
								echo ' ' . $gmessage->ai_category . '</span>';
							}
							?></h4>
							<div class="vbo-dashboard-guest-activity-content-info-icon">
							<?php
							if (!empty($gmessage->b_status)) {
								switch ($gmessage->b_status) {
									case 'standby':
										$badge_class = 'badge-warning';
										$badge_text  = JText::translate('VBSTANDBY');
										break;
									case 'cancelled':
										$badge_class = 'badge-danger';
										$badge_text  = JText::translate('VBCANCELLED');
										break;
									default:
										$badge_class = 'badge-success';
										$badge_text  = JText::translate('VBCONFIRMED');
										if (!empty($gmessage->b_checkout) && $gmessage->b_checkout < $current_ts) {
											$badge_text  = JText::translate('VBOCHECKEDSTATUSOUT');
										}
										break;
								}
								?>
								<span class="badge <?php echo $badge_class; ?>"><?php echo $badge_text; ?></span>
								<?php
							}
							if (!empty($gmessage->b_checkin)) {
								$stay_info_in  = getdate($gmessage->b_checkin);
								$stay_info_out = getdate($gmessage->b_checkout);
								$str_checkin = date('d', $gmessage->b_checkin);
								$str_checkin .= $stay_info_in['mon'] != $stay_info_out['mon'] ? ' ' . VikBooking::sayMonth($stay_info_in['mon'], $short = true) : '';
								$str_checkout = date('d', $gmessage->b_checkout) . ' ' . VikBooking::sayMonth($stay_info_out['mon'], $short = true);
								if ($stay_info_in['year'] != $stay_info_out['year'] || $stay_info_in['year'] != $current_y || $stay_info_out['year'] != $current_y) {
									$str_checkout .= ' ' . $stay_info_in['year'];
								}
								?>
								<span class="vbo-w-guestmessages-message-staydates">
									<span class="vbo-w-guestmessages-message-staydates-in"><?php echo $str_checkin; ?></span>
									<span class="vbo-w-guestmessages-message-staydates-sep">-</span>
									<span class="vbo-w-guestmessages-message-staydates-out"><?php echo $str_checkout; ?></span>
								</span>
								<?php
							}
							?>
							</div>
						</div>
						<div class="vbo-dashboard-guest-activity-content-info-date">
							<span><?php echo JHtml::fetch('date', $gmessage->last_updated, 'H:i'); ?></span>
						<?php
						if (JHtml::fetch('date', $gmessage->last_updated, 'Y-m-d') != $this->today_ymd) {
							// format and print the date
							?>
							<span><?php echo JHtml::fetch('date', $gmessage->last_updated, str_replace('/', $this->datesep, $this->df)); ?></span>
							<?php
						} else {
							// print "today"
							?>
							<span><?php echo JText::translate('VBTODAY'); ?></span>
							<?php
						}
						?>
						</div>
					</div>
					<div class="vbo-dashboard-guest-activity-content-info-msg">
						<p><?php echo $gmessage_content; ?></p>
					</div>
				</div>
			</div>
			<?php
		}

		// append navigation
		?>
		<div class="vbo-guestactivitywidget-commands">
			<div class="vbo-guestactivitywidget-commands-main">
			<?php
			if ($offset > 0) {
				// show backward navigation button
				?>
				<div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-prev">
					<span class="vbo-guestactivitywidget-prev" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', -1);"><?php VikBookingIcons::e('chevron-left'); ?></span>
				</div>
				<?php
			}
			if ($latest_messages) {
				// count current page number
				$page_number = 1;
				if ($offset > 0) {
					$page_number = floor($offset / $length) + 1;
					$page_number = $page_number > 0 ? $page_number : 1;
				}
				?>
				<div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-page">
					<span class="vbo-guestactivitywidget-page"><?php echo JText::sprintf('VBO_PAGE_NUMBER', $page_number); ?></span>
				</div>
				<div class="vbo-guestactivitywidget-command-chevron vbo-guestactivitywidget-command-next">
					<span class="vbo-guestactivitywidget-next" onclick="vboWidgetGuestMessagesNavigate('<?php echo $wrapper; ?>', 1);"><?php VikBookingIcons::e('chevron-right'); ?></span>
				</div>
				<?php
			}
			?>
			</div>
		</div>

		<script type="text/javascript">
			setTimeout(() => {
			<?php
			// check if there were actually no filters applied
			if (!$search_filters || (count($search_filters) === 1 && ($search_filters['ai_sort'] ?? 0))) {
				?>
				jQuery('#<?php echo $wrapper; ?>-filters').hide();
				<?php
			}

			// check if results were sorted through AI
			if ($search_filters['ai_sort'] ?? 0) {
				?>
				jQuery('#<?php echo $wrapper; ?>-aipowered').fadeIn();
				<?php
			} else {
				?>
				jQuery('#<?php echo $wrapper; ?>-aipowered').hide();
				<?php
			}
			?>
			}, 200);
		</script>
		<?php

		// check if we should bubble a specific conversation
		if ($bubble_convo) {
			?>
		<script type="text/javascript">
			setTimeout(() => {
				vboWidgetGuestMessagesOpenChat('<?php echo $bubble_convo; ?>');
			}, 400);
		</script>
			<?php
		}

		// append the total number of messages displayed, the current offset and the latest message datetime
		$latest_datetime = !$search_filters && $tot_messages > 0 && $offset === 0 ? $latest_messages[0]->last_updated : null;

		// get the HTML buffer
		$html_content = ob_get_contents();
		ob_end_clean();

		// return an associative array of values
		return [
			'html' 		   => $html_content,
			'tot_messages' => $tot_messages,
			'offset' 	   => ($offset + $length),
			'latest_dt'    => $latest_datetime,
		];
	}

	/**
	 * Custom method for this widget only to watch the latest guest messages.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * any content sent to output will be returned to the AJAX response.
	 * 
	 * Outputs the new number of messages found from the latest datetime.
	 */
	public function watchMessages()
	{
		$latest_dt = VikRequest::getString('latest_dt', '', 'request');
		if (empty($latest_dt)) {
			echo '0';
			return;
		}

		if (!$this->vcm_exists) {
			VBOHttpDocument::getInstance()->close(500, 'Vik Channel Manager is either not available or outdated');
		}

		// load the latest guest message (one is sufficient)
		$latest_messages = [];
		try {
			$latest_messages = VikChannelManager::getLatestFromGuests(['guest_messages'], 0, 1);
		} catch (Exception $e) {
			// do nothing
		}

		if (!$latest_messages || $latest_messages[0]->last_updated == $latest_dt) {
			// no newest messages found
			echo '0';
			return;
		}

		// print 1 to indicate that new messages should be reloaded
		echo '1';
	}

	/**
	 * Custom method for this widget only to render the chat of a booking.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * no values should be returned, as the response must be sent to output
	 * in case the JS/CSS assets will be echoed within the response.
	 * 
	 * Returns the necessary HTML code to render the chat.
	 */
	public function renderChat()
	{
		$bid = VikRequest::getInt('bid', 0, 'request');

		$booking = VikBooking::getBookingInfoFromID($bid);

		if (!$booking) {
			VBOHttpDocument::getInstance()->close(404, 'Could not find booking');
		}

		// initialize chat instance by getting the proper channel name
		if (empty($booking['channel'])) {
			// front-end reservation chat handler
			$chat_channel = 'vikbooking';
		} else {
			$channelparts = explode('_', $booking['channel']);
			// check if this is a meta search channel
			$is_meta_search = false;
			if (preg_match("/(customer).*[0-9]$/", $channelparts[0]) || !strcasecmp($channelparts[0], 'googlehotel') || !strcasecmp($channelparts[0], 'googlevr') || !strcasecmp($channelparts[0], 'trivago')) {
				$is_meta_search = empty($booking['idorderota']);
			}
			if ($is_meta_search) {
				// customer of type sales channel should use front-end reservation chat handler
				$chat_channel = 'vikbooking';
			} else {
				// let the getInstance method validate the channel chat handler
				$chat_channel = $booking['channel'];
			}
		}
		$messaging = VikBooking::getVcmChatInstance($booking['id'], $chat_channel);

		if (is_null($messaging)) {
			VBOHttpDocument::getInstance()->close(500, 'Could not render chat');
		}

		// send content to output
		echo $messaging->renderChat([
			'hideThreads' => 1,
		], $load_assets = false);
	}

	/**
	 * Custom method for this widget only to update the thread of a booking.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * no values should be returned, as the response must be sent to output.
	 * 
	 * Returns a successful string or throws an error.
	 */
	public function setNoReplyNeededThread()
	{
		$bid 	   = VikRequest::getInt('bid', 0, 'request');
		$id_thread = VikRequest::getInt('id_thread', 0, 'request');
		$status    = VikRequest::getInt('status', 0, 'request');

		$booking = VikBooking::getBookingInfoFromID($bid);

		if (!$booking || empty($id_thread)) {
			VBOHttpDocument::getInstance()->close(404, 'Could not find booking thread');
		}

		// build thread object for update
		$thread = new stdClass;
		$thread->id = $id_thread;
		$thread->idorder = $bid;
		$thread->no_reply_needed = !$status ? 1 : 0;

		$dbo = JFactory::getDbo();

		if (!$dbo->updateObject('#__vikchannelmanager_threads', $thread, ['id', 'idorder'])) {
			VBOHttpDocument::getInstance()->close(500, 'Could not update thread');
		}

		echo '1';
	}

	/**
	 * Custom method for this widget only to load the listing details for the booking.
	 * The method is called by the admin controller through an AJAX request.
	 * The visibility should be public, it should not exit the process, and
	 * an associative array is returned thanks to the request value "return":1.
	 * 
	 * Needed to provide additional information to the host about the booked listing(s).
	 */
	public function loadListingDetails()
	{
		$bid = VikRequest::getInt('bid', 0, 'request');

		$booking_rooms = VikBooking::loadOrdersRoomsData($bid);

		if (!$this->vcm_exists || !$booking_rooms) {
			VBOHttpDocument::getInstance()->close(500, 'Could not obtain the listing information');
		}

		return [
			'listings' => array_column($booking_rooms, 'room_name'),
		];
	}

	public function render(?VBOMultitaskData $data = null)
	{
		// increase widget's instance counter
		static::$instance_counter++;

		// check whether the widget is being rendered via AJAX when adding it through the customizer
		$is_ajax = $this->isAjaxRendering();

		// generate a unique ID for the guest messages wrapper instance
		$wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
		$wrapper_id = 'vbo-widget-guest-messages-' . $wrapper_instance;

		// this widget will work only if VCM is available and updated, and if permissions are met
		$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
		if (!$this->vcm_exists || !$vbo_auth_bookings) {
			return;
		}

		// multitask data event identifier for clearing intervals
		$js_intvals_id    = '';
		$wrap_extra_class = '';
		$bid_convo        = 0;
		$unread_filter    = false;
		$sort_by_ai       = false;
		$contains_filter  = '';
		if ($data && $data->isModalRendering()) {
			// access Multitask data
			$js_intvals_id = $data->getModalJsIdentifier();

			// check if a specific conversation should be opened
			if ($data->get('id_message', 0)) {
				$bid_convo = $data->getBookingId();
			} else {
				$bid_convo = $this->options()->fetchBookingId();
			}

			// set an extra class
			$wrap_extra_class = ' vbo-w-guestmessages-wrapmodal';

			// check for unread messages filter
			$unread_filter = (bool) $this->options()->get('unread', '');

			// check for AI sort filter
			$sort_by_ai = (bool) $this->options()->get('ai_sort', '');

			// check if a specific filter was set for searching guest messages
			$contains_filter = (string) $this->options()->get('message_contains', '');

			// check custom limit per page only when in modal rendering
			if (($this->widgetSettings->limpage ?? 0) > 0) {
				// set custom limit from widget settings
				$this->messages_per_page = (int) $this->widgetSettings->limpage;
			}

			// when modal rendering, we expect to adopt the inbox-style by default unless in small screens
			if ($this->messages_per_page < $this->inbox_messages_per_page) {
				// in order to facilitate the infinite scroll, we use the minimum number of messages per page
				$this->messages_per_page = $this->inbox_messages_per_page;
			}

			// check for limit filter injected
			$limpage_filter = (int) $this->options()->get('limpage', 0);
			if ($limpage_filter > 0) {
				// set custom limit
				$this->messages_per_page = $limpage_filter;
			}
		}

		?>
		<div class="vbo-admin-widget-wrapper">
			<div class="vbo-admin-widget-head">
				<div class="vbo-admin-widget-head-inline">
					<h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
					<div class="vbo-admin-widget-head-commands">
						<div class="vbo-reportwidget-commands">
							<div class="vbo-reportwidget-commands-main">
								<div class="vbo-reportwidget-command-dates">
									<div id="<?php echo $wrapper_id; ?>-filters" class="vbo-reportwidget-period-name" style="<?php echo !$contains_filter && !$unread_filter ? 'display: none;' : ''; ?>"><?php VikBookingIcons::e('filter'); ?> <?php echo JText::translate('VBO_FILTERS_APPLIED'); ?></div>
								</div>
								<div id="<?php echo $wrapper_id; ?>-aipowered" class="vbo-admin-widget-head-ai-powered" style="display: none;">
									<span class="label label-info"><?php echo JText::translate('VBO_AI_LABEL_DEF'); ?></span>
								</div>
							</div>
							<div class="vbo-reportwidget-command-dots">
								<span class="vbo-widget-command-togglefilters vbo-widget-guest-messages-togglefilters" onclick="vboWidgetGuestMessagesOpenSettings('<?php echo $wrapper_id; ?>');">
									<?php VikBookingIcons::e('ellipsis-v'); ?>
								</span>
							</div>
						</div>
					</div>
				</div>
			</div>
			<div id="<?php echo $wrapper_id; ?>" class="vbo-dashboard-guests-latest<?php echo $wrap_extra_class; ?>" data-offset="0" data-length="<?php echo $this->messages_per_page; ?>" data-eventsid="<?php echo $js_intvals_id; ?>" data-latestdt="">
				<div class="vbo-dashboard-guest-messages-inner">
					<div class="vbo-w-guestmessages-list-container">
						<div class="vbo-dashboard-guest-messages-list">
						<?php
						for ($i = 0; $i < $this->messages_per_page; $i++) {
							?>
							<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">
								<div class="vbo-dashboard-guest-activity-avatar">
									<div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>
								</div>
								<div class="vbo-dashboard-guest-activity-content">
									<div class="vbo-dashboard-guest-activity-content-head">
										<div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>
									</div>
									<div class="vbo-dashboard-guest-activity-content-subhead">
										<div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>
									</div>
									<div class="vbo-dashboard-guest-activity-content-info-msg">
										<div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>
									</div>
								</div>
							</div>
							<?php
						}
						?>
						</div>
					</div>
					<div class="vbo-w-guestmessages-inboxstyle-chat"></div>
				</div>
				<div class="vbo-widget-guest-messages-filters-hidden" style="display: none;">
					<div class="vbo-widget-guest-messages-filters-wrap">
						<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">
							<div class="vbo-params-wrap">
								<div class="vbo-params-container">
									<div class="vbo-params-block">

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBCUSTOMERNOMINATIVE'); ?></div>
											<div class="vbo-param-setting">
												<input type="text" class="vbo-widget-guest-messages-guestname" value="" autocomplete="off" />
												<span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_FIRST_NAME_ACCURATE_HELP'); ?></span>
											</div>
										</div>

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBSENDEMAILCUSTCONT'); ?></div>
											<div class="vbo-param-setting">
												<input type="text" class="vbo-widget-guest-messages-messcontains" value="<?php echo JHtml::fetch('esc_attr', $contains_filter); ?>" autocomplete="off" />
												<span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_MESSAGE_CONTAINS_HELP'); ?></span>
											</div>
										</div>

										<div class="vbo-param-container vbo-toggle-small">
											<div class="vbo-param-label"><?php echo JText::translate('VBO_UNREAD_MESSAGES'); ?></div>
											<div class="vbo-param-setting">
												<?php echo $this->vbo_app->printYesNoButtons('unread', JText::translate('VBYES'), JText::translate('VBNO'), (int) $unread_filter, 1, 0, '', ['blue']); ?>
											</div>
										</div>

										<div class="vbo-param-container vbo-toggle-small">
											<div class="vbo-param-label"><?php echo JText::translate('VBO_AI_LABEL_DEF') . ' ' . JText::translate('VBO_SORT_BY_PRIORITY'); ?></div>
											<div class="vbo-param-setting">
												<?php echo $this->vbo_app->printYesNoButtons('ai_sort', JText::translate('VBYES'), JText::translate('VBNO'), (int) $sort_by_ai, 1, 0, '', ['gold']); ?>
											</div>
										</div>

										<div class="vbo-param-container vbo-toggle-small">
											<div class="vbo-param-label"><?php echo JText::translate('VBO_SENDER'); ?></div>
											<div class="vbo-param-setting">
												<div class="vbo-widget-guest-messages-multistate">
													<?php
													echo $this->vbo_app->multiStateToggleSwitchField(
														'sender' . $wrapper_instance,
														'',
														[
															'guest',
															'hotel',
														],
														[
															[
																'value' => JText::translate('VBO_GUEST'),
															],
															[
																'value' => 'Hotel',
															],
														],
														[
															[
																'label_class' => 'vik-multiswitch-text vik-multiswitch-radiobtn-guest',
																'input' 	  => [
																	'class' => 'vbo-widget-guest-messages-filter-sender',
																],
															],
															[
																'label_class' => 'vik-multiswitch-text vik-multiswitch-radiobtn-hotel',
																'input' 	  => [
																	'class' => 'vbo-widget-guest-messages-filter-sender',
																],
															],
														],
														[
															'class' => 'vik-multiswitch-noanimation',
														]
													);
													?>
												</div>
											</div>
										</div>

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDFROMRANGE'); ?></div>
											<div class="vbo-param-setting">
												<div class="vbo-field-calendar">
													<div class="input-append">
														<input type="text" class="vbo-widget-guest-messages-fromdt" value="" autocomplete="off" />
														<button type="button" class="btn btn-secondary vbo-widget-guest-messages-fromdt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
													</div>
												</div>
												<span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_FROM_DT_HELP'); ?></span>
											</div>
										</div>

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBNEWRESTRICTIONDTORANGE'); ?></div>
											<div class="vbo-param-setting">
												<div class="vbo-field-calendar">
													<div class="input-append">
														<input type="text" class="vbo-widget-guest-messages-todt" value="" autocomplete="off" />
														<button type="button" class="btn btn-secondary vbo-widget-guest-messages-todt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
													</div>
												</div>
												<span class="vbo-param-setting-comment"><?php echo JText::translate('VBO_TO_DT_HELP'); ?></span>
											</div>
										</div>

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBO_MESS_PER_PAGE'); ?></div>
											<div class="vbo-param-setting">
												<input type="number" class="vbo-widget-guest-messages-limpage" min="1" max="100" value="<?php echo $this->messages_per_page; ?>" />
											</div>
										</div>

									</div>
								</div>
							</div>
						</div>
					</div>
				</div>
			</div>
		</div>

		<?php
		if (static::$instance_counter === 0 || $is_ajax) {
			// HTML helper tag for URL routing and some JS functions should be loaded once per widget instance
			$admin_file_base = VBOPlatformDetection::isWordPress() ? 'admin.php' : 'index.php';
		?>

		<script type="text/javascript">

			/**
			 * Open the settings to search/filter the guest messages.
			 */
			function vboWidgetGuestMessagesOpenSettings(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// determine the messages rendering method
				var mess_rendering_meth = widget_instance.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inbox' : 'paging';

				// define unique modal event name to avoid conflicts
				var eventsid = widget_instance.attr('data-eventsid') || (Math.floor(Math.random() * 100000));
				var modal_dismiss_event = 'dismiss-modal-wguestmessages-search' + eventsid;

				// the hidden container of the search input fields
				var search_elements = widget_instance.find('.vbo-widget-guest-messages-filters-wrap');

				// build the button element to apply the search filters
				var apply_search_btn = document.createElement('button');
				apply_search_btn.setAttribute('type', 'button');
				apply_search_btn.classList.add('btn', 'btn-success');
				apply_search_btn.append(document.createTextNode(Joomla.JText._('VBODASHSEARCHKEYS')));
				apply_search_btn.addEventListener('click', () => {
					VBOCore.emitEvent(modal_dismiss_event, JSON.stringify({applyfilters: 1, wrapper: wrapper}));
				});

				// build the button element to clear the search filters
				var clear_search_btn = document.createElement('button');
				clear_search_btn.setAttribute('type', 'button');
				clear_search_btn.classList.add('btn');
				clear_search_btn.append(document.createTextNode(Joomla.JText._('VBOSIGNATURECLEAR')));
				clear_search_btn.addEventListener('click', () => {
					VBOCore.emitEvent(modal_dismiss_event, JSON.stringify({clearfilters: 1, wrapper: wrapper}));
				});

				var search_modal_body = VBOCore.displayModal({
					suffix: 'wguestmessages-search',
					extra_class: 'vbo-modal-rounded vbo-modal-dialog',
					title: '<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_W_GUESTMESSAGES_TITLE')); ?> - ' + Joomla.JText._('VBODASHSEARCHKEYS'),
					body_prepend: true,
					draggable: true,
					footer_left: clear_search_btn,
					footer_right: apply_search_btn,
					dismiss_event: modal_dismiss_event,
					onDismiss: (e) => {
						// always move back the search input fields
						search_elements.appendTo(widget_instance.find('.vbo-widget-guest-messages-filters-hidden'));

						if (!e || !e.detail) {
							// no event data received, maybe the modal was simply dismissed
							jQuery('#' + wrapper + '-filters').hide();
							return;
						}

						// parse data received within the dismiss event
						try {
							let commands = JSON.parse(e.detail);

							if (!commands['wrapper']) {
								return;
							}

							// determine if we are resetting the widget
							let is_resetting = false;

							if (commands['applyfilters']) {
								if (mess_rendering_meth == 'inbox') {
									// turn flag on
									is_resetting = true;
									// empty messages list
									widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
									// destroy any previous chat instance
									widget_instance.find('.vbo-w-guestmessages-inboxstyle-chat').html('');
									if (typeof VCMChat !== 'undefined') {
										VCMChat.getInstance().destroy();
									}
									// destroy inifite scroll events
									vboWidgetGuestMessagesDestroyInfiniteScroll(wrapper);
								}

								// display filters applied label
								jQuery('#' + wrapper + '-filters').show();
								// reset offset to 0
								widget_instance.attr('data-offset', 0);
								// show loading skeletons
								vboWidgetGuestMessagesSkeletons(commands['wrapper']);
								// reload guest messages for this widget's instance with filters set
								vboWidgetGuestMessagesLoad(commands['wrapper'], null, is_resetting);
							}

							if (commands['clearfilters']) {
								if (mess_rendering_meth == 'inbox') {
									// turn flag on
									is_resetting = true;
									// empty messages list
									widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
									// destroy any previous chat instance
									widget_instance.find('.vbo-w-guestmessages-inboxstyle-chat').html('');
									if (typeof VCMChat !== 'undefined') {
										VCMChat.getInstance().destroy();
									}
									// destroy inifite scroll events
									vboWidgetGuestMessagesDestroyInfiniteScroll(wrapper);
								}

								// clear filters
								widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="text"]').val('');
								widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="number"]').val('');
								widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="checkbox"]').prop('checked', false);
								widget_instance.find('.vbo-widget-guest-messages-filters-wrap').find('input[type="radio"]').prop('checked', false);
								// hide filters applied label
								jQuery('#' + wrapper + '-filters').hide();
								// reset offset to 0
								widget_instance.attr('data-offset', 0);
								// show loading skeletons
								vboWidgetGuestMessagesSkeletons(commands['wrapper']);
								// reload guest messages for this widget's instance with filters cleared
								vboWidgetGuestMessagesLoad(commands['wrapper'], null, is_resetting);
							}
						} catch(e) {
							// abort
							return;
						}
					},
				});

				// move the search filter fields to the modal body
				search_elements.appendTo(search_modal_body);
			}

			/**
			 * Open the chat for the clicked booking guest message
			 */
			function vboWidgetGuestMessagesOpenChat(id) {
				// clicked message
				var message_el = jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').first();

				if (message_el.hasClass('vbo-w-guestmessages-message-new')) {
					// get rid of the "new/unread" status
					message_el.removeClass('vbo-w-guestmessages-message-new');
					if (message_el.find('i.message-new').length) {
						message_el.find('i.message-new').remove();
					}
				}

				// set active message class only on the clicked message
				jQuery('.vbo-w-guestmessages-message').removeClass('vbo-inbox-active-message');
				message_el.addClass('vbo-inbox-active-message');

				// get widget's main block and chat container
				var message_block = message_el.closest('.vbo-dashboard-guests-latest');
				var chat_inline_container = message_block.find('.vbo-w-guestmessages-inboxstyle-chat');

				// determine the chat rendering method (modal or inline)
				var chat_rendering_meth = message_block.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inline' : 'modal';

				// destroy any previous chat instance
				if (typeof VCMChat !== 'undefined') {
					VCMChat.getInstance().destroy();
				}

				// always empty chat container
				chat_inline_container.html('');

				// modal events unique id to avoid conflicts
				var eventsid = message_block.attr('data-eventsid') || (Math.floor(Math.random() * 100000));

				// define unique modal event names to avoid conflicts
				var modal_dismiss_event = 'dismiss-modal-wguestmessages-chat' + eventsid;
				var modal_loading_event = 'loading-modal-wguestmessages-chat' + eventsid;

				// check for multiple instances of this widget, maybe because of clicked notifications while another instance was displayed
				if (jQuery('.vbo-w-guestmessages-message[data-idorder="' + id + '"]').length > 1) {
					// multiple instances found
					if (message_block.attr('data-eventsid')) {
						// dismiss the previous modal and keep using the same event id to ensure a de-registration of the modal events
						VBOCore.emitEvent(modal_dismiss_event);
					} else {
						// fallback to using a random events id to avoid conflicts
						eventsid = Math.floor(Math.random() * 100000);
					}
				}

				// build modal content
				var chat_head_title = jQuery('<span></span>');
				var chat_head_title_wrap = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info');
				var chat_head_title_top = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-customer');
				var chat_head_title_bot = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-info-booking');

				var chat_head_title_img = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestavatar');
				var chat_head_title_txt = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestname');
				var chat_head_title_bid = jQuery('<span></span>').addClass('vbo-modal-wguestmessages-chat-guestbid');
				var chat_head_open_bid = jQuery('<a></a>').addClass('badge badge-info').attr('href', 'JavaScript: void(0);').text(id).on('click', () => {
					VBOCore.handleDisplayWidgetNotification({
						widget_id: 'booking_details'
					}, {
						bid: id,
						modal_options: {
							suffix: 'widget_modal_inner_booking_details',
						}
					});
				});
				chat_head_title_bid.append(chat_head_open_bid);

				var guest_avatar = message_el.find('.vbo-dashboard-guest-activity-avatar img');
				var guest_name = message_el.find('.vbo-w-guestmessages-message-gtitle').find('span').first().text();
				chat_head_title_txt.text(guest_name);
				if (guest_avatar && guest_avatar.length) {
					chat_head_title_img.append(guest_avatar.clone());
					chat_head_title.append(chat_head_title_img);
				}
				chat_head_title_top.append(chat_head_title_txt);
				chat_head_title_top.append(chat_head_title_bid);

				chat_head_title_bot.append(message_el.find('.vbo-dashboard-guest-activity-content-info-icon').html());

				// register callback for no-reply-needed click
				var no_reply_needed_el = jQuery('<a></a>').addClass('label').attr('href', 'JavaScript: void(0);').text(Joomla.JText._('VBO_NO_REPLY_NEEDED'));
				if (message_el.attr('data-noreply-needed') == 1) {
					// this thread was marked as no-reply needed
					no_reply_needed_el.addClass('label-danger');
				}
				no_reply_needed_el.on('click', () => {
					if (confirm(Joomla.JText._('VBO_WANT_PROCEED'))) {
						var id_thread = message_el.attr('data-idthread');
						if (!id_thread || !id_thread.length) {
							return false;
						}

						// perform the request to toggle the thread as no-reply-needed
						var call_method = 'setNoReplyNeededThread';

						VBOCore.doAjax(
							"<?php echo $this->getExecWidgetAjaxUri(); ?>",
							{
								widget_id: "<?php echo $this->getIdentifier(); ?>",
								call: call_method,
								bid: id,
								id_thread: id_thread,
								status: message_el.attr('data-noreply-needed'),
								tmpl: "component"
							},
							(response) => {
								try {
									var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
									if (!obj_res.hasOwnProperty(call_method)) {
										console.error('Unexpected JSON response', obj_res);
										return false;
									}

									if (chat_rendering_meth == 'modal') {
										// emit the event to close (dismiss) the modal
										VBOCore.emitEvent(modal_dismiss_event);

										// reload the widget
										vboWidgetGuestMessagesLoad(message_block.attr('id'));
									} else {
										// update data attribute for toggle without destroying the chat
										if (message_el.attr('data-noreply-needed') == 1) {
											message_el.attr('data-noreply-needed', 0);
											no_reply_needed_el.removeClass('label-danger');
											// remove the reply button
											message_el.find('.message-unreplied').remove();
										} else {
											message_el.attr('data-noreply-needed', 1);
											no_reply_needed_el.addClass('label-danger');
										}
									}
								} catch(err) {
									console.error('could not parse JSON response', err, response);
								}
							},
							(error) => {
								console.error(error);
							}
						);
					}
				});

				// append element that will contain the listing details involved
				var listing_details_parent_el = jQuery('<span></span>').addClass('vbo-w-guestmessages-message-stayrooms');
				var listing_details_el = jQuery('<span></span>').addClass('vbo-w-guestmessages-message-listings').text('...');
				listing_details_parent_el.append(listing_details_el);
				chat_head_title_bot.append(listing_details_parent_el);

				// append no-reply-needed element
				chat_head_title_bot.append(no_reply_needed_el);

				// append additional elements
				chat_head_title_wrap.append(chat_head_title_top);
				chat_head_title_wrap.append(chat_head_title_bot);

				// finalize title
				chat_head_title.append(chat_head_title_wrap);

				if (chat_rendering_meth == 'modal') {
					// display modal
					var chat_modal_body = VBOCore.displayModal({
						suffix: 'wguestmessages-chat',
						extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
						title: chat_head_title,
						draggable: true,
						dismiss_event: modal_dismiss_event,
						onDismiss: () => {
							if (typeof VCMChat !== 'undefined') {
								VCMChat.getInstance().destroy();
							}
						},
						loading_event: modal_loading_event,
					});

					// start loading
					VBOCore.emitEvent(modal_loading_event);
				} else {
					// append head to chat container
					let chat_inline_head = jQuery('<div></div>').addClass('vbo-w-guestmessages-inboxstyle-chat-head');
					chat_inline_head.append(chat_head_title);
					chat_inline_container.append(chat_inline_head);

					// set inline loading body
					let chat_inline_loading = jQuery('<div></div>').addClass('vbo-w-guestmessages-inboxstyle-chat-loading');
					chat_inline_loading.html('<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>');
					chat_inline_container.append(chat_inline_loading);
				}

				// perform the request to render the chat in the apposite modal or inline wrapper
				var call_method = 'renderChat';

				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: call_method,
						bid: id,
						tmpl: "component"
					},
					(response) => {
						if (chat_rendering_meth == 'modal') {
							// stop loading
							VBOCore.emitEvent(modal_loading_event);
						} else {
							// unset inline loading body
							chat_inline_container.find('.vbo-w-guestmessages-inboxstyle-chat-loading').remove();
						}

						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty(call_method)) {
								console.error('Unexpected JSON response', obj_res);
								return false;
							}

							// append HTML code to render the chat to the apposite container
							if (chat_rendering_meth == 'modal') {
								chat_modal_body.html(obj_res[call_method]);
							} else {
								let chat_inline_body = jQuery('<div></div>').addClass('vbo-w-guestmessages-inboxstyle-chat-body');
								chat_inline_body.html(obj_res[call_method]);
								chat_inline_container.append(chat_inline_body);
							}

							// register scroll to bottom with a small delay
							setTimeout(() => {
								if (typeof VCMChat !== 'undefined') {
									VCMChat.getInstance().scrollToBottom();
								}
							}, 150);
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					(error) => {
						if (chat_rendering_meth == 'modal') {
							// stop loading
							VBOCore.emitEvent(modal_loading_event);
						} else {
							// empty inline chat container
							chat_inline_container.html('');
						}

						// log and display error
						console.error(error);
						alert(error.responseText);
					}
				);

				// perform the request to load the listing details involved
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: 'loadListingDetails',
						return: 1,
						bid: id,
						tmpl: "component"
					},
					(response) => {
						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty('loadListingDetails')) {
								console.error('Unexpected JSON response', obj_res);
								return false;
							}

							if (!Array.isArray(obj_res['loadListingDetails']['listings']) || !obj_res['loadListingDetails']['listings'].length) {
								// remove listing details element
								chat_head_title_bot.find('.vbo-w-guestmessages-message-stayrooms').remove();

								return false;
							}

							// set listing details
							chat_head_title_bot.find('.vbo-w-guestmessages-message-listings').html('<?php VikBookingIcons::e('home'); ?> ' + obj_res['loadListingDetails']['listings'].join(', '));
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					(error) => {
						// log error
						console.error(error);
					}
				);
			}

			/**
			 * Display the loading skeletons.
			 */
			function vboWidgetGuestMessagesSkeletons(wrapper, howmany) {
				let widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// number of skeletons to build
				let numSkeletons = <?php echo $this->messages_per_page; ?>;
				if (howmany && !isNaN(howmany) && howmany > 0) {
					numSkeletons = howmany;
				}

				// determine the messages rendering method
				var mess_rendering_meth = widget_instance.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inbox' : 'paging';

				if (mess_rendering_meth != 'inbox') {
					// empty the current messages list
					widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
				}

				for (let i = 0; i < numSkeletons; i++) {
					// build skeleton element
					let skeleton = '';
					skeleton += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
					skeleton += '	<div class="vbo-dashboard-guest-activity-avatar">';
					skeleton += '		<div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
					skeleton += '	</div>';
					skeleton += '	<div class="vbo-dashboard-guest-activity-content">';
					skeleton += '		<div class="vbo-dashboard-guest-activity-content-head">';
					skeleton += '			<div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
					skeleton += '		</div>';
					skeleton += '		<div class="vbo-dashboard-guest-activity-content-subhead">';
					skeleton += '			<div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
					skeleton += '		</div>';
					skeleton += '		<div class="vbo-dashboard-guest-activity-content-info-msg">';
					skeleton += '			<div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
					skeleton += '		</div>';
					skeleton += '	</div>';
					skeleton += '</div>';

					// append skeleton element to the messages list
					widget_instance.find('.vbo-dashboard-guest-messages-list').append(skeleton);
				}
			}

			/**
			 * Perform the request to load the latest messages.
			 */
			function vboWidgetGuestMessagesLoad(wrapper, bid_convo, resetting) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				var current_offset  = parseInt(widget_instance.attr('data-offset'));
				var length_per_page = parseInt(widget_instance.attr('data-length'));

				// determine the messages rendering method (paging or inbox, where "inbox" will likely never be on the first loading)
				var mess_rendering_meth = widget_instance.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inbox' : 'paging';

				// build search filter values
				var filters = {
					guest_name: widget_instance.find('input.vbo-widget-guest-messages-guestname').val(),
					message: widget_instance.find('input.vbo-widget-guest-messages-messcontains').val(),
					sender: widget_instance.find('input.vbo-widget-guest-messages-filter-sender[type="radio"]:checked').val(),
					fromdt: widget_instance.find('input.vbo-widget-guest-messages-fromdt').val(),
					todt: widget_instance.find('input.vbo-widget-guest-messages-todt').val(),
					unread: (widget_instance.find('input[name="unread"]').prop('checked') ? 1 : 0),
					ai_sort: (widget_instance.find('input[name="ai_sort"]').prop('checked') ? 1 : 0),
				};

				// custom messages limit per page
				let limpage = widget_instance.find('input.vbo-widget-guest-messages-limpage').val();
				if (!isNaN(limpage) && limpage > 0 && limpage != length_per_page) {
					// update value
					length_per_page = limpage;
					// set new limit
					widget_instance.attr('data-length', limpage);
				}

				// access the current messages list
				let messagesList = document
					.querySelector('#' + wrapper)
					.querySelector('.vbo-w-guestmessages-list-container');

				// the widget method to call
				var call_method = 'loadMessages';

				// make a request to load the messages
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: call_method,
						return: 1,
						bid_convo: bid_convo,
						filters: filters,
						offset: current_offset,
						length: length_per_page,
						wrapper: wrapper,
						tmpl: "component"
					},
					(response) => {
						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty(call_method)) {
								console.error('Unexpected JSON response', obj_res);
								return false;
							}

							if (mess_rendering_meth == 'inbox' && parseInt(current_offset) > 0) {
								// inbox style should get rid of non-needed elements and append the new messages
								widget_instance.find('.vbo-guestactivitywidget-commands').remove();
								widget_instance.find('.vbo-dashboard-guest-messages-list').find('.vbo-dashboard-guest-activity-skeleton').remove();
								widget_instance.find('.vbo-dashboard-guest-messages-list').find('.info[data-no-messages="1"]').remove();
								widget_instance.find('.vbo-dashboard-guest-messages-list').append(obj_res[call_method]['html']);
								if (parseInt(obj_res[call_method]['tot_messages']) > 0) {
									// move the class to the new last guest message
									widget_instance.find('.vbo-w-guestmessages-message.vbo-w-guestmessages-message-last').removeClass('vbo-w-guestmessages-message-last');
									widget_instance.find('.vbo-w-guestmessages-message').last().addClass('vbo-w-guestmessages-message-last');
								}
							} else {
								// replace HTML with new messages
								widget_instance.find('.vbo-dashboard-guest-messages-list').html(obj_res[call_method]['html']);
							}

							if (mess_rendering_meth == 'inbox') {
								if (!messagesList.pageLoading && parseInt(current_offset) > 0) {
									// inbox style with messages not loaded through infinite scroll
									// on a page after the first, may indicate that we are on a very
									// long screen (high height) that did not detect the scrolling of
									// the messages list to be needed, so we trigger the resize event
									VBOCore.emitEvent('vbo-resize-widget-modal<?php echo $js_intvals_id; ?>');
								} else if (resetting) {
									// applying or cancelling filters may require to re-configure the infinite scroll events
									VBOCore.emitEvent('vbo-resize-widget-modal<?php echo $js_intvals_id; ?>');
								}

								// always turn custom property off for the page loading
								messagesList.pageLoading = false;
							}

							// check if latest datetime is set
							if (obj_res[call_method]['latest_dt']) {
								widget_instance.attr('data-latestdt', obj_res[call_method]['latest_dt']);
							}

							// check results
							if (parseInt(obj_res[call_method]['tot_messages']) < 1) {
								// no results can indicate the offset is invalid or too high
								if (!isNaN(current_offset) && parseInt(current_offset) > 0) {
									if (mess_rendering_meth == 'inbox') {
										// inbox style should simply destroy the infinite scroll
										vboWidgetGuestMessagesDestroyInfiniteScroll(wrapper);
									} else {
										// reset offset to 0
										widget_instance.attr('data-offset', 0);
										// show loading skeletons
										vboWidgetGuestMessagesSkeletons(wrapper);
										// reload the first page
										vboWidgetGuestMessagesLoad(wrapper);
									}
								}
							} else {
								if (bid_convo) {
									// emit the event to read all notifications in the current context
									VBOCore.emitEvent('vbo-nc-read-notifications', {
										criteria: {
											group:   'guests',
											idorder: bid_convo,
										}
									});
								}
							}
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					(error) => {
						// display and log the error
						alert(error.responseText);
						console.error(error);

						if (mess_rendering_meth == 'inbox') {
							// turn custom property off for the page loading
							messagesList.pageLoading = false;
						}

						// remove the skeleton loading
						widget_instance.find('.vbo-dashboard-guest-messages-list').find('.vbo-dashboard-guest-activity-skeleton').remove();
					}
				);
			}

			/**
			 * Navigate between the various pages of the messages.
			 */
			function vboWidgetGuestMessagesNavigate(wrapper, direction) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// current offset
				var current_offset = parseInt(widget_instance.attr('data-offset'));

				// steps per type
				var steps = parseInt(widget_instance.attr('data-length'));

				// determine the messages rendering method (paging or inbox, where "inbox" will likely never be on the first loading)
				var mess_rendering_meth = widget_instance.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inbox' : 'paging';

				if (mess_rendering_meth != 'inbox') {
					// show loading skeletons only when in chat "modal" style
					vboWidgetGuestMessagesSkeletons(wrapper);
				}

				// check direction and update offset for next nav
				if (direction > 0) {
					// navigate forward
					widget_instance.attr('data-offset', (current_offset + steps));
				} else {
					// navigate backward
					var new_offset = current_offset - steps;
					new_offset = new_offset >= 0 ? new_offset : 0;
					widget_instance.attr('data-offset', new_offset);
				}
				
				// launch navigation
				vboWidgetGuestMessagesLoad(wrapper);
			}

			/**
			 * Watch periodically if there are new messages to be displayed (inline rendering only).
			 */
			function vboWidgetGuestMessagesWatch(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				var latest_dt = widget_instance.attr('data-latestdt');
				if (!latest_dt || !latest_dt.length) {
					return false;
				}

				// determine the messages rendering method (paging or inbox, where "inbox" will likely never be on the first loading)
				var mess_rendering_meth = widget_instance.hasClass('vbo-w-guestmessages-inboxstyle') ? 'inbox' : 'paging';

				// the widget method to call
				var call_method = 'watchMessages';

				// make a request to watch the messages
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: call_method,
						latest_dt: latest_dt,
						tmpl: "component"
					},
					(response) => {
						try {
							var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
							if (!obj_res.hasOwnProperty(call_method)) {
								console.error('Unexpected JSON response', obj_res);
								return false;
							}

							// response will contain the number of new messages
							if (isNaN(obj_res[call_method]) || parseInt(obj_res[call_method]) < 1) {
								// do nothing
								return;
							}

							if (mess_rendering_meth == 'inbox') {
								// do nothing when inbox-style, hence in a modal window, because this interval
								// will not run and new messages are taken from the apposite events dispatched
								// this statement should never verify
							} else {
								// new messages found, reset the offset and re-load the first page
								widget_instance.attr('data-offset', 0);
								// show loading skeletons
								vboWidgetGuestMessagesSkeletons(wrapper);
								// reload the first page
								vboWidgetGuestMessagesLoad(wrapper);
							}
						} catch(err) {
							console.error('could not parse JSON response', err, response);
						}
					},
					(error) => {
						// do nothing
						console.error(error);
					}
				);
			}

			/**
			 * Reloads chat messages when using inbox-style.
			 */
			function vboWidgetGuestMessagesReloadInbox(wrapper) {
				let widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// check if the admin was typing a reply message
				let is_typing = false;
				try {
					let reply_message = jQuery(VCMChat.getInstance().data.element.inputBox).find('textarea').val();
					if (reply_message) {
						// turn flag on
						is_typing = true;
					}
				} catch(e) {
					// do nothing
				}

				if (is_typing) {
					console.info('New messages not reloaded because the administrator is typing a reply to a guest message.');
					return false;
				}

				// destroy inifite scroll events
				vboWidgetGuestMessagesDestroyInfiniteScroll(wrapper);
				// empty messages list
				widget_instance.find('.vbo-dashboard-guest-messages-list').html('');
				// reset offset to 0
				widget_instance.attr('data-offset', 0);
				// show loading skeletons
				vboWidgetGuestMessagesSkeletons(wrapper);
				// reload guest messages for this widget's instance
				vboWidgetGuestMessagesLoad(wrapper, null, true);
				// restore infinite scroll loading, if needed
				VBOCore.emitEvent('vbo-resize-widget-modal<?php echo $js_intvals_id; ?>');
			}

			/**
			 * Destroys the infinite scroll loading when using inbox-style.
			 */
			function vboWidgetGuestMessagesDestroyInfiniteScroll(wrapper) {
				let messagesList = document
					.querySelector('#' + wrapper)
					.querySelector('.vbo-w-guestmessages-list-container');

				// un-register infinite scroll event handler
				messagesList
					.removeEventListener('scroll', vboWidgetGuestMessagesInfiniteScroll);
			}

			/**
			 * Setups the infinite scroll loading when using inbox-style.
			 */
			function vboWidgetGuestMessagesSetupInfiniteScroll(wrapper) {
				let messagesList = document
					.querySelector('#' + wrapper)
					.querySelector('.vbo-w-guestmessages-list-container');

				// get wrapper dimensions
				let listViewHeight = messagesList.offsetHeight;
				let listGlobHeight = messagesList.scrollHeight;

				if (listViewHeight >= listGlobHeight) {
					// no scrolling detected, show manual loading of the next page
					document
						.querySelector('#' + wrapper)
						.querySelector('.vbo-guestactivitywidget-commands')
						.style
						.display = 'block';

					return;
				}

				// inject custom property to identify the wrapper ID
				messagesList.wrapperId = wrapper;

				// register infinite scroll event handler
				messagesList
					.addEventListener('scroll', vboWidgetGuestMessagesInfiniteScroll);
			}

			/**
			 * Infinite scroll event handler when using inbox-style.
			 */
			function vboWidgetGuestMessagesInfiniteScroll(e) {
				// access the injected wrapper ID property
				let wrapper = e.currentTarget.wrapperId;

				if (!wrapper) {
					return;
				}

				// register throttling callback
				VBOCore.throttleTimer(() => {
					// access the current messages list
					let messagesList = document
						.querySelector('#' + wrapper)
						.querySelector('.vbo-w-guestmessages-list-container');

					// make sure the loading of a next page isn't running
					if (messagesList.pageLoading) {
						// abort
						return;
					}

					// get wrapper dimensions
					let listViewHeight = messagesList.offsetHeight;
					let listGlobHeight = messagesList.scrollHeight;
					let listScrollTop  = messagesList.scrollTop;

					if (!listScrollTop || listViewHeight >= listGlobHeight) {
						// no scrolling detected at all
						return;
					}

					// calculate missing distance to the end of the list
					let listEndDistance = listGlobHeight - (listViewHeight + listScrollTop);

					if (listEndDistance < <?php echo $this->px_distance_threshold; ?>) {
						// inject custom property to identify a next page is loading
						messagesList.pageLoading = true;

						// show that we are loading more messages
						vboWidgetGuestMessagesSkeletons(wrapper, 3);

						// load the next page of messages
						vboWidgetGuestMessagesNavigate(wrapper, 1);
					}
				}, 500);
			}

			/**
			 * Subscribe to the event emitted by VCM's chat handler when replying to a guest message.
			 */
			document.addEventListener('vcm-guest-message-replied', (e) => {
				if (!e || !e.detail) {
					return;
				}

				// pool of messaging elements to scan
				let elements  = [];

				// gather the supported event detail properties
				let idorder   = e.detail.hasOwnProperty('idorder') ? e.detail['idorder'] : null;
				let idthread  = e.detail.hasOwnProperty('idthread') ? e.detail['idthread'] : null;
				let idmessage = e.detail.hasOwnProperty('idmessage') ? e.detail['idmessage'] : null;

				// check if some eligible elements can be fetched
				if (idorder) {
					elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idorder="' + idorder + '"]');
				} else if (idthread) {
					elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idthread="' + idthread + '"]');
				} else if (idmessage) {
					elements = document.querySelectorAll('.vbo-w-guestmessages-message[data-idmessage="' + idmessage + '"]');
				}

				// scan all the elements from which the "reply" label should be removed, if any
				elements.forEach((element) => {
					let unreplied = element.querySelector('.message-unreplied');
					if (unreplied) {
						// remove node stating that the guest message needs a reply
						unreplied.remove();
					}
				});
			});

		</script>
		<?php
		}
		?>

		<script type="text/javascript">

			jQuery(function() {

				// when document is ready, load latest messages for this widget's instance
				vboWidgetGuestMessagesLoad('<?php echo $wrapper_id; ?>', '<?php echo $bid_convo; ?>');

				// make sure we've got no other chat instances on the same page (editorder)
				if (jQuery('#vbmessagingdiv').length) {
					if (typeof VCMChat !== 'undefined') {
						VCMChat.getInstance().destroy();
					}
					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>');
				}

				// render datepicker calendar for dates navigation
				jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-guest-messages-fromdt, .vbo-widget-guest-messages-todt').datepicker({
					maxDate: "+1d",
					yearRange: "<?php echo (date('Y') - 3); ?>:<?php echo date('Y'); ?>",
					changeMonth: true,
					changeYear: true,
					dateFormat: "<?php echo $this->getDateFormat('jui'); ?>",
					onSelect: function(selectedDate) {
						if (!selectedDate) {
							return;
						}
						if (jQuery(this).hasClass('vbo-widget-guest-messages-fromdt')) {
							let nowstart = jQuery(this).datepicker('getDate');
							let nowstartdate = new Date(nowstart.getTime());
							jQuery('.vbo-widget-guest-messages-todt').datepicker('option', {minDate: nowstartdate});
						}
					}
				});

				// triggering for datepicker calendar icon
				jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-guest-messages-fromdt-trigger, .vbo-widget-guest-messages-todt-trigger').click(function() {
					var jdp = jQuery(this).parent().find('input.hasDatepicker');
					if (jdp.length) {
						jdp.focus();
					}
				});

				// register callback function for the widget "resize" event (modal only)
				const resize_fn = (e) => {
					let inboxStyleThreshold = 1000;

					let modalContent = e?.detail?.content ? (e?.detail?.content[0] || e?.detail?.content || null) : null;
					if (!modalContent || !modalContent.matches('#<?php echo $wrapper_id; ?>')) {
						modalContent = document.querySelector('#<?php echo $wrapper_id; ?>');
					}

					// always attempt to destroy infinite scroll
					vboWidgetGuestMessagesDestroyInfiniteScroll('<?php echo $wrapper_id; ?>');

					let overlayContent = modalContent.closest('.vbo-modal-overlay-content');
					let outerBody = modalContent.closest('.vbo-modal-overlay-content-body');
					if (!overlayContent || !outerBody) {
						// widget may be minimized on the admin-dock
						return;
					}

					// get messages list container
					let listContainer = modalContent.querySelector('.vbo-w-guestmessages-list-container');

					if (modalContent.offsetWidth > inboxStyleThreshold) {
						// enable inbox-style
						modalContent.classList.add('vbo-w-guestmessages-inboxstyle');
						overlayContent.classList.add('vbo-modal-body-no-scroll');
						// calculate the messages list container height to allow y-scrolling
						listContainer.style.height = (outerBody.offsetHeight - (listContainer.offsetTop - outerBody.offsetTop) - 4) + 'px';
						// set head-no-border class
						outerBody.classList.add('vbo-modal-head-no-border');
						// setup infinite scroll
						vboWidgetGuestMessagesSetupInfiniteScroll('<?php echo $wrapper_id; ?>');
					} else {
						// disable inbox-style
						modalContent.classList.remove('vbo-w-guestmessages-inboxstyle');
						overlayContent.classList.remove('vbo-modal-body-no-scroll');
						// reset the messages list container height property
						listContainer.style.height = 'initial';
						// reset head-no-border class
						outerBody.classList.remove('vbo-modal-head-no-border');
					}
				};

				// register callback for the new guest messages event (modal only)
				const new_messages_fn = (e) => {
					if (!e || !e.detail || !e.detail?.messages) {
						return;
					}

					let modalContent = document.querySelector('#<?php echo $wrapper_id; ?>');
					if (!modalContent || !Array.isArray(e.detail.messages) || !e.detail.messages.length) {
						return;
					}

					let modalWrap = modalContent.closest('.vbo-modal-widget_modal-wrap[data-dock-minimized]');
					if (!modalWrap) {
						return;
					}

					// check if the widget is minimized
					if (modalWrap.getAttribute('data-dock-minimized') == 1) {
						// register self-destructive event for reloading the inbox upon widget restoring from admin-dock
						document.addEventListener('vbo-admin-dock-restore-<?php echo $this->getIdentifier(); ?>', function vboWidgetGuestMessagesRestoreNReload(e) {
							// make sure the same event won't propagate again
							e.target.removeEventListener(e.type, vboWidgetGuestMessagesRestoreNReload);

							// reload chat messages
							vboWidgetGuestMessagesReloadInbox('<?php echo $wrapper_id; ?>');
						});
					} else {
						// reload chat messages
						vboWidgetGuestMessagesReloadInbox('<?php echo $wrapper_id; ?>');
					}
				};

			<?php
			if ($js_intvals_id) {
				// widget can be dismissed or "resized" through the modal
				?>
				document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_intvals_id; ?>', (e) => {
					if (typeof watch_intv !== 'undefined') {
						// clear interval for notifications
						clearInterval(watch_intv);
					}

					if (jQuery('#vbmessagingdiv').length) {
						// reload the page for the previously removed chat in the editorder page
						location.reload();
					}

					// get rid of widget resizing events
					document.removeEventListener('vbo-resize-widget-modal<?php echo $js_intvals_id; ?>', resize_fn);
					document.removeEventListener('vbo-admin-dock-restore-<?php echo $this->getIdentifier(); ?>', resize_fn);
					window.removeEventListener('resize', resize_fn);

					// get rid of new guest messages event
					document.removeEventListener('vbo-new-guest-messages', new_messages_fn);

					// if there was an inline chat, destroy it
					if (typeof VCMChat !== 'undefined') {
						VCMChat.getInstance().destroy();
					}
				});

				// register widget resizing events
				document.addEventListener('vbo-resize-widget-modal<?php echo $js_intvals_id; ?>', resize_fn);
				document.addEventListener('vbo-admin-dock-restore-<?php echo $this->getIdentifier(); ?>', resize_fn);
				window.addEventListener('resize', resize_fn);

				/**
				 * Subscribe to the event emitted when new guest messages are received.
				 */
				document.addEventListener('vbo-new-guest-messages', new_messages_fn);
				<?php
			} else {
				// set interval for loading new messages automatically (only when not modal rendering)
				?>
				const watch_intv = setInterval(function() {
					vboWidgetGuestMessagesWatch('<?php echo $wrapper_id; ?>');
				}, 60000);
				<?php
			}
			?>

			});

		</script>

		<?php
	}
}