File "booking_details.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/booking_details.php
File size: 72.21 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* @package VikBooking
* @subpackage com_vikbooking
* @author Alessio Gaggii - E4J srl
* @copyright Copyright (C) 2023 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 "booking details".
*
* @since 1.16.5 (J) - 1.6.5 (WP)
*/
class VikBookingAdminWidgetBookingDetails 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('VBMAINORDEREDIT');
$this->widgetDescr = JText::translate('JSEARCH_TOOLS');
$this->widgetId = basename(__FILE__, '.php');
// define widget and icon and style name
$this->widgetIcon = '<i class="' . VikBookingIcons::i('address-card') . '"></i>';
$this->widgetStyleName = 'light-blue';
}
/**
* Preload the necessary assets.
*
* @return void
*/
public function preload()
{
// load assets for contextual menu
$this->vbo_app->loadContextMenuAssets();
// JS lang def
JText::script('VBODASHSEARCHKEYS');
JText::script('VIKLOADING');
JText::script('VBDASHUPRESONE');
JText::script('VBOCHANNEL');
JText::script('VBCOUPON');
JText::script('VBCUSTOMERNOMINATIVE');
JText::script('VBO_COPY');
JText::script('VBO_COPIED');
JText::script('VBO_CONF_RM_OVERBOOKING_FLAG');
}
/**
* Custom method for this widget only to load the booking details.
* 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 loadBookingDetails()
{
$app = JFactory::getApplication();
$dbo = JFactory::getDbo();
$wrapper = $app->input->getString('wrapper', '');
$booking_key = $app->input->getString('booking_key', '');
$booking_id = $app->input->getString('bid', '');
if (empty($booking_key) && empty($booking_id)) {
// no bookings found
VBOHttpDocument::getInstance()->close(400, JText::translate('VBOREPORTSERRNORESERV'));
}
$q = $dbo->getQuery(true)
->select($dbo->qn('o') . '.*')
->from($dbo->qn('#__vikbooking_orders', 'o'));
if (!empty($booking_id)) {
// search by booking ID or OTA booking ID only
if (preg_match("/^[0-9]+$/", (string)$booking_id)) {
// only numbers could be both website and OTA
$q->where([
$dbo->qn('o.id') . ' = ' . (int)$booking_id,
$dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_id),
], $glue = 'OR');
} else {
// alphanumeric IDs can only belong to an OTA reservation
$q->where($dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_id));
}
} else {
// search by different values
$q->where(1);
if (stripos($booking_key, 'id:') === 0) {
// search by ID or OTA ID
$seek_parts = explode('id:', $booking_key);
$seek_value = trim($seek_parts[1]);
$q->andWhere([
$dbo->qn('o.id') . ' = ' . $dbo->q($seek_value),
$dbo->qn('o.idorderota') . ' = ' . $dbo->q($seek_value),
], $glue = 'OR');
} elseif (stripos($booking_key, 'otaid:') === 0) {
// search by OTA Booking ID
$seek_parts = explode('otaid:', $booking_key);
$seek_value = trim($seek_parts[1]);
$q->where($dbo->qn('o.idorderota') . ' = ' . $dbo->q($seek_value));
} elseif (stripos($booking_key, 'coupon:') === 0) {
// search by coupon code
$seek_parts = explode('coupon:', $booking_key);
$seek_value = trim($seek_parts[1]);
$q->where($dbo->qn('o.coupon') . ' LIKE ' . $dbo->q("%{$seek_value}%"));
} elseif (stripos($booking_key, 'name:') === 0) {
// search by customer nominative
$seek_parts = explode('name:', $booking_key);
$seek_value = trim($seek_parts[1]);
$q->leftJoin($dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $dbo->qn('co.idorder') . ' = ' . $dbo->qn('o.id'));
$q->leftJoin($dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $dbo->qn('c.id') . ' = ' . $dbo->qn('co.idcustomer'));
$q->where('CONCAT_WS(\' \', ' . $dbo->qn('c.first_name') . ', ' . $dbo->qn('c.last_name') . ') LIKE ' . $dbo->q("%{$seek_value}%"));
} else {
// seek for various values
$q->andWhere([
$dbo->qn('o.id') . ' = ' . $dbo->q($booking_key),
$dbo->qn('o.confirmnumber') . ' = ' . $dbo->q($booking_key),
$dbo->qn('o.idorderota') . ' = ' . $dbo->q($booking_key),
], $glue = 'OR');
}
}
$q->order($dbo->qn('id') . ' DESC');
$dbo->setQuery($q, 0, 1);
$details = $dbo->loadAssoc();
if (!$details) {
// no bookings found
VBOHttpDocument::getInstance()->close(404, JText::translate('VBOREPORTSERRNORESERV'));
}
// get customer information
$cpin = VikBooking::getCPinInstance();
$customer = $cpin->getCustomerFromBooking($details['id']);
$customer_bookings_count = 0;
if ($customer && !empty($customer['country'])) {
if (is_file(implode(DIRECTORY_SEPARATOR, [VBO_ADMIN_PATH, 'resources', 'countries', $customer['country'] . '.png']))) {
$customer['country_img'] = '<img src="' . VBO_ADMIN_URI . 'resources/countries/' . $customer['country'] . '.png' . '" title="' . $customer['country'] . '" class="vbo-country-flag vbo-country-flag-left"/>';
}
// count customer bookings (any status)
$customer_bookings_count = $cpin->countCustomerBookings((int) $customer['id']);
}
// availability helper
$av_helper = VikBooking::getAvailabilityInstance();
// get rooms
$booking_rooms = VikBooking::loadOrdersRoomsData($details['id']);
// guests and tariffs information
$tars = [];
$guests = [];
$tot_adults = 0;
$tot_children = 0;
$tot_pets = 0;
foreach ($booking_rooms as $k => $booking_room) {
$guests[] = [
'adults' => $booking_room['adults'],
'children' => $booking_room['children'],
'pets' => $booking_room['pets'],
't_first_name' => $booking_room['t_first_name'],
't_last_name' => $booking_room['t_last_name'],
];
if (!$k || !$details['split_stay']) {
$tot_adults += $booking_room['adults'];
$tot_children += $booking_room['children'];
$tot_pets += $booking_room['pets'];
}
$q = $dbo->getQuery(true)
->select('*')
->from($dbo->qn('#__vikbooking_dispcost'))
->where($dbo->qn('id') . ' = ' . (int)$booking_room['idtar']);
$dbo->setQuery($q, 0, 1);
$tars[($k + 1)] = $dbo->loadAssocList();
}
// room stay dates in case of split stay (or modified room nights)
$room_stay_dates = [];
$room_stay_records = [];
if ($details['split_stay']) {
if ($details['status'] == 'confirmed') {
$room_stay_dates = $av_helper->loadSplitStayBusyRecords($details['id']);
} else {
$room_stay_dates = VBOFactory::getConfig()->getArray('split_stay_' . $details['id'], []);
}
} elseif (!$details['split_stay'] && $details['roomsnum'] > 1 && $details['days'] > 1 && $details['status'] == 'confirmed') {
// load the occupied stay dates for each room in case they were modified
$room_stay_records = $av_helper->loadSplitStayBusyRecords($details['id']);
}
// currency
$otacurrency = !empty($details['channel']) && !empty($details['chcurrency']) ? $details['chcurrency'] : '';
$currencysymb = VikBooking::getCurrencySymb();
// channel name and reservation ID
$otachannel_name = JText::translate('VBORDFROMSITE');
$otachannel_bid = '';
if (!empty($details['channel'])) {
$channelparts = explode('_', $details['channel']);
$otachannel = isset($channelparts[1]) && strlen((string)$channelparts[1]) ? $channelparts[1] : ucwords($channelparts[0]);
$otachannel_name = $otachannel;
$otachannel_bid = !empty($details['idorderota']) ? $details['idorderota'] : '';
}
// readable dates
$checkin_info = getdate($details['checkin']);
$checkin_wday = JText::translate('VB'.strtoupper(substr($checkin_info['weekday'], 0, 3)));
$checkout_info = getdate($details['checkout']);
$checkout_wday = JText::translate('VB'.strtoupper(substr($checkout_info['weekday'], 0, 3)));
$checkin_read_dt = $checkin_wday . ', ' . implode(' ', [$checkin_info['mday'], VikBooking::sayMonth($checkin_info['mon']), $checkin_info['year'], date('H:i', $details['checkin'])]);
$checkout_read_dt = $checkout_wday . ', ' . implode(' ', [$checkout_info['mday'], VikBooking::sayMonth($checkout_info['mon']), $checkout_info['year'], date('H:i', $details['checkout'])]);
// check for special requests
$special_requests = VBOModelReservation::getInstance($details)->extractSpecialRequests();
// check for guest messaging
$messaging_supported = class_exists('VCMChatMessaging');
$tot_guest_messages = 0;
$tot_unread_messages = 0;
$last_guest_messages = [];
if ($messaging_supported) {
$messaging_handler = VCMChatMessaging::getInstance($details);
$tot_guest_messages = $messaging_handler->countBookingGuestMessages();
if ($tot_guest_messages) {
$tot_unread_messages = $messaging_handler->countBookingGuestMessages($unread = true);
if (method_exists($messaging_handler, 'loadBookingLatestMessages')) {
/**
* At the moment we save a query and we do not display
* a snapshot of the latest guest messages.
*/
// $last_guest_messages = $messaging_handler->loadBookingLatestMessages($details['id'], 0, 5);
}
}
}
// load the Channel Manager notifications for this booking, if any
$cm_notifications = [];
$cm_channels = [];
if (class_exists('VikChannelManager') && VikChannelManager::isAvailabilityRequest($api_channel = true)) {
list($cm_notifications, $cm_channels) = $this->loadChannelManagerNotifications($details);
}
// check if we got data by clicking on a push/web notification
$from_push = $this->options()->gotPushData();
// start output buffering
ob_start();
?>
<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact" data-bookingid="<?php echo $details['id']; ?>">
<div class="vbo-params-wrap">
<div class="vbo-params-container">
<div class="vbo-params-block">
<div class="vbo-param-container">
<div class="vbo-param-setting">
<?php
$ch_logo_obj = VikBooking::getVcmChannelsLogo($details['channel'], true);
$channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
$custpic_used = false;
?>
<div class="vbo-customer-info-box">
<div class="vbo-customer-info-box-avatar vbo-customer-avatar-medium">
<span>
<?php
if (!empty($channel_logo)) {
// channel logo has got the highest priority
?>
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>">
<img src="<?php echo $channel_logo; ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');" />
</span>
<?php
} elseif (!empty($customer['pic'])) {
// customer profile picture
$custpic_used = true;
?>
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>">
<img src="<?php echo strpos($customer['pic'], 'http') === 0 ? $customer['pic'] : VBO_SITE_URI . 'resources/uploads/' . $customer['pic']; ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');" />
</span>
<?php
} else {
// we use an icon as fallback
?>
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $otachannel_name); ?>" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');"><?php VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon'); ?></span>
<?php
}
?>
</span>
</div>
</div>
<span class="label label-info"><?php echo JText::translate('VBDASHUPRESONE') . ' ' . $details['id']; ?></span>
<?php
if (!empty($otachannel_bid)) {
?>
<span class="label label-info"><?php echo $otachannel_name . ' ' . $otachannel_bid; ?></span>
<?php
}
$status_type = '';
if (!empty($details['type'])) {
$status_type = JText::translate('VBO_BTYPE_' . strtoupper($details['type']));
if (!strcasecmp($details['type'], 'overbooking')) {
$status_type = '<span class="label label-error vbo-label-nested vbo-label-overbooking" onclick="vboWidgetBookDetsToggleOverbooking(\'' . $wrapper . '\');">' . $status_type . '</span>';
}
$status_type .= ' / ';
}
$extra_status = $details['refund'] > 0 ? ' / ' . JText::translate('VBO_STATUS_REFUNDED') : '';
if ($details['status'] == "confirmed") {
$saystaus = '<span class="label label-success">' . $status_type . JText::translate('VBCONFIRMED') . $extra_status . '</span>';
} elseif ($details['status'] == "standby") {
$saystaus = '<span class="label label-warning">' . $status_type . JText::translate('VBSTANDBY') . $extra_status . '</span>';
} else {
$saystaus = '<span class="label label-error">' . $status_type . JText::translate('VBCANCELLED') . $extra_status . '</span>';
}
echo $saystaus;
if ($details['closure']) {
?>
<span class="label label-error"><?php VikBookingIcons::e('ban'); ?> <?php echo JText::translate('VBDBTEXTROOMCLOSED'); ?></span>
<?php
}
?>
</div>
</div>
<?php
if (!$customer && !$details['closure'] && !empty($details['custdata'])) {
?>
<div class="vbo-param-container">
<div class="vbo-param-setting"><?php echo $details['custdata']; ?></div>
</div>
<?php
}
?>
<div class="vbo-param-container">
<div class="vbo-param-label">
<div class="vbo-customer-info-box">
<div class="vbo-customer-info-box-name">
<?php echo (isset($customer['country_img']) ? $customer['country_img'] . ' ' : '') . ($customer ? ltrim($customer['first_name'] . ' ' . $customer['last_name']) : JText::translate('VBPVIEWORDERSPEOPLE')); ?>
</div>
<?php
if (!$custpic_used && !empty($customer['pic'])) {
$customer_name = $customer ? ltrim($customer['first_name'] . ' ' . $customer['last_name']) : '';
?>
<div class="vbo-customer-info-box-avatar vbo-customer-avatar-small vbo-widget-bookdets-cpic-zoom">
<span>
<img src="<?php echo strpos($customer['pic'], 'http') === 0 ? $customer['pic'] : VBO_SITE_URI . 'resources/uploads/' . $customer['pic']; ?>" data-caption="<?php echo JHtml::fetch('esc_attr', $customer_name); ?>" />
</span>
</div>
<?php
}
?>
</div>
</div>
<div class="vbo-param-setting">
<?php
$guest_counters = [];
if ($tot_adults) {
$guest_counters[] = $tot_adults . ' ' . JText::translate(($tot_adults > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'));
}
if ($tot_children) {
$guest_counters[] = $tot_children . ' ' . JText::translate(($tot_children > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD'));
}
if ($tot_pets) {
$guest_counters[] = $tot_pets . ' ' . JText::translate(($tot_pets > 1 ? 'VBO_PETS' : 'VBO_PET'));
}
echo implode(', ', $guest_counters);
?>
</div>
</div>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBPICKUPAT'); ?></div>
<div class="vbo-param-setting">
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $checkin_read_dt); ?>"><?php echo date(str_replace("/", $this->datesep, $this->df), $details['checkin']); ?></span>
</div>
</div>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBRELEASEAT'); ?></div>
<div class="vbo-param-setting">
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $checkout_read_dt); ?>"><?php echo date(str_replace("/", $this->datesep, $this->df), $details['checkout']); ?></span>
<span>(<?php echo $details['days'] . ' ' . ($details['days'] > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?>)</span>
<?php
if ($details['split_stay']) {
?>
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_SPLIT_STAY')); ?>"><?php VikBookingIcons::e('random'); ?></span>
<?php
}
?>
</div>
</div>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBOINVCREATIONDATE'); ?></div>
<div class="vbo-param-setting">
<?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i', $details['ts']); ?>
</div>
</div>
<?php
if ($special_requests) {
?>
<div class="vbo-param-container">
<div class="vbo-param-setting">
<blockquote class="vbo-booking-special-requests"><?php echo $special_requests; ?></blockquote>
</div>
</div>
<?php
}
/**
* Attempt to build the front-site booking link.
*
* @since 1.17.6 (J) - 1.7.6 (WP)
*/
$use_sid = empty($details['sid']) && !empty($details['idorderota']) ? $details['idorderota'] : $details['sid'];
$bestitemid = VikBooking::findProperItemIdType(['booking'], (!empty($details['lang']) ? $details['lang'] : null));
$lang_suffix = $bestitemid && !empty($details['lang']) ? '&lang=' . $details['lang'] : '';
$book_link = VikBooking::externalroute("index.php?option=com_vikbooking&view=booking&sid=" . $use_sid . "&ts=" . $details['ts'] . $lang_suffix, false, (!empty($bestitemid) ? $bestitemid : null));
// access the model for shortening URLs
$model = VBOModelShortenurl::getInstance($onlyRouted = true)->setBooking($details);
$short_url = $model->getShortUrl($book_link);
?>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBO_CRONJOB_WEBHOOK_TYPE_URL_OPTION'); ?></div>
<div class="vbo-param-setting">
<a href="<?php echo $short_url; ?>" target="_blank"><?php echo $short_url; ?></a>
</div>
</div>
<?php
if ($tot_unread_messages) {
?>
<div class="vbo-param-container">
<div class="vbo-param-label"> </div>
<div class="vbo-param-setting">
<button type="button" class="btn vbo-config-btn vbo-btn-icon-right" data-hasguestmessages="1">
<span><?php echo JText::translate('VBO_GUEST_MESSAGING'); ?></span>
<span class="vbo-bookings-guestmessages-bubble-cont vbo-admin-tipsicon"><i class="<?php echo VikBookingIcons::i('comments'); ?>" data-message-count="<?php echo $tot_unread_messages; ?>"></i></span>
</button>
</div>
</div>
<?php
} elseif ($tot_guest_messages) {
?>
<div class="vbo-param-container">
<div class="vbo-param-label"> </div>
<div class="vbo-param-setting">
<button type="button" class="btn vbo-config-btn vbo-btn-icon-right" data-hasguestmessages="1">
<span><?php echo JText::translate('VBO_GUEST_MESSAGING'); ?> <?php VikBookingIcons::e('comment-dots'); ?></span>
</button>
</div>
</div>
<?php
}
?>
</div>
<div class="vbo-params-block">
<?php
foreach ($booking_rooms as $ind => $booking_room) {
$num = $ind + 1;
$room_icon = $details['split_stay'] && $ind > 0 ? 'random' : 'bed';
?>
<div class="vbo-param-container">
<div class="vbo-param-label">
<span><?php VikBookingIcons::e($room_icon); ?> <?php echo $booking_room['room_name']; ?></span>
<?php
// room sub-unit
$room_number = '';
if (!empty($booking_room['roomindex']) && !$details['closure'] && !empty($booking_room['params'])) {
$room_params = json_decode($booking_room['params'], true);
$arr_features = [];
if (is_array($room_params) && !empty($room_params['features']) && is_array($room_params['features'])) {
// parse distinctive features
foreach ($room_params['features'] as $rind => $rfeatures) {
if ($rind != $booking_room['roomindex']) {
continue;
}
foreach ($rfeatures as $fname => $fval) {
if (strlen((string)$fval)) {
$room_number = '#' . $fval;
break 2;
}
}
}
}
}
if ($room_number) {
?>
<div>
<small><?php echo $room_number; ?></small>
</div>
<?php
}
if ($details['roomsnum'] > 1 && !$details['split_stay'] && isset($guests[$ind])) {
// print guest details for this room
$room_guest_counters = [];
if (!empty($guests[$ind]['t_first_name']) || !empty($guests[$ind]['t_last_name'])) {
$room_guest_counters[] = trim($guests[$ind]['t_first_name'] . ' ' . $guests[$ind]['t_last_name']);
}
if ($guests[$ind]['adults']) {
$room_guest_counters[] = $guests[$ind]['adults'] . ' ' . JText::translate(($guests[$ind]['adults'] > 1 ? 'VBMAILADULTS' : 'VBMAILADULT'));
}
if ($guests[$ind]['children']) {
$room_guest_counters[] = $guests[$ind]['children'] . ' ' . JText::translate(($guests[$ind]['children'] > 1 ? 'VBMAILCHILDREN' : 'VBMAILCHILD'));
}
if ($guests[$ind]['pets']) {
$room_guest_counters[] = $guests[$ind]['pets'] . ' ' . JText::translate(($guests[$ind]['pets'] > 1 ? 'VBO_PETS' : 'VBO_PET'));
}
?>
<div>
<small><?php echo implode(', ', $room_guest_counters); ?></small>
</div>
<?php
}
?>
</div>
<div class="vbo-param-setting">
<div class="vbo-widget-bookdets-roomrate">
<?php
$active_rplan_id = 0;
if (!empty($details['pkg']) || $booking_room['cust_cost'] > 0) {
if (!empty($booking_room['pkg_name'])) {
// package
echo $booking_room['pkg_name'];
} else {
// custom cost can have an OTA Rate Plan name
if (!empty($booking_room['otarplan'])) {
echo ucwords($booking_room['otarplan']);
} else {
echo JText::translate('VBOROOMCUSTRATEPLAN');
}
}
} elseif (!empty($tars[$num]) && !empty($tars[$num][0]['idprice'])) {
$active_rplan_id = $tars[$num][0]['idprice'];
echo VikBooking::getPriceName($tars[$num][0]['idprice']);
} elseif (!empty($booking_room['otarplan'])) {
echo ucwords($booking_room['otarplan']);
} elseif (!$details['closure']) {
echo JText::translate('VBOROOMNORATE');
}
?>
</div>
<?php
// meals included in the room rate
if (!empty($booking_room['meals'])) {
// display included meals defined at room-reservation record
$included_meals = VBOMealplanManager::getInstance()->roomRateIncludedMeals($booking_room);
} else {
// fetch default included meals in the selected rate plan
$included_meals = $active_rplan_id ? VBOMealplanManager::getInstance()->ratePlanIncludedMeals($active_rplan_id) : [];
}
if (!$included_meals && empty($booking_room['meals']) && !empty($details['idorderota']) && !empty($details['channel']) && !empty($details['custdata'])) {
// attempt to fetch the included meal plans from the raw customer data or OTA reservation and room
$included_meals = VBOMealplanManager::getInstance()->otaDataIncludedMeals($details, $booking_room);
}
if ($included_meals) {
?>
<div class="vbo-widget-bookdets-roommeals vbo-wider-badges-wrap">
<?php
foreach ($included_meals as $included_meal) {
?>
<span class="badge badge-info"><?php echo $included_meal; ?></span>
<?php
}
?>
</div>
<?php
}
// check for split-stay or modified stay-dates
if ($details['split_stay'] && $room_stay_dates && isset($room_stay_dates[$ind]) && $room_stay_dates[$ind]['idroom'] == $booking_room['idroom']) {
// print split stay information for this room
$room_stay_checkin = !empty($room_stay_dates[$ind]['checkin_ts']) ? $room_stay_dates[$ind]['checkin_ts'] : $room_stay_dates[$ind]['checkin'];
$room_stay_checkout = !empty($room_stay_dates[$ind]['checkout_ts']) ? $room_stay_dates[$ind]['checkout_ts'] : $room_stay_dates[$ind]['checkout'];
$room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout);
?>
<div class="vbo-cal-splitstay-details vbo-bookdet-splitstay-details">
<div class="vbo-cal-splitstay-dates">
<span class="vbo-cal-splitstay-room-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo $room_stay_nights . ' ' . ($room_stay_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?></span>
<span class="vbo-cal-splitstay-dates-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkin); ?></span>
<span class="vbo-cal-splitstay-dates-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkout); ?></span>
</div>
</div>
<?php
} elseif (!$details['split_stay'] && $room_stay_records && isset($room_stay_records[$ind]) && $room_stay_records[$ind]['idroom'] == $booking_room['idroom']) {
// print modified stay dates information for this room
$room_stay_checkin = $room_stay_records[$ind]['checkin'];
$room_stay_checkout = $room_stay_records[$ind]['checkout'];
$room_stay_nights = $av_helper->countNightsOfStay($room_stay_checkin, $room_stay_checkout);
if ($room_stay_checkin != $details['checkin'] || $room_stay_checkout != $details['checkout']) {
?>
<div class="vbo-cal-splitstay-details vbo-bookdet-splitstay-details vbo-bookdet-roomdatesmod-details">
<div class="vbo-cal-splitstay-dates">
<span class="vbo-cal-splitstay-room-nights"><?php VikBookingIcons::e('moon'); ?> <?php echo $room_stay_nights . ' ' . ($room_stay_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')); ?></span>
<span class="vbo-cal-splitstay-dates-in"><?php VikBookingIcons::e('plane-arrival'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkin); ?></span>
<span class="vbo-cal-splitstay-dates-out"><?php VikBookingIcons::e('plane-departure'); ?> <?php echo date(str_replace("/", $this->datesep, $this->df), $room_stay_checkout); ?></span>
</div>
</div>
<?php
}
}
?>
</div>
</div>
<?php
}
?>
</div>
<?php
if (!$details['closure']) {
?>
<div class="vbo-params-block">
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBEDITORDERNINE'); ?></div>
<div class="vbo-param-setting">
<strong><?php echo ($otacurrency ? "({$otacurrency}) " : '') . $currencysymb . ' ' . VikBooking::numberFormat($details['total']); ?></strong>
</div>
</div>
<?php
if ($details['totpaid'] > 0) {
?>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBPEDITBUSYTOTPAID'); ?></div>
<div class="vbo-param-setting">
<span><?php echo $currencysymb . ' ' . VikBooking::numberFormat($details['totpaid']); ?></span>
</div>
</div>
<?php
}
?>
</div>
<?php
}
// check for administrator notes
if (!empty($details['adminnotes'])) {
?>
<div class="vbo-params-fieldset">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBADMINNOTESTOGGLE'); ?></div>
<div class="vbo-params-block">
<div class="vbo-param-container">
<div class="vbo-param-setting">
<blockquote class="vbo-booking-admin-notes"><?php echo nl2br($details['adminnotes']); ?></blockquote>
</div>
</div>
</div>
</div>
<?php
}
/**
* Check if the booking has got some tasks assigned.
*
* @since 1.18.0 (J) - 1.8.0 (WP)
*/
$taskManager = VBOFactory::getTaskManager();
$bookingTasks = VBOTaskModelTask::getInstance()->filterItems(['id_order' => $details['id']]);
if ($bookingTasks) {
?>
<div class="vbo-params-fieldset vbo-widget-bookdets-tasks-list">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBO_TASK_MANAGER'); ?></div>
<div class="vbo-bookingdet-tasks-list">
<?php
foreach ($bookingTasks as $taskRecord) {
$task = VBOTaskTaskregistry::getInstance((array) $taskRecord);
?>
<div class="vbo-bookingdet-task-details" data-task-id="<?php echo $task->getID(); ?>">
<div class="vbo-bookingdet-task-detail" data-type="info">
<span class="vbo-bookingdet-task-title"><?php echo $task->getTitle(); ?></span>
<span class="vbo-bookingdet-task-sub-title"><?php echo $task->getAreaName($task->getAreaID()); ?></span>
</div>
<?php
$taskUnreadMessages = (bool) $task->get('hasUnreadMessages', false);
if ($taskUnreadMessages) {
?>
<div class="vbo-bookingdet-task-detail" data-type="unread-messages">
<span class="unread-message-dot"><?php VikBookingIcons::e('comment'); ?></span>
</div>
<?php
}
if ($taskManager->statusTypeExists($task->getStatus())) {
$taskStatus = $taskManager->getStatusTypeInstance($task->getStatus());
?>
<div class="vbo-bookingdet-task-detail" data-type="status">
<span class="vbo-tm-task-status-badge vbo-tm-color <?php echo $taskStatus->getColor(); ?>" data-status="<?php echo JHtml::fetch('esc_attr', $taskStatus->getEnum()); ?>"><?php echo $taskStatus->getName(); ?></span>
</div>
<?php
}
?>
</div>
<?php
}
?>
</div>
</div>
<?php
}
// check if this booking has got a reminder
$reminders_helper = VBORemindersHelper::getInstance();
$has_reminders = $reminders_helper->bookingHasReminder($details['id']);
$few_reminders = [];
if ($has_reminders) {
$few_reminders = $reminders_helper->loadReminders([
'idorder' => $details['id'],
'onlyorder' => 1,
'completed' => 1,
'expired' => 1,
], 0, 5);
}
?>
<div class="vbo-params-fieldset">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBO_W_REMINDERS_TITLE'); ?></div>
<div class="vbo-params-block">
<?php
foreach ($few_reminders as $reminder) {
$diff_data = [];
if (!empty($reminder->duedate)) {
// calculate distance to expiration date from today
$diff_data = $reminders_helper->relativeDatesDiff($reminder->duedate);
}
?>
<div class="vbo-param-container">
<div class="vbo-param-setting">
<div class="vbo-widget-reminders-record-info">
<div class="vbo-widget-reminders-record-txt">
<div class="vbo-widget-reminder-title"><?php echo htmlspecialchars($reminder->title); ?></div>
<?php
if (!empty($reminder->descr)) {
?>
<div class="vbo-widget-reminder-descr"><?php echo htmlspecialchars($reminder->descr); ?></div>
<?php
}
?>
</div>
<div class="vbo-widget-reminders-record-due">
<?php
if (!empty($reminder->duedate)) {
?>
<div class="vbo-widget-reminders-record-due-datetime">
<span class="vbo-widget-reminders-record-due-date">
<span title="<?php echo $reminder->duedate; ?>"><?php echo $diff_data['relative']; ?></span>
</span>
<?php
if ($reminder->usetime) {
?>
<span class="vbo-widget-reminders-record-due-time">
<span><?php echo $diff_data['date_a']->format('H:i'); ?></span>
</span>
<?php
}
?>
</div>
<?php
}
?>
</div>
</div>
</div>
</div>
<?php
}
?>
<div class="vbo-param-container">
<div class="vbo-param-label"> </div>
<div class="vbo-param-setting">
<?php
if ($has_reminders) {
?>
<button type="button" class="btn vbo-config-btn" data-hasreminders="1"><?php VikBookingIcons::e('bell'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
<?php
} else {
?>
<button type="button" class="btn btn-success" data-hasreminders="0"><?php VikBookingIcons::e('plus-circle'); ?> <?php echo JText::translate('VBO_ADD_NEW'); ?></button>
<?php
}
?>
</div>
</div>
</div>
</div>
<?php
if ($customer) {
/**
* Allow to manage the customer documents and display the registration status.
*
* @since 1.16.10 (J) - 1.6.10 (WP)
*/
$checked_status = null;
$checked_cls = 'label-info';
if (!$details['closure'] && $details['status'] == 'confirmed') {
switch ($details['checked']) {
case -1:
$checked_status = JText::translate('VBOCHECKEDSTATUSNOS');
$checked_cls = 'label-danger';
break;
case 1:
$checked_status = JText::translate('VBOCHECKEDSTATUSIN');
$checked_cls = 'label-success';
break;
case 2:
$checked_status = JText::translate('VBOCHECKEDSTATUSOUT');
$checked_cls = 'label-warning';
break;
default:
if (!empty($customer['pax_data'])) {
// pre check-in performed via front-end
$checked_status = JText::translate('VBOCHECKEDSTATUSPRECHECKIN');
}
break;
}
}
?>
<div class="vbo-params-fieldset vbo-widget-bookdets-customer-docs">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOCUSTOMERDOCUMENTS'); ?></div>
<div class="vbo-params-block">
<?php
if ($customer_bookings_count > 1) {
// display badge for returning customer
?>
<div class="vbo-param-container">
<div class="vbo-param-label">
<span class="label label-warning"><?php VikBookingIcons::e('certificate'); ?> <?php echo JText::translate('VBO_CONDTEXT_RULE_RETCUST'); ?></span>
</div>
<div class="vbo-param-setting"></div>
</div>
<?php
}
if ($checked_status) {
// display the registration status
?>
<div class="vbo-param-container">
<div class="vbo-param-label"><?php echo JText::translate('VBOCHECKEDSTATUS'); ?></div>
<div class="vbo-param-setting">
<span class="label <?php echo $checked_cls; ?>"><?php echo $checked_status; ?></span>
</div>
</div>
<?php
}
?>
<div class="vbo-param-container">
<div class="vbo-param-setting">
<?php
/**
* Render the customer-dropfiles layout to handle the customer documents.
*/
$layout_data = [
'caller' => 'widget',
'customer' => $customer,
];
// render the permissions layout
echo JLayoutHelper::render('customer.dropfiles', $layout_data);
?>
</div>
</div>
</div>
</div>
<?php
}
// booking history
$history_obj = VikBooking::getBookingHistoryInstance($details['id']);
$history_list = $history_obj->loadHistory();
if ($history_list) {
?>
<div class="vbo-params-fieldset vbo-widget-bookdets-history-list">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOBOOKHISTORYTAB'); ?></div>
<div class="vbo-params-block">
<?php
$max_display_records = 3;
foreach ($history_list as $hind => $hist) {
$html_descr = strpos((string) $hist['descr'], '<') !== false ? $hist['descr'] : nl2br((string) $hist['descr']);
$text_descr = strip_tags((string) $hist['descr']);
$text_lines = preg_split("/[\r\n]/", $text_descr);
$read_more = false;
if ($text_lines && count($text_lines) > 1) {
$first_line = $text_lines[0];
unset($text_lines[0]);
if (strlen($first_line) < strlen(implode('', $text_lines))) {
$text_descr = $first_line;
$read_more = true;
}
}
$tip_info = [
JText::translate('VBOBOOKHISTORYLBLTOT') . ': ' . $currencysymb . ' ' . VikBooking::numberFormat($hist['total']),
JText::translate('VBOBOOKHISTORYLBLTPAID') . ': ' . $currencysymb . ' ' . VikBooking::numberFormat($hist['totpaid']),
];
?>
<div class="vbo-param-container vbo-widget-bookdets-history-record" style="<?php echo $hind >= $max_display_records ? 'display: none;' : ''; ?>">
<div class="vbo-param-label">
<div>
<span class="vbo-tooltip vbo-tooltip-top vbo-widget-bookdets-history-evname" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', implode(', ', $tip_info)); ?>"><?php echo $history_obj->validType($hist['type'], true); ?></span>
</div>
<div>
<span class="vbo-widget-bookdets-history-evdate"><?php echo JHtml::fetch('date', $hist['dt'], 'Y-m-d H:i:s'); ?></span>
</div>
</div>
<div class="vbo-param-setting vbo-widget-bookdets-history-descr-wrap">
<?php
if ($read_more) {
?>
<div class="vbo-widget-bookdets-history-descr-txt">
<span><?php echo $text_descr; ?></span>
<div>
<span class="vbo-tooltip vbo-tooltip-top vbo-widget-bookdets-history-descr-more" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_READ_MORE')); ?>" onclick="vboWidgetBookDetsReadFullHistory(this);">
<a href="javascript:void(0);">[...]</a>
</span>
</div>
</div>
<?php
}
?>
<div class="vbo-widget-bookdets-history-descr-html" style="<?php echo $read_more ? 'display: none;' : ''; ?>"><?php echo $html_descr; ?></div>
</div>
</div>
<?php
}
if (count($history_list) > $max_display_records) {
// display button to display all history records
?>
<div class="vbo-param-container vbo-widget-bookdets-history-showall">
<div class="vbo-param-label"> </div>
<div class="vbo-param-setting">
<button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsHistoryAll('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('history'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
</div>
</div>
<?php
}
?>
</div>
</div>
<?php
}
// channel manager notifications and channels involved
if ($cm_notifications) {
?>
<div class="vbo-params-fieldset">
<div class="vbo-params-fieldset-label"><?php echo JText::translate('VBOBOOKHISTORYTCM'); ?></div>
<div class="vbo-params-block">
<?php
$max_display_notifications = 2;
foreach ($cm_notifications as $notif_ind => $cm_notification) {
$badge_class = 'success';
$badge_icon = 'check-circle';
if ($cm_notification['type'] == 0) {
$badge_class = 'error';
$badge_icon = 'times-circle';
} elseif ($cm_notification['type'] == 2) {
$badge_class = 'warning';
$badge_icon = 'exclamation-triangle';
}
?>
<div class="vbo-params-fieldset vbo-widget-bookdets-cmnotifs-record" style="<?php echo $notif_ind >= $max_display_notifications ? 'display: none;' : ''; ?>">
<div class="vbo-params-fieldset-label">
<span class="label label-<?php echo $badge_class; ?>">
<?php VikBookingIcons::e($badge_icon); ?>
<span><?php echo date(str_replace("/", $this->datesep, $this->df) . ' H:i:s', $cm_notification['ts']); ?></span>
</span>
</div>
<div class="vbo-params-block vbo-params-block-compact">
<?php
foreach ($cm_notification['children'] as $channel_key => $channel_notifs) {
$logo_obj = VikBooking::getVcmChannelsLogo($cm_channels[$channel_key]['name'], true);
$channel_logo = $logo_obj ? $logo_obj->getSmallLogoURL() : '';
if (!$channel_logo) {
continue;
}
// build readable channel name
$raw_ch_name = (string)$cm_channels[$channel_key]['name'];
$lower_name = strtolower($raw_ch_name);
$lower_name = preg_replace("/hotel$/", ' hotel', $lower_name);
$channel_name = ucwords(preg_replace("/api$/", '', $lower_name));
?>
<div class="vbo-param-container">
<div class="vbo-param-label">
<div class="vbo-customer-info-box">
<div class="vbo-customer-info-box-avatar vbo-customer-avatar-small">
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $channel_name); ?>">
<img src="<?php echo $channel_logo; ?>" />
</span>
</div>
</div>
</div>
<div class="vbo-param-setting">
<?php
$channel_responses = [];
foreach ($channel_notifs as $resp_ind => $channel_notif) {
// build update result string
$upd_result = (string)$channel_notif['cont'];
// strip "e4j." from the beginning of the string
$upd_result = preg_replace("/^e4j./i", '', $upd_result);
// strip ending hotel code "{hotelid 000000}"
$upd_result = preg_replace("/\s?\{hotelid\s?[0-9A-Z]+\}$/i", '', $upd_result);
// default values for extra logging
$log_result = '';
$ruid_codes = [];
// we expect this to be a channel update result
if ($resp_ind) {
// parse the response type
if (preg_match("/^OK\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
// successful response example ("OK.Airbnb.AR_RS")
$upd_result = trim(str_replace($matches[0], "OK {$channel_name}", $upd_result));
} elseif (preg_match("/^warning\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
// warning response example ("warning.Airbnb.AR_RS")
$upd_result = trim(str_replace($matches[0], $channel_name, $upd_result));
$log_result = 'warning';
} elseif (preg_match("/^error\.[A-Z\.]+\.AR_RS/i", $upd_result, $matches)) {
// error response example ("error.Airbnb.AR_RS")
$upd_result = trim(str_replace($matches[0], $channel_name, $upd_result));
$log_result = 'error';
}
// check for RUID codes (there could be more than one)
if (preg_match_all("/RUID: \[.+\]/", $upd_result, $ruid_matches)) {
$ruid_codes = is_array($ruid_matches[0]) ? $ruid_matches[0] : [];
foreach ($ruid_matches[0] as $ruid_log) {
// strip log from update result because it will be displayed separately
$upd_result = str_replace($ruid_log, '', $upd_result);
}
}
}
if (in_array($upd_result, $channel_responses)) {
// do not display duplicate responses from the same channel (i.e. duplicate unexpected notifications)
continue;
}
// register channel response
$channel_responses[] = $upd_result;
?>
<div class="vbo-widget-bookdets-cm-updresult">
<?php
// arrow icon
VikBookingIcons::e(($resp_ind === 0 ? 'long-arrow-right' : 'long-arrow-left'));
// print a label in case of errors or warning
if ($log_result) {
?>
<span class="label label-<?php echo $log_result == 'warning' ? 'warning' : 'error'; ?>"><?php echo $log_result; ?></span>
<?php
}
?>
<span class="vbo-widget-bookdets-cm-updresult-log"><?php echo $upd_result; ?></span>
<?php
// check for extra logs (RUIDs)
if ($ruid_codes) {
?>
<span class="vbo-widget-bookdets-cm-ruids">
<button class="btn btn-small vbo-btn-icon-right" type="button" onclick="vboWidgetBookDetsCopyRuids(this);">
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', JText::translate('VBO_COPY')); ?>">RUID <?php VikBookingIcons::e('copy'); ?></span>
</button>
</span>
<textarea class="vbo-textarea-copyable"><?php echo htmlentities(implode("\n", $ruid_codes)); ?></textarea>
<?php
}
?>
</div>
<?php
}
// check if the channel was notified but did not respond
if ($channel_responses && (count($channel_responses) % 2) != 0) {
?>
<div><?php echo str_repeat('-', 6); ?></div>
<?php
}
?>
</div>
</div>
<?php
}
?>
</div>
</div>
<?php
}
// check if some notifications were hidden
if (count($cm_notifications) > $max_display_notifications) {
// display button to show all channel manager notifications
?>
<div class="vbo-param-container vbo-widget-bookdets-cmnotifs-showall">
<div class="vbo-param-label"> </div>
<div class="vbo-param-setting">
<button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsCmnotifsAll('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('network-wired'); ?> <?php echo JText::translate('VBO_SEE_ALL'); ?></button>
</div>
</div>
<?php
}
?>
</div>
</div>
<?php
}
?>
<div class="vbo-param-container">
<div class="vbo-param-setting">
<button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>');"><?php echo JText::translate('VBOVIEWBOOKINGDET'); ?></button>
<button type="button" class="btn" onclick="vboWidgetBookDetsOpenBooking('<?php echo $details['id']; ?>', 'edit');"><?php echo JText::translate('VBMODRES'); ?></button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
jQuery(function() {
jQuery('#<?php echo $wrapper; ?>').find('button[data-hasreminders]').on('click', function() {
// check if the clicked button indicates that there are reminders
let has_reminders = jQuery(this).attr('data-hasreminders') == '1';
// render the reminders widget by injecting the proper options
VBOCore.handleDisplayWidgetNotification({widget_id: 'reminders'}, {
bid: <?php echo $details['id']; ?>,
action: has_reminders ? '' : 'add_new',
completed: has_reminders ? 1 : 0,
expired: has_reminders ? 1 : 0,
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. Prepending to body is needed
* because the reminders widget use the datepicker calendar.
*/
suffix: 'widget_modal_inner_reminders',
body_prepend: true,
},
});
});
jQuery('#<?php echo $wrapper; ?>').find('button[data-hasguestmessages="1"]').on('click', function() {
// render the guest messages widget by injecting the proper options
VBOCore.handleDisplayWidgetNotification({widget_id: 'guest_messages'}, {
bid: <?php echo $details['id']; ?>,
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_guest_messages',
},
});
});
jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookdets-cpic-zoom').find('img').on('click', function() {
// display modal
VBOCore.displayModal({
suffix: 'zoom-image',
title: jQuery(this).attr('data-caption'),
body: jQuery(this).clone(),
});
});
// check for action button
var vbo_widget_bookdets_action_btn = jQuery('#<?php echo $wrapper; ?>').find('.vbo-widget-bookdets-actionbtn');
if (vbo_widget_bookdets_action_btn.length) {
vbo_widget_bookdets_action_btn.attr('href', vbo_widget_bookdets_action_btn.attr('href').replace('%d', '<?php echo $details['id']; ?>'));
vbo_widget_bookdets_action_btn.closest('.vbo-widget-push-notification-action').show();
}
// register click event on the booking task(s)
document.querySelector('#<?php echo $wrapper; ?>').querySelectorAll('.vbo-bookingdet-task-details[data-task-id]').forEach((task) => {
const taskId = task.getAttribute('data-task-id');
task.addEventListener('click', (e) => {
// define the modal cancel button
let cancel_btn = jQuery('<button></button>')
.attr('type', 'button')
.addClass('btn')
.text(<?php echo json_encode(JText::translate('VBANNULLA')); ?>)
.on('click', () => {
VBOCore.emitEvent('vbo-tm-edittask-dismiss');
});
// define the modal save button
let save_btn = jQuery('<button></button>')
.attr('type', 'button')
.addClass('btn btn-success')
.text(<?php echo json_encode(JText::translate('VBSAVE')); ?>)
.on('click', function() {
// disable button to prevent double submissions
let submit_btn = jQuery(this);
submit_btn.prop('disabled', true);
// start loading animation
VBOCore.emitEvent('vbo-tm-edittask-loading');
// get form data
const taskForm = new FormData(document.querySelector('#vbo-tm-task-manage-form'));
// build query parameters for the request
let qpRequest = new URLSearchParams(taskForm);
// make sure the request always includes the assignees query parameter, even if the list is empty
if (!qpRequest.has('data[assignees][]')) {
qpRequest.append('data[assignees][]', []);
}
// make sure the request always includes the tags query parameter, even if the list is empty
if (!qpRequest.has('data[tags][]')) {
qpRequest.append('data[tags][]', []);
}
// make the request
VBOCore.doAjax(
"<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.updateTask'); ?>",
qpRequest.toString(),
(resp) => {
// dismiss the modal on success
VBOCore.emitEvent('vbo-tm-edittask-dismiss');
// reload the current booking
vboWidgetBookDetsLoad('<?php echo $wrapper; ?>');
},
(error) => {
// display error message
alert(error.responseText);
// re-enable submit button
submit_btn.prop('disabled', false);
// stop loading
VBOCore.emitEvent('vbo-tm-edittask-loading');
}
);
});
// display modal
let modalBody = VBOCore.displayModal({
suffix: 'tm_edittask_modal',
title: <?php echo json_encode(JText::translate('VBO_TASK')); ?> + ' #' + taskId,
extra_class: 'vbo-modal-rounded vbo-modal-taller vbo-modal-large',
body_prepend: true,
lock_scroll: true,
escape_dismiss: false,
footer_left: cancel_btn,
footer_right: save_btn,
loading_event: 'vbo-tm-edittask-loading',
dismiss_event: 'vbo-tm-edittask-dismiss',
});
// start loading animation
VBOCore.emitEvent('vbo-tm-edittask-loading');
// make the request
VBOCore.doAjax(
"<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.renderLayout'); ?>",
{
type: 'tasks.managetask',
data: {
task_id: taskId,
form_id: 'vbo-tm-task-manage-form',
},
},
(resp) => {
// stop loading
VBOCore.emitEvent('vbo-tm-edittask-loading');
try {
// decode the response (if needed), and append the content to the modal body
let obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
modalBody.append(obj_res['html']);
} catch (err) {
console.error('Error decoding the response', err, resp);
}
},
(error) => {
// display error message
alert(error.responseText);
// stop loading
VBOCore.emitEvent('vbo-tm-edittask-loading');
}
);
});
});
<?php
if ($open_task = $this->getOption('task_id', 0)) {
?>
document.querySelector('#<?php echo $wrapper; ?> .vbo-bookingdet-task-details[data-task-id="<?php echo $open_task; ?>"]').click();
<?php
}
?>
});
<?php
if ($from_push) {
// emit the event to read all notifications in the current context when clicking on a push/web notification
?>
setTimeout(() => {
VBOCore.emitEvent('vbo-nc-read-notifications', {
criteria: {
group: '<?php echo !empty($details['idorderota']) && !empty($details['channel']) ? 'otas' : 'website'; ?>',
idorder: '<?php echo $details['id']; ?>',
}
});
}, 200);
<?php
}
?>
</script>
<?php
// get the HTML buffer
$html_content = ob_get_contents();
ob_end_clean();
// return an associative array of values
return [
'html' => $html_content,
];
}
/**
* 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-bookdets-' . $wrapper_instance;
// get permissions
$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
if (!$vbo_auth_bookings) {
// display nothing
return;
}
// check multitask data
$load_bid = 0;
$js_modal_id = '';
$is_modal_rendering = false;
if ($data) {
// access Multitask data
$is_modal_rendering = $data->isModalRendering();
if ($is_modal_rendering) {
// get modal JS identifier
$js_modal_id = $data->getModalJsIdentifier();
}
/**
* This widget should not get the current ID from Multitask data
* to avoid displaying duplicate contents, it should rather get
* the ID from the injected options (i.e. new Push notification).
*/
$load_bid = $this->options()->fetchBookingId();
}
// check if we got data by clicking on a push notification from the ServiceWorker
$from_push = $this->options()->gotPushData();
?>
<div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>" data-loadbid="<?php echo $load_bid; ?>">
<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-bookdets-wrap">
<div class="vbo-widget-bookdets-inner">
<div class="vbo-widget-element-filter-top">
<div class="btn-group-inline">
<button type="button" class="btn btn-secondary vbo-context-menu-btn vbo-widget-bookdets-searchtype">
<span class="vbo-context-menu-ico"><?php VikBookingIcons::e('sort-down'); ?></span>
</button>
<input type="text" name="booking_key" value="<?php echo $load_bid ? "id: {$load_bid}" : ''; ?>" placeholder="<?php echo htmlspecialchars(JText::translate('VBOFILTCONFNUMCUST')); ?>" autocomplete="off" />
<button type="button" class="btn vbo-config-btn" onclick="vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('search'); ?> <span class="vbo-widget-bookdets-searchbtn"><?php echo JText::translate('VBODASHSEARCHKEYS'); ?></span></button>
</div>
</div>
<?php
// check if we need to display the Push notification message
if ($from_push) {
// get the title and message
$push_title = $this->getOption('title', '');
$push_message = $this->getOption('message', '');
// check for notification action and "from"
$push_action = $this->getOption('action', '');
$push_from = $this->getOption('from', '');
// notification severity
$push_severity = $this->getOption('severity', 'info');
// replace new line characters with a white space
$push_message = preg_replace("/[\r\n]/", ' ', $push_message);
// get rid of white space characters (only white space, no new lines or tabs like "\s") used more than once in a row
$push_message = preg_replace("/ {2,}/", ' ', $push_message);
?>
<div class="vbo-widget-push-notification vbo-widget-push-notification-<?php echo $push_severity; ?>">
<?php
if (!empty($push_title)) {
?>
<div class="vbo-widget-push-notification-title">
<strong><?php echo $push_title; ?></strong>
</div>
<?php
}
if (!empty($push_message)) {
?>
<div class="vbo-widget-push-notification-message">
<span><?php echo $push_message; ?></span>
</div>
<?php
}
if ($push_action && !strcasecmp($push_from, 'airbnb')) {
// supported action
if (!strcasecmp($push_action, 'host_guest_review')) {
// prepare link to trigger the host-to-guest review action
?>
<div class="vbo-widget-push-notification-action" style="display: none;">
<a class="btn vbo-config-btn vbo-widget-bookdets-actionbtn" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d¬if_action=airbnb_host_guest_review', $xhtml = false); ?>" target="_blank"><?php VikBookingIcons::e('star'); ?> <?php echo JText::translate('VBO_REVIEW_YOUR_GUEST'); ?></a>
</div>
<?php
} elseif (!strcasecmp($push_action, 'new_guest_review')) {
// prepare link to display the guest review received
?>
<div class="vbo-widget-push-notification-action" style="display: none;">
<a class="btn vbo-config-btn vbo-widget-bookdets-actionbtn" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d¬if_action=see_guest_review', $xhtml = false); ?>" target="_blank"><?php VikBookingIcons::e('star'); ?> <?php echo JText::translate('VBOSEEGUESTREVIEW'); ?></a>
</div>
<?php
}
}
?>
</div>
<?php
}
?>
<div class="vbo-widget-element-body"></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.
*/
?>
<a class="vbo-widget-bookdets-basenavuri" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editorder&cid[]=%d', $xhtml = false); ?>" style="display: none;"></a>
<a class="vbo-widget-editbook-basenavuri" href="<?php echo VBOFactory::getPlatform()->getUri()->admin('index.php?option=com_vikbooking&task=editbusy&cid[]=%d', $xhtml = false); ?>" style="display: none;"></a>
<script type="text/javascript">
/**
* Open the booking details (or edit booking) page for the clicked reservation.
*/
function vboWidgetBookDetsOpenBooking(id, edit) {
var open_url = jQuery((edit ? '.vbo-widget-editbook-basenavuri' : '.vbo-widget-bookdets-basenavuri')).first().attr('href');
open_url = open_url.replace('%d', id);
// navigate in a new tab
window.open(open_url, '_blank');
}
/**
* Searches for a booking according to input filter.
*/
function vboWidgetBookDetsSearch(wrapper, options, bid) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
var booking_key = widget_instance.find('input[name="booking_key"]').val();
// show loading
widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VIKLOADING'));
// the widget method to call
var call_method = 'loadBookingDetails';
// make a request to load the booking details
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
bid: bid,
booking_key: booking_key,
_options: options,
wrapper: wrapper,
tmpl: "component"
},
(response) => {
// hide loading
widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VBODASHSEARCHKEYS'));
try {
var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
if (!obj_res.hasOwnProperty(call_method)) {
widget_instance.find('.vbo-widget-element-body').html('');
console.error('Unexpected JSON response', obj_res);
return false;
}
// replace HTML with new content
widget_instance.find('.vbo-widget-element-body').html(obj_res[call_method]['html']);
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// hide loading
widget_instance.find('.vbo-widget-bookdets-searchbtn').text(Joomla.JText._('VBODASHSEARCHKEYS'));
// display no bookings message
widget_instance.find('.vbo-widget-element-body').html((error.status != 400 ? '<p class="err">' + error.responseText + '</p>' : ''));
}
);
}
/**
* Attempts to load a booking ID from the injected options.
*/
function vboWidgetBookDetsLoad(wrapper, options) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
var load_bid = widget_instance.attr('data-loadbid');
if (!load_bid || load_bid == '0') {
return false;
}
// load booking
vboWidgetBookDetsSearch(wrapper, options, load_bid);
}
/**
* Prepares the input search field with the proper type hint.
*/
function vboWidgetBookDetsSearchType(wrapper, search_hint) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
if (!search_hint) {
search_hint = '';
} else {
search_hint += ': ';
}
widget_instance.find('input[name="booking_key"]').val(search_hint).focus();
}
/**
* Displays the full history record description.
*/
function vboWidgetBookDetsReadFullHistory(btn) {
var descr_wrapper = jQuery(btn).closest('.vbo-widget-bookdets-history-descr-wrap');
descr_wrapper.find('.vbo-widget-bookdets-history-descr-txt').hide();
descr_wrapper.find('.vbo-widget-bookdets-history-descr-html').show();
}
/**
* Displays all booking history records.
*/
function vboWidgetBookDetsHistoryAll(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// show all records
widget_instance.find('.vbo-widget-bookdets-history-record').show();
// hide button
widget_instance.find('.vbo-widget-bookdets-history-showall').hide('.');
}
/**
* Display all channel manager notifications.
*/
function vboWidgetBookDetsCmnotifsAll(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// show all records
widget_instance.find('.vbo-widget-bookdets-cmnotifs-record').show();
// hide button
widget_instance.find('.vbo-widget-bookdets-cmnotifs-showall').hide('.');
}
/**
* Copies to clipboard the Booking.com RUID response codes, if any.
*/
function vboWidgetBookDetsCopyRuids(btn) {
var tarea = btn.closest('.vbo-widget-bookdets-cm-updresult').querySelector('.vbo-textarea-copyable');
VBOCore.copyToClipboard(tarea).then((success) => {
jQuery(btn).find('.vbo-tooltip').attr('data-tooltiptext', Joomla.JText._('VBO_COPIED') + '!');
}).catch((err) => {
alert('Could not copy the logs');
});
}
/**
* Toggles the overbooking status-type.
*/
function vboWidgetBookDetsToggleOverbooking(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
var bid = widget_instance.find('[data-bookingid]').attr('data-bookingid');
if (confirm(Joomla.JText._('VBO_CONF_RM_OVERBOOKING_FLAG'))) {
VBOCore.doAjax(
"<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=bookings.delete_type_flag'); ?>",
{
bid: bid,
flag: 'overbooking',
},
(success) => {
// remove "overbooking" label
let parent_label = widget_instance.find('.vbo-label-overbooking').parent('.label');
widget_instance.find('.vbo-label-overbooking').remove();
parent_label.text(parent_label.text().replace(' / ', ''));
// turn flag on for the booking modified
vbo_widget_book_dets_last_mod_bid = bid;
},
(error) => {
alert(error.responseText);
}
);
}
}
</script>
<?php
}
?>
<script type="text/javascript">
// holds the lastly modified booking ID
var vbo_widget_book_dets_last_mod_bid = null;
jQuery(function() {
// when document is ready, load contents for this widget's instance
vboWidgetBookDetsLoad('<?php echo $wrapper_id; ?>', <?php echo json_encode($this->getOptions()); ?>);
// register keyup event for auto-submit
let input_search = document.querySelector('#<?php echo $wrapper_id; ?> input[name="booking_key"]');
if (input_search) {
input_search.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
return vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
}
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
// match a valid booking ID to increase or decrease
let search_val = input_search.value;
let rgx_id = new RegExp(/^(id:)?\s?[0-9]+$/);
if (search_val && search_val.match(rgx_id)) {
let rgx_num = new RegExp(/[^0-9]/g);
let raw_idn = parseInt(search_val.replace(rgx_num, ''));
if (!isNaN(raw_idn)) {
// replace value and start search
input_search.value = search_val.replace(raw_idn, (e.key === 'ArrowUp' ? (raw_idn + 1) : (raw_idn - 1)));
return vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
}
}
}
});
}
// render context menu
jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-bookdets-searchtype').vboContextMenu({
placement: 'bottom-left',
buttons: [
{
icon: '<?php echo VikBookingIcons::i('search'); ?>',
text: Joomla.JText._('VBDASHUPRESONE'),
separator: false,
action: (root, config) => {
vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'id');
},
},
{
icon: '<?php echo VikBookingIcons::i('search'); ?>',
text: Joomla.JText._('VBDASHUPRESONE') + ' (' + Joomla.JText._('VBOCHANNEL') + ')',
separator: false,
action: (root, config) => {
vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'otaid');
},
},
{
icon: '<?php echo VikBookingIcons::i('user-tag'); ?>',
text: Joomla.JText._('VBCOUPON'),
separator: false,
action: (root, config) => {
vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'coupon');
},
},
{
icon: '<?php echo VikBookingIcons::i('user'); ?>',
text: Joomla.JText._('VBCUSTOMERNOMINATIVE'),
separator: false,
action: (root, config) => {
vboWidgetBookDetsSearchType('<?php echo $wrapper_id; ?>', 'name');
},
},
],
});
// subscribe to the multitask-panel-close event to emit the event for the lastly modified booking ID
document.addEventListener(VBOCore.multitask_close_event, function() {
if (vbo_widget_book_dets_last_mod_bid) {
// emit the event with data for anyone who is listening to it
VBOCore.emitEvent('vbo_booking_modified', {
bid: vbo_widget_book_dets_last_mod_bid
});
}
});
<?php
if ($is_modal_rendering) {
// focus search input field & register to the event emitted when reminders have changed
?>
setTimeout(() => {
jQuery('#<?php echo $wrapper_id; ?>').find('input[name="booking_key"]').focus();
}, 400);
var vbo_widget_bookdets_watch_reminders_fn = (e) => {
if (!e || !e.detail || !e.detail.hasOwnProperty('bid') || !e.detail['bid']) {
return;
}
let booking_element = jQuery('#<?php echo $wrapper_id; ?>').find('[data-bookingid]').first();
if (booking_element.length && booking_element.attr('data-bookingid') == e.detail['bid']) {
// reload current booking details
vboWidgetBookDetsSearch('<?php echo $wrapper_id; ?>');
}
};
document.addEventListener('vbo_reminders_changed', vbo_widget_bookdets_watch_reminders_fn);
document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', (e) => {
document.removeEventListener('vbo_reminders_changed', vbo_widget_bookdets_watch_reminders_fn);
});
// subscribe to the modal-dismissed event to emit the event for the lastly modified booking ID
document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', function() {
if (vbo_widget_book_dets_last_mod_bid) {
// emit the event with data for anyone who is listening to it
VBOCore.emitEvent('vbo_booking_modified', {
bid: vbo_widget_book_dets_last_mod_bid
});
}
});
<?php
}
?>
});
</script>
<?php
}
/**
* Loads a list of Channel Manager notifications and channels for the given booking.
*
* @param array $booking the current booking record.
*
* @return array list of notifications and channels involved.
*/
protected function loadChannelManagerNotifications(array $booking)
{
$dbo = JFactory::getDbo();
$channels = [];
$q = $dbo->getQuery(true)
->select('*')
->from($dbo->qn('#__vikchannelmanager_notifications'))
->where($dbo->qn('from') . ' = ' . $dbo->q('VCM'))
->where($dbo->qn('idordervb') . ' = ' . (int)$booking['id'])
->order($dbo->qn('ts') . ' DESC');
$dbo->setQuery($q);
$parents = $dbo->loadAssocList();
foreach ($parents as $k => $parent) {
$q = $dbo->getQuery(true)
->select($dbo->qn(['type', 'cont', 'channel']))
->from($dbo->qn('#__vikchannelmanager_notification_child'))
->where($dbo->qn('id_parent') . ' = ' . (int)$parent['id'])
->order($dbo->qn('id') . ' ASC');
$dbo->setQuery($q);
$children = $dbo->loadAssocList();
// set children notifications by grouping them under each channel
$parents[$k]['children'] = [];
// fetch the details for each channel involved
foreach ($children as $ind => $child) {
if (empty($child['channel'])) {
continue;
}
$channel = VikChannelManager::getChannel($child['channel']);
if (!$channel) {
// we don't want to list a notification with no channel details
unset($children[$ind]);
continue;
}
// set channel details
$channels[$child['channel']] = $channel;
if (!isset($parents[$k]['children'][$child['channel']])) {
// open channel container
$parents[$k]['children'][$child['channel']] = [];
}
// push channel notification details
$parents[$k]['children'][$child['channel']][] = $child;
}
if (!$children) {
// we don't want a notification with no children (channels notified)
unset($parents[$k]);
continue;
}
}
if (!$channels) {
// unset all notifications in case of no channel details
$parents = [];
}
return [array_values($parents), $channels];
}
}