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;
}
}