File "bulk_messaging.php"

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

<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - e4j - Extensionsforjoomla.com
 * @copyright   Copyright (C) 2018 e4j - Extensionsforjoomla.com. 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 "bulk messaging".
 * 
 * @since 	1.16.7 (J) - 1.6.7 (WP)
 */
class VikBookingAdminWidgetBulkMessaging extends VikBookingAdminWidget
{
	/**
	 * The instance counter of this widget. Since we do not load individual parameters
	 * for each widget's instance, we use a static counter to determine its settings.
	 *
	 * @var 	int
	 */
	protected static $instance_counter = -1;

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

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

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

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

	/**
	 * Custom method for this widget only to load the reservations.
	 * 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.
	 * In this case we return an array because this method requires "return":1.
	 */
	public function loadBookings()
	{
		// get today's date
		$today_ymd = date('Y-m-d');

		$wrapper = VikRequest::getString('wrapper', '', 'request');
		$type 	 = VikRequest::getString('type', 'stayover', 'request');
		$from_dt = VikRequest::getString('from_dt', $today_ymd, 'request');
		$to_dt 	 = VikRequest::getString('to_dt', '', 'request') ?: $from_dt;

		if (empty($from_dt)) {
			VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PLEASE_FILL_FIELDS'));
		}

		// get date timestamps
		$from_ts = VikBooking::getDateTimestamp($from_dt, 0, 0);
		$to_ts = VikBooking::getDateTimestamp($to_dt, 23, 59, 59);
		$from_ts_end = VikBooking::getDateTimestamp($from_dt, 23, 59, 59);

		// query the db
		$dbo = JFactory::getDbo();

		$q = $dbo->getQuery(true)
			->select($dbo->qn('o') . '.*')
			->select($dbo->qn('co.idcustomer'))
			->select('CONCAT_WS(" ", ' . $dbo->qn('c.first_name') . ', ' . $dbo->qn('c.last_name') . ') AS ' . $dbo->qn('customer_fullname'))
			->select($dbo->qn('c.country', 'customer_country'))
			->select($dbo->qn('c.pic'))
			->from($dbo->qn('#__vikbooking_orders', 'o'))
			->leftJoin($dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $dbo->qn('co.idorder') . ' = ' . $dbo->qn('o.id'))
			->leftJoin($dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('co.idcustomer'))
			->where($dbo->qn('o.closure') . ' = 0');

		if ($type == 'arrival') {
			$q->where($dbo->qn('o.checkin') . ' >= ' . $from_ts);
			$q->where($dbo->qn('o.checkin') . ' <= ' . $to_ts);
		} elseif ($type == 'departure') {
			$q->where($dbo->qn('o.checkout') . ' >= ' . $from_ts);
			$q->where($dbo->qn('o.checkout') . ' <= ' . $to_ts);
		} elseif ($type == 'bookdate') {
			$q->where($dbo->qn('o.ts') . ' >= ' . $from_ts);
			$q->where($dbo->qn('o.ts') . ' <= ' . $to_ts);
		} else {
			// stayover
			$q->where($dbo->qn('o.checkin') . ' < ' . $from_ts_end);
			$q->where($dbo->qn('o.checkout') . ' > ' . $to_ts);
		}

		$dbo->setQuery($q);
		$bookings = $dbo->loadAssocList();

		// total checkboxes checked
		$tot_checked = 0;

		// first booking ID "checked"
		$first_checked_bid = 0;
		$widget_id = $this->widgetId;

		// start output buffering
		ob_start();

		if (!$bookings) {
			?>
			<p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
			<?php
		} else {
			// display all bookings of this day
			foreach ($bookings as $ind => $booking) {
				// get channel logo and other details
				$ch_logo_obj  = VikBooking::getVcmChannelsLogo($booking['channel'], true);
				$channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
				$nights_lbl = $booking['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY');
				$rooms_lbl = !empty($booking['roomsnum']) && $booking['roomsnum'] > 1 ? ', ' . $booking['roomsnum'] . ' ' . JText::translate('VBPVIEWORDERSTHREE') : '';

				// compose customer name
				$customer_name = !empty($booking['customer_fullname']) ? $booking['customer_fullname'] : '';
				if ($booking['closure'] > 0 || !strcasecmp($booking['custdata'], JText::translate('VBDBTEXTROOMCLOSED'))) {
					$customer_name = '<span class="vbordersroomclosed"><i class="' . VikBookingIcons::i('ban') . '"></i> ' . JText::translate('VBDBTEXTROOMCLOSED') . '</span>';
				}
				if (empty($customer_name)) {
					$customer_name = VikBooking::getFirstCustDataField($booking['custdata']);
				}

				// customer country flag
				$customer_country = '';
				$customer_cflag   = '';
				if (!empty($booking['customer_country'])) {
					$customer_country = $booking['customer_country'];
				} elseif (!empty($booking['country'])) {
					$customer_country = $booking['country'];
				}
				if ($customer_country && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'countries' . DIRECTORY_SEPARATOR . $customer_country . '.png')) {
					$customer_cflag = '<img src="'.VBO_ADMIN_URI.'resources/countries/' . $customer_country . '.png'.'" title="' . htmlspecialchars($customer_country) . '" class="vbo-country-flag vbo-country-flag-left"/>';
				}

				// check for any previous event triggered by this widget for the current reservation
				$last_notified = null;
				$history_obj   = VikBooking::getBookingHistoryInstance($booking['id']);
				$prev_ev_data  = $history_obj->getEventsWithData('CE', function($data) use ($widget_id) {
					return (is_object($data) && !empty($data->widget) && $data->widget == $widget_id);
				}, $onlydata = false);
				if (is_array($prev_ev_data) && $prev_ev_data) {
					$last_notified = $prev_ev_data[0]['dt'];
				}

				// default checked status
				$booking_is_checked = ($booking['status'] == 'confirmed' && !$last_notified);
				if ($booking_is_checked) {
					$tot_checked++;
					if (!$first_checked_bid) {
						$first_checked_bid = $booking['id'];
					}
				}

				?>
				<div class="vbo-dashboard-guest-activity vbo-widget-bulkmess-reservation" data-type="<?php echo $booking['status']; ?>" data-resid="<?php echo $booking['id']; ?>">
					<div class="vbo-widget-bulkmess-ckbox">
						<input type="checkbox" value="<?php echo $booking['id']; ?>" <?php echo $booking_is_checked ? 'checked ' : ''; ?>/>
					</div>
					<div class="vbo-dashboard-guest-activity-avatar">
					<?php
					if (!empty($channel_logo)) {
						// channel logo has got the highest priority
						?>
						<img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo $channel_logo; ?>" />
						<?php
					} elseif (!empty($booking['pic'])) {
						// customer profile picture
						?>
						<img class="vbo-dashboard-guest-activity-avatar-profile" src="<?php echo strpos($booking['pic'], 'http') === 0 ? $booking['pic'] : VBO_SITE_URI . 'resources/uploads/' . $booking['pic']; ?>" />
						<?php
					} else {
						// we use an icon as fallback
						VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon');
					}
					?>
					</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><?php echo $customer_name . $customer_cflag; ?></h4>
								<div class="vbo-dashboard-guest-activity-content-info-icon">
								<?php
								if ($booking['status'] == 'cancelled') {
									?>
									<span class="badge badge-danger"><?php echo JText::translate('VBCANCELLED'); ?></span>
									<?php
								} elseif ($booking['status'] == 'standby') {
									?>
									<span class="badge badge-warning"><?php echo JText::translate('VBSTANDBY'); ?></span>
									<?php
								}
								?>
									<span><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $booking['checkin']); ?> - <?php echo $booking['days'] . ' ' . $nights_lbl . $rooms_lbl; ?></span>
								<?php
								if ($last_notified) {
									?>
									<span class="vbo-widget-bulkmess-notified vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBOBOOKHISTORYTCE')); ?>"><?php VikBookingIcons::e('envelope'); ?> <?php echo JHtml::fetch('date', $hist['dt'], 'Y-m-d H:i:s'); ?></span>
									<?php
								}
								?>
								</div>
							</div>
							<div class="vbo-dashboard-guest-activity-content-info-date">
								<span class="vbo-widget-bulkmess-openbook" onclick="vboWidgetBulkMessOpenBooking('<?php echo $booking['id']; ?>');">
									<span class="label label-info"><?php VikBookingIcons::e('eye'); ?> <?php echo $booking['id']; ?></span>
								</span>
								<span><?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i', $booking['ts']); ?></span>
							</div>
						</div>
					</div>
				</div>
				<?php
			}
		}

		?>
		<script type="text/javascript">

			jQuery(function() {

				/**
				 * Register the first checked booking ID to let the mail preview work.
				 */
				if (typeof window['vbo_current_bid'] === 'undefined') {
					window['vbo_current_bid'] = '<?php echo $first_checked_bid; ?>';
				}

				/**
				 * Prepare the elements to toggle the checkbox on click.
				 */
				jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bulkmess-reservation').on('click', function(e) {
					if (jQuery(e.target).hasClass('.vbo-widget-bulkmess-openbook') || jQuery(e.target).closest('.vbo-widget-bulkmess-openbook').length) {
						return;
					}

					if (!jQuery(e.target).is('input[type="checkbox"]')) {
						var ckbox = jQuery(this).find('input[type="checkbox"]');
						if (ckbox.prop('checked')) {
							ckbox.prop('checked', false);
						} else {
							ckbox.prop('checked', true);
						}
					}

					// update total checked count
					var checked_stats = vboWidgetBulkMessCountChecked('<?php echo $wrapper; ?>');

					// update status on bookings step
					jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-step-status').text(checked_stats['tot_checked'] + ' / ' + checked_stats['tot_bookings']);
				});

			});

		</script>
		<?php

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

		// return an associative array of values
		return [
			'html' 		   => $html_content,
			'tot_bookings' => count($bookings),
			'tot_checked'  => $tot_checked,
		];
	}

	/**
	 * Custom method for this widget only to send the communication message.
	 * 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.
	 * In this case we return an array because this method requires "return":1.
	 */
	public function sendOneMessage()
	{
		$wrapper = VikRequest::getString('wrapper', '', 'request');
		$bid 	 = VikRequest::getInt('bid', 0, 'request');
		$index 	 = VikRequest::getInt('index', 0, 'request');
		$message = VikRequest::getString('message', '', 'request', VIKREQUEST_ALLOWHTML);
		$subject = VikRequest::getString('subject', '', 'request');

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

		if (!$booking) {
			VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
		}

		if (empty($message)) {
			VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PLEASE_FILL_FIELDS'));
		}

		// inject the customer information
		$customer = VikBooking::getCPinInstance()->getCustomerFromBooking($booking['id']);
		$booking['customer'] = $customer;

		if ($index === 0) {
			// update widget's settings with last message and subject
			$this->widgetSettings->last_subject = $subject;
			$this->widgetSettings->last_message = $message;
			$this->updateSettings(json_encode($this->widgetSettings));
		}

		// determine the message dispatching method for this booking
		$sending_method = 'eMail';

		// check if support for OTA messaging is available
		$ota_messaging_supported = class_exists('VCMChatMessaging');

		if ($ota_messaging_supported && VCMChatMessaging::getInstance($booking)->supportsOtaMessaging($mandatory = true)) {
			// set flag to identify an OTA messaging notification over the regular email
			$sending_method = 'Message';
		} elseif (preg_match("/^no-email-[^@]+@[a-z0-9\.]+\.com$/i", $booking['custmail'])) {
			// false email address, typical of Airbnb, ignore reservation record
			// this statement should be entered only if VCM is outdated, or the Guest Messaging API will be used
			VBOHttpDocument::getInstance()->close(500, 'The booking ID ' . $booking['id'] . ' does not have a valid email address that can be notified.');
		}

		if ($sending_method === 'eMail' && empty($booking['custmail'])) {
			VBOHttpDocument::getInstance()->close(500, 'The booking ID ' . $booking['id'] . ' is missing the guest email address.');
		}

		// language translation
		$lang = JFactory::getLanguage();
		$vbo_tn = VikBooking::getTranslator();
		$vbo_tn::$force_tolang = null;
		$website_def_lang = $vbo_tn->getDefaultLang();
		if (!empty($booking['lang'])) {
			if ($lang->getTag() != $booking['lang']) {
				if (VBOPlatformDetection::isWordPress()) {
					// wp
					$lang->load('com_vikbooking', VIKBOOKING_SITE_LANG, $booking['lang'], true);
					$lang->load('com_vikbooking', VIKBOOKING_ADMIN_LANG, $booking['lang'], true);
				} else {
					// J
					$lang->load('com_vikbooking', JPATH_SITE, $booking['lang'], true);
					$lang->load('com_vikbooking', JPATH_ADMINISTRATOR, $booking['lang'], true);
					$lang->load('joomla', JPATH_SITE, $booking['lang'], true);
					$lang->load('joomla', JPATH_ADMINISTRATOR, $booking['lang'], true);
				}
			}
			if ($website_def_lang != $booking['lang']) {
				// force the translation to start because contents should be translated
				$vbo_tn::$force_tolang = $booking['lang'];
			}
		}

		// dispatch the message to the guest reservation
		if ($sending_method === 'eMail') {
			// regular email notification
			$message = $this->notifyThroughEmail($booking, $message, $subject);
		} else {
			// notification through guest messaging API
			$message = $this->notifyThroughMessage($booking, $message);
		}

		// update history for this booking with the information about the communication sent
		VikBooking::getBookingHistoryInstance($booking['id'])
			->setExtraData(['widget' => $this->widgetId])
			->store('CE', $message);

		// return an associative array of values
		return [
			'result' => 1,
		];
	}

	/**
	 * Preload the necessary CSS/JS assets.
	 * 
	 * @return 	void
	 */
	public function preload()
	{
		// load assets
		$this->vbo_app->loadDatePicker();

		if (VBOPlatformDetection::isJoomla()) {
			// load assets
			$this->vbo_app->loadVisualEditorAssets();
		} else {
			// load lang defs
			$this->vbo_app->loadVisualEditorDefinitions();
		}

		// JS lang defs
		JText::script('VBO_PLEASE_SELECT');
		JText::script('VBPVIEWORDERSPEOPLE');
		JText::script('VBO_WANT_PROCEED');
		JText::script('VBO_CONT_WRAPPER');
		JText::script('VBO_CONT_WRAPPER_HELP');
	}

	/**
	 * Main method to invoke the widget. Contents will be loaded
	 * through AJAX requests, not via PHP when the page loads.
	 * 
	 * @param 	?VBOMultitaskData 	$data
	 * 
	 * @return 	void
	 */
	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 sticky notes wrapper instance
		$wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
		$wrapper_id = 'vbo-widget-bulkmess-' . $wrapper_instance;

		// get permissions
		$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
		if (!$vbo_auth_bookings) {
			// display nothing
			return;
		}

		// date format
		$dtpicker_df = $this->getDateFormat('jui');

		// build the default message subject
		$def_subject = !empty($this->widgetSettings->last_subject) ? $this->widgetSettings->last_subject : JText::sprintf('VBOMAILSUBJECT', VikBooking::getFrontTitle());

		?>
		<div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>">
			<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>
			</div>
			<div class="vbo-widget-bulkmess-wrap">
				<div class="vbo-widget-bulkmess-steps">

					<div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-search">
						<div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'search');">
							<h4><?php VikBookingIcons::e('calendar'); ?> <?php echo JText::translate('VBODASHSEARCHKEYS'); ?></h4>
							<span class="vbo-widget-bulkmess-step-status"></span>
						</div>
						<div class="vbo-widget-bulkmess-step-content">
							<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-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-bulkmess-fromdt" value="" autocomplete="off" />
														<button type="button" class="btn btn-secondary vbo-widget-bulkmess-fromdt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
													</div>
												</div>
											</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-bulkmess-todt" value="" autocomplete="off" />
														<button type="button" class="btn btn-secondary vbo-widget-bulkmess-todt-trigger"><?php VikBookingIcons::e('calendar'); ?></button>
													</div>
												</div>
											</div>
										</div>

										<div class="vbo-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBPSHOWSEASONSTHREE'); ?></div>
											<div class="vbo-param-setting">
												<select class="vbo-widget-bulkmess-type">
													<option value="stayover"><?php echo JText::translate('VBOTYPESTAYOVER'); ?></option>
													<option value="arrival"><?php echo JText::translate('VBOTYPEARRIVAL'); ?></option>
													<option value="departure"><?php echo JText::translate('VBOTYPEDEPARTURE'); ?></option>
													<option value="bookdate"><?php echo JText::translate('VBPEDITBUSYTWO'); ?></option>
												</select>
											</div>
										</div>

										<div class="vbo-param-container vbo-param-confirm-btn">
											<div class="vbo-param-label"></div>
											<div class="vbo-param-setting">
												<button type="button" class="btn btn-primary vbo-btn-wide vbo-widget-bulkmess-loadbtn" onclick="vboWidgetBulkMessLoadBookings('<?php echo $wrapper_id; ?>');"><span><?php echo JText::translate('VBJQCALNEXT'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
											</div>
										</div>

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

					<div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-bookings">
						<div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'bookings');">
							<h4><?php VikBookingIcons::e('users'); ?> <?php echo JText::translate('VBMENUCUSTOMERS'); ?></h4>
							<span class="vbo-widget-bulkmess-step-status"></span>
						</div>
						<div class="vbo-widget-bulkmess-step-content">
							<div class="vbo-widget-bulkmess-bookings-actions">
								<button type="button" class="btn btn-small" onclick="vboWidgetBulkMessSelectAll('<?php echo $wrapper_id; ?>');"><?php echo JText::translate('VBINVSELECTALL'); ?></button>
							</div>
							<div class="vbo-dashboard-guests-latest vbo-widget-bulkmess-bookings-list">
								<p class="info"><?php echo JText::translate('VBNOORDERSFOUND'); ?></p>
							</div>
							<div class="vbo-widget-bulkmess-gonext" style="display: none;">
								<button type="button" class="btn btn-primary vbo-btn-wide" onclick="vboWidgetBulkMessGoNext('<?php echo $wrapper_id; ?>', 'message');"><span><?php echo JText::translate('VBJQCALNEXT'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
							</div>
						</div>
					</div>

					<div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-message">
						<div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'message');">
							<h4><?php VikBookingIcons::e('envelope'); ?> <?php echo JText::translate('VBSENDEMAILCUSTCONT'); ?></h4>
							<span class="vbo-widget-bulkmess-step-status"></span>
						</div>
						<div class="vbo-widget-bulkmess-step-content">
							<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-param-container">
											<div class="vbo-param-label"><?php echo JText::translate('VBSENDEMAILCUSTSUBJ'); ?></div>
											<div class="vbo-param-setting">
												<input type="text" class="vbo-widget-bulkmess-subject" value="<?php echo JHtml::fetch('esc_attr', $def_subject); ?>" autocomplete="off" />
											</div>
										</div>

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

							<div class="vbo-widget-bulkmess-message">
								<?php echo $this->renderEditor(); ?>
							</div>
							<div class="vbo-widget-bulkmess-gonext">
								<button type="button" class="btn btn-primary vbo-btn-wide" onclick="vboWidgetBulkMessGoNext('<?php echo $wrapper_id; ?>', 'send');"><span><?php echo JText::translate('VBO_SEND'); ?></span> <?php VikBookingIcons::e('chevron-right'); ?></button>
							</div>
						</div>
					</div>

					<div class="vbo-widget-bulkmess-step vbo-widget-bulkmess-step-hidden vbo-widget-bulkmess-step-send">
						<div class="vbo-widget-bulkmess-step-title" onclick="vboWidgetBulkMessToggleStep('<?php echo $wrapper_id; ?>', 'send');">
							<h4><?php VikBookingIcons::e('rocket'); ?> <?php echo JText::translate('VBO_SEND_MESSAGES'); ?></h4>
							<span class="vbo-widget-bulkmess-step-status"></span>
						</div>
						<div class="vbo-widget-bulkmess-step-content">
							<div class="vbo-widget-bulkmess-progress">
								<div class="vbo-widget-bulkmess-progress-inner">
									<progress value="0" max="100">0 / 0</progress>
								</div>
							</div>
						</div>
					</div>

				</div>
			</div>
		</div>
		<?php

		if (static::$instance_counter === 0 || $is_ajax) {
			/**
			 * Print the JS code only once for all instances of this widget.
			 * The real rendering is made through AJAX, not when the page loads.
			 */
			?>

		<script type="text/javascript">

			var vbo_widget_bulkmess_icn_load = '<?php echo VikBookingIcons::i('spinner', 'fa-spin fa-fw'); ?>';
			var vbo_widget_bulkmess_icn_next = '<?php echo VikBookingIcons::i('chevron-right'); ?>';

			/**
			 * Perform the request to load the bookings.
			 */
			function vboWidgetBulkMessLoadBookings(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// get vars for making the request
				var from_dt = widget_instance.find('.vbo-widget-bulkmess-fromdt').val();
				var to_dt   = widget_instance.find('.vbo-widget-bulkmess-todt').val();
				var type    = widget_instance.find('.vbo-widget-bulkmess-type').val();
				var typelbl = widget_instance.find('.vbo-widget-bulkmess-type').find('option:selected').text();
				typelbl = typelbl ? typelbl : 'Stayover';

				// set loading icon
				widget_instance.find('.vbo-widget-bulkmess-step-search')
					.find('.vbo-widget-bulkmess-loadbtn')
					.prop('disabled', true)
					.find('i')
					.attr('class', vbo_widget_bulkmess_icn_load);

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

				// make a request to load the bookings
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: 	   call_method,
						return:    1,
						from_dt:   from_dt,
						to_dt: 	   to_dt,
						type: 	   type,
						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;
							}

							// replace HTML with new bookings calendar
							widget_instance.find('.vbo-widget-bulkmess-bookings-list').html(obj_res[call_method]['html']);

							// update search step status
							widget_instance.find('.vbo-widget-bulkmess-step-search').find('.vbo-widget-bulkmess-step-status').text(typelbl);

							// check if we got results
							if (obj_res[call_method]['tot_bookings'] > 0) {
								// hide all steps except the bookings list
								widget_instance.find('.vbo-widget-bulkmess-step').not('.vbo-widget-bulkmess-step-bookings').addClass('vbo-widget-bulkmess-step-hidden');
								// show the button to go next
								widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-gonext').show();
							} else {
								// hide the button to go next
								widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-gonext').hide();
							}

							// display the bookings list step
							widget_instance.find('.vbo-widget-bulkmess-step-bookings').removeClass('vbo-widget-bulkmess-step-hidden');

							// update bookings step status
							widget_instance.find('.vbo-widget-bulkmess-step-bookings').find('.vbo-widget-bulkmess-step-status').text(obj_res[call_method]['tot_checked'] + ' / ' + obj_res[call_method]['tot_bookings']);

							// restore next icon
							widget_instance.find('.vbo-widget-bulkmess-step-search')
								.find('.vbo-widget-bulkmess-loadbtn')
								.prop('disabled', false)
								.find('i')
								.attr('class', vbo_widget_bulkmess_icn_next);
						} catch(err) {
							console.error('could not parse JSON response', err, response);
							widget_instance.find('.vbo-widget-bulkmess-bookings-list').html('');
						}
					},
					(error) => {
						widget_instance.find('.vbo-widget-bulkmess-bookings-list').html('');
						console.error(error);
						alert(error.responseText);
					}
				);
			}

			/**
			 * Renders the booking details widget.
			 */
			function vboWidgetBulkMessOpenBooking(bid) {
				VBOCore.handleDisplayWidgetNotification({widget_id: 'booking_details'}, {
					booking_id: bid,
					modal_options: {
						/**
						 * Overwrite modal options for rendering the admin widget.
						 * We need to use a different suffix in case this current widget was
						 * also rendered within a modal, or it would get dismissed in favour
						 * of the newly opened admin widget.
						 */
						suffix: 'widget_modal_inner_booking_details',
					},
				});
			}

			/**
			 * Completes a step.
			 */
			function vboWidgetBulkMessGoNext(wrapper, stepname) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				if (stepname == 'message' || stepname == 'send') {
					// count the number of selected bookings
					var checked_stats = vboWidgetBulkMessCountChecked(wrapper);
					if (!checked_stats['tot_checked']) {
						alert(Joomla.JText._('VBO_PLEASE_SELECT'));
						return false;
					}

					// update step status if about to build the message
					if (stepname == 'message') {
						widget_instance.find('.vbo-widget-bulkmess-step-message').find('.vbo-widget-bulkmess-step-status').text(Joomla.JText._('VBPVIEWORDERSPEOPLE') + ': ' + checked_stats['tot_checked']);
					}

					// ask for confirmation before sending
					if (stepname == 'send') {
						if (!confirm(Joomla.JText._('VBO_WANT_PROCEED'))) {
							return false;
						} else {
							// start sending
							vboWidgetBulkMessDoSend(wrapper);
						}
					}
				}

				// hide all steps except the next one
				widget_instance.find('.vbo-widget-bulkmess-step').not('.vbo-widget-bulkmess-step-' + stepname).addClass('vbo-widget-bulkmess-step-hidden');

				// display the next step
				widget_instance.find('.vbo-widget-bulkmess-step-' + stepname).removeClass('vbo-widget-bulkmess-step-hidden');
			}

			/**
			 * Starts the process to send the communication messages.
			 */
			function vboWidgetBulkMessDoSend(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// gather the list of booking IDs to notify
				var bids_pool = [];
				widget_instance.find('.vbo-widget-bulkmess-step-bookings')
					.find('input[type="checkbox"]:checked')
					.each(function() {
						bids_pool.push(jQuery(this).val());
					});

				if (!bids_pool.length) {
					alert(Joomla.JText._('VBO_PLEASE_SELECT'));
					return false;
				}

				// hide all buttons to go next
				widget_instance.find('.vbo-widget-bulkmess-gonext').hide();

				// update step status
				widget_instance.find('.vbo-widget-bulkmess-step-send').find('.vbo-widget-bulkmess-step-status').html('<?php VikBookingIcons::e('spinner', 'fa-spin fa-fw'); ?>');

				// trigger the recursive async sending process
				vboWodgetBulkMessRecursiveAsyncSend(0, bids_pool, wrapper);
			}

			/**
			 * Recursive function to asynchronously dispatch the messages.
			 */
			function vboWodgetBulkMessRecursiveAsyncSend(i, pool, wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					throw new Error('Widget instance not found');
				}

				if (i >= pool.length) {
					// process completed, update status
					widget_instance.find('.vbo-widget-bulkmess-step-send')
						.find('.vbo-widget-bulkmess-step-status')
						.html('<?php VikBookingIcons::e('check-circle'); ?>');

					// do not continue
					return;
				}

				if (!pool.hasOwnProperty(i) || !pool[i]) {
					throw new Error('Invalid index argument');
				}

				if (i === 0) {
					// start the progress bar
					widget_instance.find('.vbo-widget-bulkmess-step-send')
						.find('progress')
						.attr('value', 0)
						.attr('max', pool.length)
						.text('0 / ' + pool.length);
				}

				// gather message subject and content
				var subject = widget_instance.find('.vbo-widget-bulkmess-subject').val();
				var message = widget_instance.find('.vbo-widget-bulkmess-messagecont').val();

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

				// make a request to send the message
				VBOCore.doAjax(
					"<?php echo $this->getExecWidgetAjaxUri(); ?>",
					{
						widget_id: "<?php echo $this->getIdentifier(); ?>",
						call: 	   call_method,
						return:    1,
						bid:   	   pool[i],
						index: 	   i,
						message:   message,
						subject:   subject,
						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;
							}

							// update progress bar
							widget_instance.find('.vbo-widget-bulkmess-step-send')
								.find('progress')
								.attr('value', (i + 1))
								.text((i + 1) + ' / ' + pool.length);

							// go next
							vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);

						} catch(err) {
							// log and display error
							console.error('could not parse JSON response', err, response);
							alert('could not parse JSON response');

							// go next either way
							vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);
						}
					},
					(error) => {
						// log and display error
						console.error(error);
						alert(error.responseText);

						// update progress bar
						widget_instance.find('.vbo-widget-bulkmess-step-send')
							.find('progress')
							.attr('value', (i + 1))
							.text((i + 1) + ' / ' + pool.length);

						// go next either way
						vboWodgetBulkMessRecursiveAsyncSend(i + 1, pool, wrapper);
					}
				);
			}

			/**
			 * Toggles the visibility of a step.
			 */
			function vboWidgetBulkMessToggleStep(wrapper, stepname) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				// display the current step
				widget_instance.find('.vbo-widget-bulkmess-step-' + stepname).toggleClass('vbo-widget-bulkmess-step-hidden');
			}

			/**
			 * Counts the number of bookings checked.
			 */
			function vboWidgetBulkMessCountChecked(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				var container = widget_instance.find('.vbo-widget-bulkmess-step-bookings');

				var tot_bookings = container.find('input[type="checkbox"]').length;
				var tot_checked  = container.find('input[type="checkbox"]:checked').length;

				if (tot_checked) {
					// overwrite the first checked booking ID to let the mail preview work
					window['vbo_current_bid'] = container.find('input[type="checkbox"]:checked').first().attr('value');
				}

				return {
					tot_checked:  tot_checked,
					tot_bookings: tot_bookings
				};
			}

			/**
			 * Toggles the checked status for all bookings.
			 */
			function vboWidgetBulkMessSelectAll(wrapper) {
				var widget_instance = jQuery('#' + wrapper);
				if (!widget_instance.length) {
					return false;
				}

				var checked_stats = vboWidgetBulkMessCountChecked(wrapper);
				if (!checked_stats['tot_bookings']) {
					return false;
				}

				var container 	 = widget_instance.find('.vbo-widget-bulkmess-step-bookings');
				var tot_elements = checked_stats['tot_bookings'];

				if (checked_stats['tot_checked'] < checked_stats['tot_bookings']) {
					// select all
					container.find('input[type="checkbox"]').prop('checked', true);
				} else {
					// select none
					container.find('input[type="checkbox"]').prop('checked', false);
					tot_elements = 0;
				}

				// update status on bookings step
				container.find('.vbo-widget-bulkmess-step-status').text(tot_elements + ' / ' + checked_stats['tot_bookings']);
			}

		</script>
			<?php
		}
		?>

		<script type="text/javascript">

			jQuery(function() {

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

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

			});

		</script>

		<?php
	}

	/**
	 * Renders the editor to compose the message.
	 * 
	 * @return 	string
	 */
	private function renderEditor()
	{
		// load assets
		$this->vbo_app->loadVisualEditorAssets();

		// build a list of all special tags for the visual editor
		$special_tags_base = [
			'{customer_name}',
			'{customer_pin}',
			'{booking_id}',
			'{checkin_date}',
			'{checkout_date}',
			'{num_nights}',
			'{rooms_booked}',
			'{tot_adults}',
			'{tot_children}',
			'{tot_guests}',
			'{total}',
			'{total_paid}',
			'{remaining_balance}',
			'{booking_link}',
		];

		// load all conditional text special tags
		$condtext_tags = array_keys(VikBooking::getConditionalRulesInstance()->getSpecialTags());

		// join special tags with conditional texts to construct a list of editor buttons,
		// displayed within the toolbar of Quill editor
		$editor_btns = array_merge($special_tags_base, $condtext_tags);

		// convert special tags into HTML buttons, displayed under the text editor
		$special_tags_base = array_map(function($tag)
		{
			return '<button type="button" class="btn" onclick="setCronTplTag(\'tpl_text\', \'' . $tag . '\');">' . $tag . '</button>';
		}, $special_tags_base);

		// convert conditional texts into HTML buttons, displayed under the text editor
		$condtext_tags = array_map(function($tag)
		{
			return '<button type="button" class="btn vbo-condtext-specialtag-btn" onclick="setCronTplTag(\'tpl_text\', \'' . $tag . '\');">' . $tag . '</button>';
		}, $condtext_tags);

		// build the default message value
		$def_message = !empty($this->widgetSettings->last_message) ? $this->widgetSettings->last_message : $this->getDefaultMessage();

		return $this->vbo_app->renderVisualEditor(
			'vbo_widget_bulkmess_mess',
			$def_message,
			[
				'class' => 'vbo-widget-bulkmess-messagecont',
				'style' => 'width: 96%; height: 150px;',
			],
			[
				'modes' => [
					'visual',
					'text',
				],
			],
			$editor_btns
		);
	}

	/**
	 * Returns the default message.
	 * 
	 * @return 	string
	 */
	private function getDefaultMessage()
	{
		$message 	  = '';
		$logo_html 	  = '';
		$sitelogo 	  = VBOFactory::getConfig()->get('sitelogo');
		$company_name = VikBooking::getFrontTitle();

		if ($sitelogo && is_file(VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources'. DIRECTORY_SEPARATOR . $sitelogo)) {
			$logo_html = '<p style="text-align: center;">'
				. '<img src="' . VBO_ADMIN_URI . 'resources/' . $sitelogo . '" alt="' . htmlspecialchars($company_name) . '" /></p>'
				. "\n";
		}

		$message = 
<<<HTML
$logo_html
<h1 style="text-align: center;">
	<span style="font-family: verdana;">$company_name</span>
</h1>
<hr class="vbo-editor-hl-mailwrapper">
<h4>Dear {customer_name},</h4>
<p><br></p>
<p>This is a message for your stay from {checkin_date} to {checkout_date}.</p>
<p><br></p>
<p><br></p>
<p>Thank you.</p>
<p>$company_name</p>
<hr class="vbo-editor-hl-mailwrapper">
<p><br></p>
HTML
		;

		return $message;
	}

	/**
	 * Sends a message to the guest through a regular email.
	 * 
	 * @param 	array 	$booking 	the reservation record to notify.
	 * @param 	string 	$message 	the message to send.
	 * @param 	string 	$subject 	the email subject.
	 * 
	 * @return 	string 				the message built and sent.
	 */
	private function notifyThroughEmail(array $booking, $message, $subject)
	{
		// fetch booked room details
		$booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);

		// translate contents
		$vbo_tn = VikBooking::getTranslator();
		$vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', ['id' => 'idroom', 'name' => 'room_name']);

		$message = $this->parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn);

		$is_html = (strpos($message, '<') !== false || strpos($message, '</') !== false);
		if ($is_html && !preg_match("/(<\/?br\/?>)+/", $message)) {
			// when no br tags found, apply nl2br
			$message = nl2br($message);
		}

		// get sender email address
		$admin_sendermail = VikBooking::getSenderMail();

		if (!$this->vbo_app->sendMail($admin_sendermail, $admin_sendermail, $booking['custmail'], $admin_sendermail, $subject, $message, $is_html)) {
			VBOHttpDocument::getInstance()->close(500, 'Sending the email message to the booking ID ' . $booking['id'] . ' failed.');
		}

		return $message;
	}

	/**
	 * Sends a message to the guest through a regular email.
	 * 
	 * @param 	array 	$booking 	the reservation record to notify.
	 * @param 	string 	$message 	the message to send.
	 * 
	 * @return 	string 				the message built and sent.
	 */
	private function notifyThroughMessage(array $booking, $message)
	{
		// fetch booked room details
		$booking_rooms = VikBooking::loadOrdersRoomsData($booking['id']);

		// translate contents
		$vbo_tn = VikBooking::getTranslator();
		$vbo_tn->translateContents($booking_rooms, '#__vikbooking_rooms', ['id' => 'idroom', 'name' => 'room_name']);

		$message = $this->parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn);
		if (empty($message)) {
			VBOHttpDocument::getInstance()->close(500, 'Message for the booking ID ' . $booking['id'] . ' is empty.');
		}

		$messaging = VCMChatMessaging::getInstance($booking);
		$result = $messaging->setMessage($message)
			->sendGuestMessage();

		if (!$result && $error = $messaging->getError()) {
			// terminate with the error description
			VBOHttpDocument::getInstance()->close(500, "Message could not be sent to guest - Booking ID {$booking['id']} ({$booking['customer_name']}): {$error}");
		}

		return $message;
	}

	/**
	 * Composes the actual email message by parsing special tokens.
	 * 
	 * @param 	string 	$message 		the message content for the email.
	 * @param   array 	$booking 		booking array record.
	 * @param   array 	$booking_rooms 	list of rooms booked.
	 * @param 	array 	$vbo_tn 		translator object.
	 * 
	 * @return  string 					the email message content ready to be sent.
	 */
	private function parseCustomerEmailTemplate($message, $booking, $booking_rooms, $vbo_tn = null)
	{
		$tpl = $message;

		/**
		 * Parse all conditional text rules.
		 */
		VikBooking::getConditionalRulesInstance()
			->set(array('booking', 'rooms'), array($booking, $booking_rooms))
			->parseTokens($tpl);

		// normalize customer details
		if (empty($booking['customer_name']) && !empty($booking['customer'])) {
			$booking['customer_name'] = trim($booking['first_name'] . ' ' . $booking['last_name']);
		}

		$vbo_df = VikBooking::getDateFormat();
		$df = $vbo_df == "%d/%m/%Y" ? 'd/m/Y' : ($vbo_df == "%m/%d/%Y" ? 'm/d/Y' : 'Y-m-d');
		$tpl = str_replace('{customer_name}', ($booking['customer_name'] ?: ''), $tpl);
		$tpl = str_replace('{booking_id}', $booking['id'], $tpl);
		$tpl = str_replace('{checkin_date}', date($df, $booking['checkin']), $tpl);
		$tpl = str_replace('{checkout_date}', date($df, $booking['checkout']), $tpl);
		$tpl = str_replace('{num_nights}', $booking['days'], $tpl);
		$rooms_booked = [];
		$tot_adults = 0;
		$tot_children = 0;
		$tot_guests = 0;
		foreach ($booking_rooms as $broom) {
			if (array_key_exists($broom['room_name'], $rooms_booked)) {
				$rooms_booked[$broom['room_name']] += 1;
			} else {
				$rooms_booked[$broom['room_name']] = 1;
			}
			$tot_adults += (int)$broom['adults'];
			$tot_children += (int)$broom['children'];
			$tot_guests += ((int)$broom['adults'] + (int)$broom['children']);
		}
		$tpl = str_replace('{tot_adults}', $tot_adults, $tpl);
		$tpl = str_replace('{tot_children}', $tot_children, $tpl);
		$tpl = str_replace('{tot_guests}', $tot_guests, $tpl);
		$rooms_booked_quant = [];
		foreach ($rooms_booked as $rname => $quant) {
			$rooms_booked_quant[] = ($quant > 1 ? $quant.' ' : '').$rname;
		}
		$tpl = str_replace('{rooms_booked}', implode(', ', $rooms_booked_quant), $tpl);
		$tpl = str_replace('{total}', VikBooking::numberFormat($booking['total']), $tpl);
		$tpl = str_replace('{total_paid}', VikBooking::numberFormat($booking['totpaid']), $tpl);
		$remaining_bal = $booking['total'] - $booking['totpaid'];
		$tpl = str_replace('{remaining_balance}', VikBooking::numberFormat($remaining_bal), $tpl);
		$tpl = str_replace('{customer_pin}', (($booking['customer_pin'] ?? '') ?: ($booking['customer']['pin'] ?? '')), $tpl);

		$use_sid = empty($booking['sid']) && !empty($booking['idorderota']) ? $booking['idorderota'] : $booking['sid'];

		$bestitemid  = VikBooking::findProperItemIdType(['booking'], (!empty($booking['lang']) ? $booking['lang'] : null));
		$lang_suffix = $bestitemid && !empty($booking['lang']) ? '&lang=' . $booking['lang'] : '';
		$book_link 	 = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $use_sid . "&ts=" . $booking['ts'] . $lang_suffix, false, (!empty($bestitemid) ? $bestitemid : null));

		$tpl = str_replace('{booking_link}', $book_link, $tpl);

		/**
		 * Rooms Distinctive Features parsing
		 */
		preg_match_all('/\{roomfeature ([a-zA-Z0-9 ]+)\}/U', $tpl, $matches);
		if (isset($matches[1]) && $matches[1]) {
			foreach ($matches[1] as $reqf) {
				$rooms_features = [];
				foreach ($booking_rooms as $broom) {
					$distinctive_features = [];
					$rparams = json_decode($broom['params'], true);
					if (array_key_exists('features', $rparams) && count($rparams['features']) > 0 && array_key_exists('roomindex', $broom) && !empty($broom['roomindex']) && array_key_exists($broom['roomindex'], $rparams['features'])) {
						$distinctive_features = $rparams['features'][$broom['roomindex']];
					}
					if (!count($distinctive_features)) {
						continue;
					}
					$feature_found = false;
					foreach ($distinctive_features as $dfk => $dfv) {
						if (stripos($dfk, $reqf) !== false) {
							$feature_found = $dfk;
							if (strlen(trim($dfk)) == strlen(trim($reqf))) {
								break;
							}
						}
					}
					if ($feature_found !== false && strlen((string)$distinctive_features[$feature_found])) {
						$rooms_features[] = $distinctive_features[$feature_found];
					}
				}
				if ($rooms_features) {
					$rpval = implode(', ', $rooms_features);
				} else {
					$rpval = '';
				}
				$tpl = str_replace("{roomfeature ".$reqf."}", $rpval, $tpl);
			}
		}

		return $tpl;
	}
}