File "notifications_center.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/notifications_center.php
File size: 59.23 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* @package VikBooking
* @subpackage com_vikbooking
* @author Alessio Gaggii - E4J srl
* @copyright Copyright (C) 2024 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 "notifications center".
*
* @since 1.16.8 (J) - 1.6.8 (WP)
*/
class VikBookingAdminWidgetNotificationsCenter extends VikBookingAdminWidget
{
/**
* The instance counter of this widget.
*
* @var int
*/
protected static $instance_counter = -1;
/**
* The number of notifications to show per page.
*
* @var int
*/
protected $records_per_page = 15;
/**
* The maximum number of groups to display.
*
* @var int
*/
protected $max_groups = 6;
/**
* The total number of skeleton loading elements.
*
* @var int
*/
protected $tot_skeletons = 4;
/**
* The distance threshold in pixels between the current scroll
* position and the end of the list for triggering the loading
* of a next page within an infinite scroll mechanism.
*
* @var int
*/
protected $px_distance_threshold = 140;
/**
* Class constructor will define the widget name and identifier.
*/
public function __construct()
{
// call parent constructor
parent::__construct();
$this->widgetName = JText::translate('VBO_W_NOTIFSCENTER_TITLE');
$this->widgetDescr = JText::translate('VBO_W_NOTIFSCENTER_DESCR');
$this->widgetId = basename(__FILE__, '.php');
$this->widgetIcon = '<i class="' . VikBookingIcons::i('bell') . '"></i>';
$this->widgetStyleName = 'light-orange';
}
/**
* This widget monitors the latest notification record ID to schedule
* periodic watch data in order to be able to update the badge counter.
*
* @return object
*/
public function preload()
{
// JS lang defs
JText::script('VBO_NOMORE_NOTIFS');
JText::script('VBO_NO_NOTIFS');
// load assets for datepicker
$this->vbo_app->loadDatePicker();
// count the number of unread notifications
$watch_data = new stdClass;
$watch_data->badge_count = (new VBONotificationCenter)
->countUnread();
return $watch_data;
}
/**
* Checks for new notifications by using the previous preloaded watch-data.
* This widget will actually never dispatch any notifications, but only events.
*
* @param ?VBONotificationWatchdata $watch_data the preloaded watch-data object.
*
* @return array data object to watch next and notifications array.
*
* @see preload()
*/
public function getNotifications(?VBONotificationWatchdata $watch_data = null)
{
// default empty values
$watch_next = null;
$notifications = [];
if (!$watch_data) {
return [$watch_next, $notifications];
}
// get the number of unread notifications
$unread = (new VBONotificationCenter)
->countUnread();
// build the next watch data for this widget
$watch_next = new stdClass;
$watch_next->badge_count = $unread;
// no notifications to dispatch ever, we simply update the next watch data
return [$watch_next, $notifications];
}
/**
* Checks for new events to be dispatched by using the previous preloaded watch-data.
*
* @param ?VBONotificationWatchdata $watch_data the preloaded watch-data object.
*
* @return array list of event objects to dispatch, if any.
*
* @see preload()
*/
public function getNotificationEvents(?VBONotificationWatchdata $watch_data = null)
{
if (!$watch_data) {
return [];
}
// check the number of unread notifications
$unread = (new VBONotificationCenter)
->countUnread();
if ((int) $watch_data->get('badge_count', 0) == $unread) {
// nothing has changed
return [];
}
// return the notification events to dispatch
return [
'vbo-badge-count' => [
'badge_count' => $unread,
],
];
}
/**
* Custom method for this widget only to load the notifications from a new group.
* 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.
*
* @return array
*/
public function loadGroupNotifications()
{
$input = JFactory::getApplication()->input;
$wrapper = $input->getString('wrapper', '');
$group = $input->getString('group', '');
$from_date = $input->getString('from_date', '');
$to_date = $input->getString('to_date', '');
$unread = $input->getBool('unread', false);
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// build query filters
$filters = [];
if ($group) {
$filters['group'] = $group;
}
if ($from_date || $to_date) {
// make the filter a non-scalar value
$filters['createdon'] = [];
if ($from_date) {
// push filter with operand
$filters['createdon'][] = [
'operand' => '>=',
'value' => $from_date . ' 00:00:00',
];
}
if ($to_date) {
// push filter with operand
$filters['createdon'][] = [
'operand' => '<=',
'value' => $to_date . ' 23:59:59',
];
}
}
if ($unread) {
// make the filter a non-scalar value
$filters['read'] = [
[
'operand' => '=',
'value' => '0',
],
];
}
// get and build the latest notifications for the requested group
$html_content = $this->buildNotificationsHTML(
$notifCenter->loadNotifications(0, $this->records_per_page, $filters)
);
// return an associative array of values
return [
'html' => $html_content,
'pages_count' => ceil($notifCenter->countFoundNotifications() / $this->records_per_page),
];
}
/**
* Custom method for this widget only to load the next page of notifications.
* 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.
*
* @return array
*/
public function loadNextNotifications()
{
$input = JFactory::getApplication()->input;
$wrapper = $input->getString('wrapper', '');
$group = $input->getString('group', '');
$from_date = $input->getString('from_date', '');
$to_date = $input->getString('to_date', '');
$unread = $input->getBool('unread', false);
$page_num = $input->getUInt('page_num', 1);
$page_num = $page_num ?: 1;
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// build query filters
$filters = [];
if ($group) {
$filters['group'] = $group;
}
if ($from_date || $to_date) {
// make the filter a non-scalar value
$filters['createdon'] = [];
if ($from_date) {
// push filter with operand
$filters['createdon'][] = [
'operand' => '>=',
'value' => $from_date . ' 00:00:00',
];
}
if ($to_date) {
// push filter with operand
$filters['createdon'][] = [
'operand' => '<=',
'value' => $to_date . ' 23:59:59',
];
}
}
if ($unread) {
// make the filter a non-scalar value
$filters['read'] = [
[
'operand' => '=',
'value' => '0',
],
];
}
// determine the query limit start
$lim_start = ($page_num - 1) * $this->records_per_page;
// get and build the latest notifications for the requested group
$html_content = $this->buildNotificationsHTML(
$notifCenter->loadNotifications($lim_start, $this->records_per_page, $filters),
($page_num - 1)
);
// return an associative array of values
return [
'html' => $html_content,
'page_number' => $page_num,
'pages_count' => ceil($notifCenter->countFoundNotifications() / $this->records_per_page),
];
}
/**
* Custom method for this widget only to mark some/all notifications as read.
* 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.
*
* @return array
*/
public function markNotificationsRead()
{
$input = JFactory::getApplication()->input;
$notification_ids = $input->getUInt('notif_ids', [], 'array');
$mark_all = $input->getUInt('mark_all', 0);
if (!$notification_ids && !$mark_all) {
// do not proceed or all notifications would be marked as read
VBOHttpDocument::getInstance()->close(400, 'No notification IDs to mark as read');
}
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// update the given notification IDs as read and obtain the groups involved
$groups = $notifCenter->readNotifications($notification_ids);
if (!$groups) {
VBOHttpDocument::getInstance()->close(404, 'No notifications found for marking as read');
}
// build a list of badge counters per group
$group_badge_counters = [];
foreach ($groups as $group) {
// determine the group key for dispatching the dedicated event
$group_key = 'vbo-badge-count' . ($group ? '-' . $group : '');
// count the unread notifications for this group identifier
$group_badge_counters[$group_key] = $notifCenter->countUnread($group ?: '');
}
// return an associative array of group-badge counters
return $group_badge_counters;
}
/**
* Custom method for this widget only to count the unread notifications per group.
* 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.
*
* @return array
*/
public function countUnreadNotifications()
{
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// return an associative array of group-badge counters
return [
'vbo-badge-count' => $notifCenter->countUnread(),
];
}
/**
* Custom method for this widget only to read some criteria-matching notifications.
* 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.
*
* @return array
*/
public function readMatchingNotifications()
{
$input = JFactory::getApplication()->input;
$criteria = $input->get('criteria', [], 'array');
$read_count = 0;
if (is_array($criteria) && $criteria) {
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// read the matching notifications
$read_count = $notifCenter->readMatchingNotifications($criteria);
}
return [
'read_count' => $read_count,
];
}
/**
* Main method to invoke the widget.
*
* @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-notifscenter-' . $wrapper_instance;
// check permissions
$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
if (!$vbo_auth_bookings) {
// permissions are not met
return;
}
// check multitask data
$in_menu = false;
$suggest_push = false;
$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();
}
/**
* Check the MultitaskOptions to see if the widget is rendered within
* the admin-menu through the Notifications Center trigger button.
*/
$in_menu = (bool) $this->options()->get('inMenu');
$suggest_push = $in_menu && $this->options()->get('suggestPush');
}
// get minimum and maximum dates for datepicker filters
list($mindate, $maxdate) = $this->getMinDatesNotifications();
$mindate = empty($mindate) ? time() : $mindate;
$maxdate = empty($maxdate) ? $mindate : $maxdate;
// access the notification-center object
$notifCenter = new VBONotificationCenter;
// load the latest notifications
$notifications = $notifCenter->loadNotifications(0, $this->records_per_page);
// immediately count the number of pages to show all notifications
$pages_count = ceil($notifCenter->countFoundNotifications() / $this->records_per_page);
?>
<div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper<?php echo $in_menu ? ' vbo-notifications-center-inmenu-widget' : ''; ?>" 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 class="vbo-admin-widget-head-commands">
<div class="vbo-reportwidget-commands">
<?php
if ($suggest_push) {
// suggest to subscribe to push notifications
?>
<div class="vbo-widget-notifscenter-suggest-push">
<button class="vbo-widget-notifscenter-suggest-push-btn vbo-suggest-notifications-btn" type="button"><?php VikBookingIcons::e('bell', 'can-shake') ?></button>
</div>
<?php
}
?>
<div class="vbo-reportwidget-commands-main" style="display: none;">
<div class="vbo-reportwidget-command-dates">
<div class="vbo-reportwidget-period-name"><?php echo JText::translate('VBNEWRESTRICTIONDATERANGE'); ?></div>
<div class="vbo-widget-notifscenter-filter-info"></div>
</div>
</div>
<div class="vbo-reportwidget-command-dots">
<span class="vbo-widget-command-togglefilters vbo-widget-notifscenter-togglefilters" onclick="vboWidgetNotifsCenterToggleFilters('<?php echo $wrapper_id; ?>');"><?php VikBookingIcons::e('ellipsis-v'); ?></span>
</div>
</div>
<div class="vbo-reportwidget-filters">
<div class="vbo-reportwidget-filter vbo-widget-notifscenter-markread">
<span class="vbo-widget-notifscenter-read-all"><?php echo JText::translate('VBO_MARK_ALL_READ'); ?></span>
</div>
<div class="vbo-reportwidget-filter vbo-widget-notifscenter-onlyunread">
<span class="vbo-widget-notifscenter-filter-lbl"><?php echo JText::translate('VBO_ONLY_UNREAD'); ?></span>
<span class="vbo-widget-notifscenter-filter-val"><?php echo $this->vbo_app->printYesNoButtons('onlyunread', JText::translate('VBYES'), JText::translate('VBNO'), 0, 1, 0); ?></span>
</div>
<div class="vbo-reportwidget-filter">
<span class="vbo-reportwidget-datepicker">
<?php VikBookingIcons::e('calendar', 'vbo-widget-notifscenter-caltrigger'); ?>
<input type="text" class="vbo-notifscenter-dtpicker-from" value="" placeholder="<?php echo JHtml::fetch('esc_attr', JText::translate('VBNEWRESTRICTIONDFROMRANGE')); ?>" />
</span>
</div>
<div class="vbo-reportwidget-filter">
<span class="vbo-reportwidget-datepicker">
<?php VikBookingIcons::e('calendar', 'vbo-widget-notifscenter-caltrigger'); ?>
<input type="text" class="vbo-notifscenter-dtpicker-to" value="" placeholder="<?php echo JHtml::fetch('esc_attr', JText::translate('VBNEWRESTRICTIONDTORANGE')); ?>" />
</span>
</div>
<div class="vbo-reportwidget-filter vbo-reportwidget-filter-confirm vbo-widget-notifscenter-filter-confirm">
<button type="button" class="btn btn-secondary" onclick="vboWidgetNotifsCenterClearFilters('<?php echo $wrapper_id; ?>');" title="<?php echo JHtml::fetch('esc_attr', JText::translate('VBOSIGNATURECLEAR')); ?>"><?php VikBookingIcons::e('broom'); ?></button>
<button type="button" class="btn vbo-config-btn" onclick="vboWidgetNotifsCenterApplyFilters('<?php echo $wrapper_id; ?>');"><?php echo JText::translate('VBADMINNOTESUPD'); ?></button>
</div>
</div>
</div>
</div>
</div>
<div class="vbo-widget-notifscenter-wrap">
<div class="vbo-widget-notifscenter-groups">
<?php
foreach ($notifCenter->getGroups() as $k => $group) {
$group_badge_val = $group['badge_count'] ?: '';
$group_badge_node = $group_badge_val ? '<span class="vbo-widget-notifscenter-group-badge">' . $group_badge_val . '</span>' : '';
?>
<div class="vbo-widget-notifscenter-group<?php echo ($k === 0 ? ' vbo-widget-notifscenter-group-active' : ''); ?>">
<span class="vbo-widget-notifscenter-group-name" data-badge-count="<?php echo $group_badge_val; ?>" data-group-id="<?php echo $group['id']; ?>"><?php echo $group['name'] . $group_badge_node; ?></span>
</div>
<?php
if (($k + 1) >= $this->max_groups) {
break;
}
}
?>
</div>
<div class="vbo-widget-notifscenter-list" data-group-id="" data-page-number="1" data-pages-count="<?php echo $pages_count; ?>">
<?php
// output all notifications
echo $this->buildNotificationsHTML($notifications);
?>
</div>
<div class="vbo-widget-notifscenter-loadmore-hidden" style="display: none;">
<button type="button" class="btn vbo-widget-notifscenter-loadmore-manual"><?php VikBookingIcons::e('chevron-right'); ?></button>
</div>
</div>
<?php
if (!$notifications) {
?>
<div class="vbo-widget-notifscenter-loadmore-info">
<p><?php echo JText::translate('VBO_NO_NOTIFS'); ?></p>
</div>
<?php
}
?>
</div>
<?php
if (static::$instance_counter === 0 || $is_ajax) {
/**
* Print the JS code only once for all instances of this widget.
*/
?>
<script type="text/javascript">
/**
* @var array
*/
var vbo_widget_nc_badge_evs = [];
/**
* Toggle filters.
*/
function vboWidgetNotifsCenterToggleFilters(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
widget_instance.find('.vbo-reportwidget-filters').toggle();
}
/**
* Datepicker dates selection.
*/
function vboWidgetNotifsCenterCheckDates(selectedDate, inst) {
if (selectedDate === null || inst === null) {
return;
}
var cur_from_date = jQuery(this).val();
if (jQuery(this).hasClass('vbo-notifscenter-dtpicker-from') && cur_from_date.length) {
var nowstart = jQuery(this).datepicker('getDate');
var nowstartdate = new Date(nowstart.getTime());
jQuery('.vbo-notifscenter-dtpicker-to').datepicker('option', {minDate: nowstartdate});
}
}
/**
* Applies the selected filters and reloads the notifications.
*/
function vboWidgetNotifsCenterApplyFilters(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// get the notifications list element
let notificationsList = widget_instance
.find('.vbo-widget-notifscenter-list');
// find the currently active group
let group = notificationsList
.attr('data-group-id');
group = group ? group : '';
// get the current filter values
let from_date = widget_instance
.find('.vbo-notifscenter-dtpicker-from')
.val();
let to_date = widget_instance
.find('.vbo-notifscenter-dtpicker-to')
.val();
let only_unread = widget_instance
.find('.vbo-widget-notifscenter-onlyunread')
.find('input[type="checkbox"]')
.prop('checked');
// apply the filter attributes
notificationsList
.attr('data-date-from', from_date);
notificationsList
.attr('data-date-to', to_date);
notificationsList
.attr('data-only-unread', (only_unread ? '1' : ''));
// update filter information node
if (from_date || to_date) {
let dfilter_info = '';
if (from_date == to_date) {
dfilter_info = from_date;
} else {
dfilter_info = from_date + ' ' + (from_date && to_date ? '- ' : '') + to_date;
}
widget_instance
.find('.vbo-widget-notifscenter-filter-info')
.html(dfilter_info);
widget_instance
.find('.vbo-reportwidget-commands-main')
.show();
} else {
widget_instance
.find('.vbo-reportwidget-commands-main')
.hide();
widget_instance
.find('.vbo-widget-notifscenter-filter-info')
.html('');
}
// toggle filters
vboWidgetNotifsCenterToggleFilters(wrapper);
// reload the notifications for the current group
vboWidgetNotifsCenterLoadGroupNotifs(wrapper, group);
}
/**
* Clears the current filters and reloads the notifications.
*/
function vboWidgetNotifsCenterClearFilters(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// get the notifications list element
let notificationsList = widget_instance
.find('.vbo-widget-notifscenter-list');
// find the currently active group
let group = notificationsList
.attr('data-group-id');
group = group ? group : '';
// unset current filters
widget_instance
.find('.vbo-notifscenter-dtpicker-from')
.val('');
widget_instance
.find('.vbo-notifscenter-dtpicker-to')
.val('');
widget_instance
.find('.vbo-widget-notifscenter-onlyunread')
.find('input[type="checkbox"]')
.prop('checked', false);
// update the filter attributes
notificationsList
.attr('data-date-from', '');
notificationsList
.attr('data-date-to', '');
notificationsList
.attr('data-only-unread', '');
// update filter information node
widget_instance
.find('.vbo-reportwidget-commands-main')
.hide();
widget_instance
.find('.vbo-widget-notifscenter-filter-info')
.html('');
// toggle filters
vboWidgetNotifsCenterToggleFilters(wrapper);
// reload the notifications for the current group
vboWidgetNotifsCenterLoadGroupNotifs(wrapper, group);
}
/**
* Returns the skeletons loading HTML.
*/
function vboWidgetNotifsCenterGetSkeletons() {
var skeletons = '<div class="vbo-dashboard-guests-latest vbo-widget-notifscenter-skeletons">' + "\n";
for (var i = 0; i < <?php echo $this->tot_skeletons; ?>; i++) {
skeletons += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">' + "\n";
skeletons += ' <div class="vbo-dashboard-guest-activity-avatar">' + "\n";
skeletons += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>' + "\n";
skeletons += ' </div>' + "\n";
skeletons += ' <div class="vbo-dashboard-guest-activity-content">' + "\n";
skeletons += ' <div class="vbo-dashboard-guest-activity-content-head">' + "\n";
skeletons += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>' + "\n";
skeletons += ' </div>' + "\n";
skeletons += ' <div class="vbo-dashboard-guest-activity-content-subhead">' + "\n";
skeletons += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>' + "\n";
skeletons += ' </div>' + "\n";
skeletons += ' <div class="vbo-dashboard-guest-activity-content-info-msg">' + "\n";
skeletons += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>' + "\n";
skeletons += ' </div>' + "\n";
skeletons += ' </div>' + "\n";
skeletons += '</div>' + "\n";
}
skeletons += '</div>' + "\n";
return skeletons;
}
/**
* Prepares the loading of the notifications for a group.
*/
function vboWidgetNotifsCenterLoadGroupNotifs(wrapper, group) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// remove the currently active group
widget_instance
.find('.vbo-widget-notifscenter-group-active')
.removeClass('vbo-widget-notifscenter-group-active');
// add the active class to the clicked group
widget_instance
.find('.vbo-widget-notifscenter-group-name[data-group-id="' + group + '"]')
.closest('.vbo-widget-notifscenter-group')
.addClass('vbo-widget-notifscenter-group-active');
// make sure to hide the information block, if any
widget_instance
.find('.vbo-widget-notifscenter-loadmore-info')
.remove();
// set the new group identifier, reset page counters and populate skeletons
widget_instance
.find('.vbo-widget-notifscenter-list')
.attr('data-group-id', group)
.attr('data-page-number', 1)
.attr('data-pages-count', 1)
.html(vboWidgetNotifsCenterGetSkeletons());
// reload notifications for the current group
vboWidgetNotifsCenterReloadNotifs(wrapper);
}
/**
* Reloads the notifications for the current group.
*/
function vboWidgetNotifsCenterReloadNotifs(wrapper) {
var notificationsList = document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-list');
if (!notificationsList) {
throw new Error('Could not find notifications list element');
}
// remove scroll listener for the current list
if (notificationsList.wrapperId) {
// unregister the infinite scroll
notificationsList
.removeEventListener('scroll', vboWidgetNotifsCenterInfiniteScroll);
}
// get the current group
var group_id = notificationsList.getAttribute('data-group-id');
// get the search filters, if any
var from_date = notificationsList.getAttribute('data-date-from');
var to_date = notificationsList.getAttribute('data-date-to');
var only_unread = notificationsList.getAttribute('data-only-unread');
// the widget method to call
var call_method = 'loadGroupNotifications';
// make a request to load the notifications for the current group
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
group: group_id,
from_date: from_date,
to_date: to_date,
unread: only_unread,
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 the reloaded notifications for the current group and set page infos
notificationsList
.setAttribute('data-page-number', 1);
notificationsList
.setAttribute('data-pages-count', obj_res[call_method]['pages_count']);
notificationsList
.innerHTML = obj_res[call_method]['html'];
if (!obj_res[call_method]['html']) {
// print message for no notifications found
notificationsList
.innerHTML = '<div class="vbo-widget-notifscenter-loadmore-info"><p>' + Joomla.JText._('VBO_NO_NOTIFS') + '</p></div>';
} else {
// schedule setup functions
setTimeout(() => {
// set up infinite scroll listener for the new list
vboWidgetNotifsCenterSetupInfiniteScroll(wrapper);
// set up notifications click listeners
vboWidgetNotifsCenterRegisterClickListeners(wrapper);
}, 100);
}
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// display the error
alert(error.responseText);
// remove loading skeletons
notificationsList
.querySelector('.vbo-widget-notifscenter-skeletons')
.remove();
}
);
}
/**
* Loads the next page of notifications.
*/
function vboWidgetNotifsCenterLoadNextPage(wrapper) {
var notificationsList = document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-list');
if (!notificationsList) {
throw new Error('Could not find notifications list element');
}
// ensure we've got other pages to load
var pageNumber = parseInt(notificationsList.getAttribute('data-page-number')) || 1;
var pagesCount = parseInt(notificationsList.getAttribute('data-pages-count')) || 1;
if (pageNumber >= pagesCount) {
// no more pages available, abort
return;
}
// load the next page of notifications
// append loading skeletons
notificationsList
.insertAdjacentHTML('beforeend', vboWidgetNotifsCenterGetSkeletons());
// get the current group
var group_id = notificationsList.getAttribute('data-group-id');
// get the search filters, if any
var from_date = notificationsList.getAttribute('data-date-from');
var to_date = notificationsList.getAttribute('data-date-to');
var only_unread = notificationsList.getAttribute('data-only-unread');
// the widget method to call
var call_method = 'loadNextNotifications';
// make a request to load the next page of notifications for the current group
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
group: group_id,
from_date: from_date,
to_date: to_date,
unread: only_unread,
page_num: parseInt(pageNumber) + 1,
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;
}
// remove loading skeletons
notificationsList
.querySelector('.vbo-widget-notifscenter-skeletons')
.remove();
// append HTML with the new notifications for the current group and set page infos
notificationsList
.setAttribute('data-page-number', obj_res[call_method]['page_number']);
notificationsList
.setAttribute('data-pages-count', obj_res[call_method]['pages_count']);
notificationsList
.insertAdjacentHTML('beforeend', obj_res[call_method]['html']);
// turn custom property off for the page loading
notificationsList.pageLoading = false;
// set up notifications click listeners for the new notifications read
vboWidgetNotifsCenterRegisterClickListeners(wrapper);
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// display the error
alert(error.responseText);
// turn custom property off for the page loading
notificationsList.pageLoading = false;
// remove loading skeletons
notificationsList
.querySelector('.vbo-widget-notifscenter-skeletons')
.remove();
}
);
}
/**
* Setups the infinite scroll loading.
*/
function vboWidgetNotifsCenterSetupInfiniteScroll(wrapper) {
var notificationsList = document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-list');
if (!notificationsList) {
throw new Error('Could not find notifications list element');
}
// ensure we've got more pages to load
var pageNumber = parseInt(notificationsList.getAttribute('data-page-number')) || 1;
var pagesCount = parseInt(notificationsList.getAttribute('data-pages-count')) || 1;
if (pageNumber >= pagesCount) {
// no pagination needed
return;
}
// get wrapper dimensions
var listViewHeight = notificationsList.offsetHeight;
var listGlobHeight = notificationsList.scrollHeight;
var listScrollTop = notificationsList.scrollTop;
if (listViewHeight >= listGlobHeight) {
// no scrolling detected, show manual loading
document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-loadmore-hidden')
.style
.display = 'block';
return;
}
// inject custom property to identify the wrapper ID
notificationsList.wrapperId = wrapper;
// register infinite scroll event handler
notificationsList
.addEventListener('scroll', vboWidgetNotifsCenterInfiniteScroll);
}
/**
* Infinite scroll event handler.
*/
function vboWidgetNotifsCenterInfiniteScroll(e) {
// access the injected wrapper ID property
var wrapper = e.currentTarget.wrapperId;
if (!wrapper) {
return;
}
// register throttling callback
VBOCore.throttleTimer(() => {
// access the current notifications list
var notificationsList = document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-list');
// ensure we've got more pages to load
var pageNumber = parseInt(notificationsList.getAttribute('data-page-number')) || 1;
var pagesCount = parseInt(notificationsList.getAttribute('data-pages-count')) || 1;
if (pageNumber >= pagesCount) {
// unregister the infinite scroll
notificationsList
.removeEventListener('scroll', vboWidgetNotifsCenterInfiniteScroll);
// display message for all notifications loaded
if (pagesCount > 1) {
let widget_content = document
.querySelector('#' + wrapper);
// hide the eventually displayed manual loading
widget_content
.querySelector('.vbo-widget-notifscenter-loadmore-hidden')
.style
.display = 'none';
if (!widget_content.querySelector('.vbo-widget-notifscenter-loadmore-info')) {
// append the message stating that all notifications have been displayed
let infoDiv = document
.createElement('div');
infoDiv.classList
.add('vbo-widget-notifscenter-loadmore-info');
let infoTxt = document
.createElement('p');
infoTxt.append(Joomla.JText._('VBO_NOMORE_NOTIFS'));
infoDiv.append(infoTxt);
widget_content.append(infoDiv);
}
}
return;
}
// make sure the loading of a next page isn't running
if (notificationsList.pageLoading) {
// abort
return;
}
// get wrapper dimensions
var listViewHeight = notificationsList.offsetHeight;
var listGlobHeight = notificationsList.scrollHeight;
var listScrollTop = notificationsList.scrollTop;
if (!listScrollTop || listViewHeight >= listGlobHeight) {
// no scrolling detected at all
return;
}
// calculate missing distance to the end of the list
var listEndDistance = listGlobHeight - (listViewHeight + listScrollTop);
if (listEndDistance < <?php echo $this->px_distance_threshold; ?>) {
// inject custom property to identify a next page is loading
notificationsList.pageLoading = true;
// load the next page of notifications
vboWidgetNotifsCenterLoadNextPage(wrapper);
}
}, 500);
}
/**
* Registers the click listener on all the eligible notification entries.
*/
function vboWidgetNotifsCenterRegisterClickListeners(wrapper) {
var notifications = document
.querySelector('#' + wrapper)
.querySelector('.vbo-widget-notifscenter-list')
.querySelectorAll('.vbo-widget-notifscenter-notif-wrap:not([data-listening])');
notifications.forEach((notification) => {
// immediately set attribute flag with listening enabled
notification.setAttribute('data-listening', 1);
// check if the notification has to be read
if (notification.classList.contains('vbo-widget-notifscenter-notif-unread')) {
// add click event listener to mark the notification as read
notification.addEventListener('click', vboWidgetNotifsCenterReadNotification);
}
// get main attributes
let idorder = notification.getAttribute('data-idorder');
let idorderota = notification.getAttribute('data-idorderota');
if (idorder || idorderota) {
// add click event listener to open the booking details admin-widget
notification.addEventListener('click', (e) => {
if (e.target && e.target.classList.contains('vbo-notifscenter-cta-btn')) {
// do nothing on a call-to-action button
return;
}
// this event listener will never need to be removed
VBOCore.handleDisplayWidgetNotification({widget_id: 'booking_details'}, {
bid: idorder || idorderota,
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',
},
});
});
}
});
}
/**
* Read notification click event handler.
*/
function vboWidgetNotifsCenterReadNotification(e) {
let notification = e.currentTarget || null;
if (!notification) {
throw new Error('This function requires an event target');
}
// immediately remove the click listener to read this notification
notification.removeEventListener('click', vboWidgetNotifsCenterReadNotification);
// add the "read" class
notification.classList.add('vbo-widget-notifscenter-notif-read');
// remove the "unread" class
notification.classList.remove('vbo-widget-notifscenter-notif-unread');
// the widget method to call
var call_method = 'markNotificationsRead';
// execute the request to update the notification as read
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
notif_ids: [notification.getAttribute('data-notif-id')],
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;
}
// build events data object
let events_data = {};
// dispatch the events for the groups returned to update the badge counters
for (var ev_name in obj_res[call_method]) {
if (!obj_res[call_method].hasOwnProperty(ev_name)) {
continue;
}
if (ev_name.indexOf('vbo-badge-count') !== 0) {
// not a valid event name
continue;
}
// build the event data object
let event_data = {
badge_count: obj_res[call_method][ev_name],
};
// dispatch the group badge count update event
VBOCore.emitEvent(ev_name, event_data);
// set event data property to object
events_data[ev_name] = event_data;
}
// post message onto broadcast channel for any other browsing context
if (VBOCore.broadcast_watch_events) {
// this will trigger the events on any other browsing context
VBOCore.broadcast_watch_events.postMessage(events_data);
}
} catch (err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// silently log the error
console.error(error.responseText);
}
);
}
/**
* Mark all notifications as read.
*/
function vboWidgetNotifsCenterReadAllNotifs(wrapper) {
// toggle filters
vboWidgetNotifsCenterToggleFilters(wrapper);
// remove the unread class from any occurrence
document
.querySelectorAll('.vbo-widget-notifscenter-notif-wrap.vbo-widget-notifscenter-notif-unread')
.forEach((notification) => {
notification.classList.add('vbo-widget-notifscenter-notif-read');
notification.classList.remove('vbo-widget-notifscenter-notif-unread');
});
// the widget method to call
var call_method = 'markNotificationsRead';
// execute the request to update the notification as read
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
notif_ids: [],
mark_all: 1,
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;
}
// dispatch the events for the groups returned to update the badge counters
for (var ev_name in obj_res[call_method]) {
if (!obj_res[call_method].hasOwnProperty(ev_name)) {
continue;
}
if (ev_name.indexOf('vbo-badge-count') !== 0) {
// not a valid event name
continue;
}
// dispatch the group badge count update event
VBOCore.emitEvent(ev_name, {
badge_count: obj_res[call_method][ev_name],
});
}
} catch (err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// silently log the error
console.error(error.responseText);
}
);
}
/**
* Registers the event listeners for updating the group badge counters.
*/
function vboWidgetNotifsCenterRegisterGroupBadgeListeners(wrapper) {
var groups = document
.querySelector('#' + wrapper)
.querySelectorAll('.vbo-widget-notifscenter-group-name');
groups.forEach((group) => {
let group_id = group.getAttribute('data-group-id');
let group_event = 'vbo-badge-count' + (group_id ? '-' + group_id : '');
/**
* Register the listener with a precise handler so that it can be removed upon destruction,
* unlike the global event "vbo-badge-count" for the in-menu Notifications Center handler.
*/
document.addEventListener(group_event, vboWidgetNotifsCenterUpdateGroupBadge);
// push event name for later destruction
vbo_widget_nc_badge_evs.push(group_event);
});
}
/**
* Update group badge counter event handler.
*/
function vboWidgetNotifsCenterUpdateGroupBadge(e) {
if (!e || !e.detail || !e.detail.hasOwnProperty('badge_count') || isNaN(e.detail['badge_count'])) {
return;
}
// get the current counter
let badge_count = parseInt(e.detail['badge_count']);
// get the event type to identify the group ID
let group_nm_rgx = new RegExp(/^vbo-badge-count-?/);
let group_id = e.type.replace(group_nm_rgx, '');
// parse all group badges of this type-ID in the document
var groups = document
.querySelectorAll('.vbo-widget-notifscenter-group-name[data-group-id="' + group_id + '"]');
groups.forEach((group) => {
// find the child node for better supporting large numbers
let group_badge_element = group.querySelector('.vbo-widget-notifscenter-group-badge');
if (badge_count <= 0) {
// no notifications to be read
group.setAttribute('data-badge-count', '');
// delete the badge element node, if any
if (group_badge_element) {
group_badge_element.remove();
}
} else {
// update badge counter
group.setAttribute('data-badge-count', badge_count);
// append or update badge element node
if (group_badge_element) {
// update badge element node
group_badge_element.innerText = badge_count;
} else {
// append badge element node
let group_badge_node = document.createElement('span');
group_badge_node.className = 'vbo-widget-notifscenter-group-badge';
group_badge_node.innerText = badge_count;
group.appendChild(group_badge_node);
}
}
});
}
/**
* Removes the group badge update event listeners.
*/
function vboWidgetNotifsCenterRemoveGroupBadgeListeners(e) {
e.stopPropagation();
// remove event listener so that it will be re-registered
document.removeEventListener(<?php echo $js_modal_id ? "VBOCore.widget_modal_dismissed + '{$js_modal_id}'" : "'vbo_widget_nc_destroy'"; ?>, vboWidgetNotifsCenterRemoveGroupBadgeListeners);
if (vbo_widget_nc_badge_evs && Array.isArray(vbo_widget_nc_badge_evs)) {
vbo_widget_nc_badge_evs.forEach((ev_name, index) => {
// remove event listener for updating the group badge
document.removeEventListener(ev_name, vboWidgetNotifsCenterUpdateGroupBadge);
// remove the current element from the array
vbo_widget_nc_badge_evs.splice(index, 1);
});
}
}
/**
* Register event listener that runs upon widget destruction.
*/
document.addEventListener(<?php echo $js_modal_id ? "VBOCore.widget_modal_dismissed + '{$js_modal_id}'" : "'vbo_widget_nc_destroy'"; ?>, vboWidgetNotifsCenterRemoveGroupBadgeListeners);
</script>
<?php
}
?>
<script type="text/javascript">
jQuery(function() {
// listen to the click event on the group names
jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-notifscenter-group').on('click', function() {
// get clicked group identifier
var group_id = jQuery(this)
.find('.vbo-widget-notifscenter-group-name')
.attr('data-group-id');
// load the notifications for the clicked group, even if it's active
vboWidgetNotifsCenterLoadGroupNotifs('<?php echo $wrapper_id; ?>', group_id);
});
// listen to the "mark all as read" command
jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-notifscenter-read-all').on('click', function() {
vboWidgetNotifsCenterReadAllNotifs('<?php echo $wrapper_id; ?>');
});
// listen to the manual load-more button
jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-widget-notifscenter-loadmore-manual').on('click', function() {
// load the next page of notifications
vboWidgetNotifsCenterLoadNextPage('<?php echo $wrapper_id; ?>');
});
// set up infinite scroll loading
vboWidgetNotifsCenterSetupInfiniteScroll('<?php echo $wrapper_id; ?>');
// set up notifications click listeners
vboWidgetNotifsCenterRegisterClickListeners('<?php echo $wrapper_id; ?>');
// set up group badge update listeners
vboWidgetNotifsCenterRegisterGroupBadgeListeners('<?php echo $wrapper_id; ?>');
// render datepicker calendars for date filters
jQuery('#<?php echo $wrapper_id; ?>').find('.vbo-notifscenter-dtpicker-from, .vbo-notifscenter-dtpicker-to').datepicker({
minDate: "<?php echo date('Y-m-d', $mindate); ?>",
maxDate: "<?php echo date('Y-m-d', $maxdate); ?>",
yearRange: "<?php echo date('Y', $mindate); ?>:<?php echo date('Y', $maxdate); ?>",
changeMonth: true,
changeYear: true,
dateFormat: "yy-mm-dd",
onSelect: vboWidgetNotifsCenterCheckDates
});
// triggering for datepicker calendar icons
jQuery('i.vbo-widget-notifscenter-caltrigger').click(function() {
var jdp = jQuery(this).parent().find('input.hasDatepicker');
if (jdp.length) {
jdp.focus();
}
});
if (<?php echo $suggest_push ? 'true' : 'false'; ?>) {
// suggest to subscribe to push notifications
VBOCore.suggestNotifications('.vbo-widget-notifscenter-suggest-push-btn');
}
});
</script>
<?php
}
/**
* Given a list of notification objects, builds and returns the HTML rendering code.
*
* @param array $notifications list of notification objects to render.
* @param int $page_num optional page number.
*
* @return string
*/
protected function buildNotificationsHTML(array $notifications, int $page_num = 0)
{
if (!$notifications) {
return '';
}
// get back-end logo URI
$backlogo = VikBooking::getBackendLogo();
// start output buffering
ob_start();
foreach ($notifications as $index => $notif) {
// obtain notification date info
$date_info = VBORemindersHelper::getInstance()->relativeDatesDiff(JHtml::fetch('date', $notif->createdon, 'Y-m-d H:i:s'));
// build relative date
$relative_dt = $date_info['relative'];
if ($date_info['past'] && ($date_info['today'] || ($date_info['yesterday'] && !$date_info['days']))) {
// recent date to be expressed as hours, minutes or seconds ago
if ($date_info['hours']) {
$rel_time_str = $date_info['hours'] . ' ' . JText::translate(($date_info['hours'] == 1 ? 'VBO_HOUR' : 'VBCONFIGONETENEIGHT'));
} elseif ($date_info['minutes']) {
$rel_time_str = $date_info['minutes'] . ' ' . JText::translate(($date_info['minutes'] == 1 ? 'VBO_MINUTE' : 'VBTRKDIFFMINS'));
} else {
$rel_time_str = $date_info['seconds'] . ' ' . JText::translate(($date_info['seconds'] == 1 ? 'VBO_SECOND' : 'VBTRKDIFFSECS'));
}
$relative_dt = JText::sprintf('VBO_REL_EXP_PAST', strtolower($rel_time_str));
} elseif ($date_info['past'] && $date_info['yesterday'] && $date_info['days'] == 1 && $date_info['hours'] < 6) {
// less than 30 hours ago
$rel_time_str = ($date_info['hours'] + 24) . ' ' . JText::translate('VBCONFIGONETENEIGHT');
$relative_dt = JText::sprintf('VBO_REL_EXP_PAST', strtolower($rel_time_str));
}
// build a human-readable date and time
if ($date_info['today']) {
$human_dtime = JHtml::fetch('date', $notif->createdon, 'H:i:s');
} else {
$human_dtime = JHtml::fetch('date', $notif->createdon, str_replace("/", $this->datesep, $this->df) . ' H:i:s');
}
// get channel logo and name
$channel_logo = '';
$channel_name = '';
if (!empty($notif->channel)) {
$ch_logo_obj = VikBooking::getVcmChannelsLogo($notif->channel, true);
$channel_logo = is_object($ch_logo_obj) ? $ch_logo_obj->getSmallLogoURL() : '';
$channelparts = explode('_', $notif->channel);
$channel_name = isset($channelparts[1]) && strlen((string)$channelparts[1]) ? $channelparts[1] : ucwords($channelparts[0]);
}
// check if the notification group belongs to specific types
$group_website = !strcasecmp($notif->group, 'website');
$group_guests = !strcasecmp($notif->group, 'guests');
// determine the notification group icon and color
$group_badge_icon = '';
$group_badge_cnt = '';
$group_badge_cls = '';
if (!strcasecmp($notif->group, 'website')) {
$group_badge_icon = 'clipboard-list';
$group_badge_cls = 'vbo-badge-group-green';
if (in_array($notif->type, ['p0', 'pn'])) {
$group_badge_icon = 'credit-card';
} elseif ($notif->type == 'info') {
$group_badge_icon = 'bullhorn';
}
if (in_array($notif->type, ['cr', 'cw', 'ob'])) {
$group_badge_cls = 'vbo-badge-group-orange';
}
} elseif (!strcasecmp($notif->group, 'otas')) {
$group_badge_icon = 'cloud';
$group_badge_cls = 'vbo-badge-group-purple';
if (in_array($notif->type, ['po', 'vcc_balance', 'vcc_balance_updated'])) {
$group_badge_icon = 'credit-card';
}
if (in_array($notif->type, ['cc', 'ob'])) {
$group_badge_cls = 'vbo-badge-group-orange';
}
} elseif (!strcasecmp($notif->group, 'guests')) {
$group_badge_icon = 'comment-dots';
$group_badge_cls = 'vbo-badge-group-lightblue';
} elseif (!strcasecmp($notif->group, 'cm')) {
$group_badge_icon = 'bullhorn';
$group_badge_cls = 'vbo-badge-group-lightblue';
} elseif (!strcasecmp($notif->group, 'operators')) {
$group_badge_icon = 'user-tie';
$group_badge_cls = 'vbo-badge-group-orange';
if (in_array($notif->type, ['chat.newmessage'])) {
$group_badge_icon = 'comment-dots';
}
if ($notif->type == 'task.unassigned') {
$group_badge_icon = 'tasks';
$group_badge_cls = 'vbo-badge-group-red';
}
} elseif (!strcasecmp($notif->group, 'reports')) {
$group_badge_icon = 'cash-register';
if (strpos($notif->type, 'error') !== false) {
$group_badge_cls = 'vbo-badge-group-red';
} else {
$group_badge_cls = 'vbo-badge-group-green';
}
} elseif (!strcasecmp($notif->group, 'ai')) {
$group_badge_cnt = '<img class="vbo-ai-icn" src="' . VBO_ADMIN_URI . 'resources/channels/ai-icn-white.png" />';
if (strpos($notif->type, 'error') !== false) {
$group_badge_cls = 'vbo-badge-group-red';
} else {
$group_badge_cls = 'vbo-badge-group-green';
}
}
?>
<div
class="vbo-widget-notifscenter-notif-wrap vbo-widget-notifscenter-notif-<?php echo $notif->read ? 'read' : 'unread'; ?>"
data-notif-id="<?php echo $notif->id; ?>"
data-idorder="<?php echo $notif->idorder; ?>"
data-idorderota="<?php echo $notif->idorderota; ?>"
>
<div class="vbo-widget-notifscenter-notif-avatar">
<div class="vbo-customer-info-box">
<div class="vbo-customer-info-box-avatar vbo-customer-avatar-medium">
<?php
if (!empty($notif->customer_pic)) {
// use customer profile picture
?>
<span class="vbo-widget-notifscenter-cpic-zoom">
<img src="<?php echo strpos($notif->customer_pic, 'http') === 0 ? $notif->customer_pic : VBO_SITE_URI . 'resources/uploads/' . $notif->customer_pic; ?>" data-caption="<?php echo JHtml::fetch('esc_attr', (string) $notif->customer_name); ?>" decoding="async" loading="lazy" />
</span>
<?php
} elseif (!empty($notif->avatar)) {
// use notification avatar
?>
<span class="vbo-widget-notifscenter-cpic-zoom">
<img src="<?php echo strpos($notif->avatar, 'http') === 0 ? $notif->avatar : JUri::root() . $notif->avatar; ?>" decoding="async" loading="lazy" />
</span>
<?php
} elseif (!empty($channel_logo)) {
// use channel logo
?>
<span class="vbo-tooltip vbo-tooltip-top" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $channel_name); ?>">
<img src="<?php echo $channel_logo; ?>" />
</span>
<?php
} elseif (!empty($notif->customer_name)) {
// use customer initials
?>
<span>
<span class="vbo-widget-notifscenter-customer-initials"><?php echo $this->getCustomerInitials($notif->customer_name); ?></span>
</span>
<?php
} elseif (!empty($backlogo)) {
// use back-end logo
?>
<span>
<img src="<?php echo VBO_ADMIN_URI . "resources/{$backlogo}"; ?>" />
</span>
<?php
} else {
// fallback onto website icon
?>
<span><?php VikBookingIcons::e('hotel', 'vbo-dashboard-guest-activity-avatar-icon'); ?></span>
<?php
}
// take care of the group badge icon and color, if any
if ($group_badge_icon) {
?>
<span class="vbo-customer-avatar-badge <?php echo $group_badge_cls; ?>">
<?php VikBookingIcons::e($group_badge_icon); ?>
</span>
<?php
} elseif ($group_badge_cnt) {
?>
<span class="vbo-customer-avatar-badge <?php echo $group_badge_cls; ?>">
<?php echo $group_badge_cnt; ?>
</span>
<?php
}
?>
</div>
</div>
</div>
<div class="vbo-widget-notifscenter-notif-details">
<?php
if ($notif->title) {
if (preg_match("/^VBO/", $notif->title)) {
/**
* @todo to be removed on next updates.
*/
$notif->title = JText::translate($notif->title);
}
?>
<div class="vbo-widget-notifscenter-notif-head">
<span class="vbo-widget-notifscenter-notif-title"><?php echo $notif->title; ?></span>
<?php
if (!$group_guests && $notif->customer_name) {
?>
<span class="vbo-widget-notifscenter-notif-subtitle">• <?php echo $notif->customer_name; ?></span>
<?php
}
?>
</div>
<?php
}
?>
<div class="vbo-widget-notifscenter-notif-dt">
<?php
if ($notif->idorder || $notif->idorderota) {
?>
<span class="label label-info"><?php echo $notif->idorder ?: $notif->idorderota; ?></span>
<?php
}
?>
<span class="vbo-tooltip vbo-tooltip-<?php echo !$index && !$page_num ? 'bottom' : 'top'; ?>" data-tooltiptext="<?php echo JHtml::fetch('esc_attr', $human_dtime); ?>"><?php echo $relative_dt; ?></span>
</div>
<?php
if ((!$group_website || !strcasecmp($notif->type, 'info')) && $notif->summary) {
?>
<div class="vbo-widget-notifscenter-notif-summary" data-group-name="<?php echo $notif->group; ?>" data-notif-type="<?php echo $notif->type; ?>">
<span><?php echo $notif->summary; ?></span>
</div>
<?php
}
if (is_object($notif->cta_data) && (!empty($notif->cta_data->url) || !empty($notif->cta_data->widget))) {
// call-to-action data available
$cta_btn_lbl = !empty($notif->cta_data->label) ? $notif->cta_data->label : JText::translate('VBO_TAKE_ACTION');
?>
<div class="vbo-widget-notifscenter-notif-cta">
<?php
if (!empty($notif->cta_data->url)) {
// open "remote" URL
?>
<a class="btn btn-small vbo-notifscenter-cta-btn" href="<?php echo JHtml::fetch('esc_attr', $notif->cta_data->url); ?>" target="_blank"><?php echo $cta_btn_lbl; ?></a>
<?php
} elseif (!empty($notif->cta_data->widget)) {
// open a specific admin-widget with the necessary options
$def_widget_options = [
'modal_options' => [
'suffix' => 'widget_modal_inner_' . $notif->cta_data->widget,
],
];
if (is_object($notif->cta_data->widget_options) && !isset($notif->cta_data->widget_options->modal_options)) {
// inject the suffix property
$notif->cta_data->widget_options->modal_options = new stdClass;
$notif->cta_data->widget_options->modal_options->suffix = 'widget_modal_inner_' . $notif->cta_data->widget;
}
$js_options = !empty($notif->cta_data->widget_options) ? json_encode($notif->cta_data->widget_options) : json_encode($def_widget_options);
$js_command = 'VBOCore.handleDisplayWidgetNotification({widget_id: "' . $notif->cta_data->widget . '"}, ' . $js_options . ');';
?>
<button type="button" class="btn btn-small vbo-notifscenter-cta-btn" onclick="<?php echo JHtml::fetch('esc_attr', $js_command); ?>"><?php echo $cta_btn_lbl; ?></button>
<?php
}
?>
</div>
<?php
}
?>
</div>
</div>
<?php
}
// get the HTML buffer
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Returns the initials for the given full name.
*
* @param string $name the full name (first + last).
*
* @return string the capitalized name initials.
*/
protected function getCustomerInitials(string $name)
{
$parts = explode(' ', trim($name));
$first = $parts[0];
$last = end($parts);
if (function_exists('mb_substr')) {
if ($first != $last) {
return strtoupper(mb_substr($first, 0, 1, 'UTF-8') . mb_substr($last, 0, 1, 'UTF-8'));
}
return strtoupper(mb_substr($first, 0, 2, 'UTF-8'));
}
if ($first != $last) {
return strtoupper(substr($first, 0, 1) . substr($last, 0, 1));
}
return strtoupper(substr($first, 0, 2));
}
/**
* Returns an array with the minimum and maximum dates with notifications.
*
* @return array to be used with list() to get the min/max notification date timestamps.
*/
protected function getMinDatesNotifications()
{
$dbo = JFactory::getDbo();
$mindate = null;
$maxdate = null;
$dbo->setQuery(
$dbo->getQuery(true)
->select('MIN(' . $dbo->qn('createdon') . ') AS ' . $dbo->qn('mindate'))
->select('MAX(' . $dbo->qn('createdon') . ') AS ' . $dbo->qn('maxdate'))
->from($dbo->qn('#__vikbooking_notifications'))
);
$info_dates = $dbo->loadObject();
if ($info_dates && !empty($info_dates->mindate)) {
$mindate = strtotime(JHtml::fetch('date', $info_dates->mindate, 'Y-m-d'));
$maxdate = strtotime(JHtml::fetch('date', $info_dates->maxdate, 'Y-m-d'));
}
return [$mindate, $maxdate];
}
}