Files
mrbs-equbao-2026/web/js/datepicker.js.php
T
人事系统开发 48092cab42
Docker image / push (push) Canceled after 0s
MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、登录页JS修复等全部改动
2026-09-08 21:19:47 +08:00

511 lines
17 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
use MRBS\Intl\FormatterFlatpickr;use MRBS\Intl\IntlDatePatternConverter;
require "../defaultincludes.inc";
// We use ICU datetime patterns in the config file rather than flatpickr formatting
// tokens so that we can switch to a different datepicker if necessary, without
// having to change the config settings.
function get_date_format() : string
{
global $datetime_formats;
if (isset($datetime_formats['datepicker']['pattern']))
{
try
{
$converter = new IntlDatePatternConverter(new FormatterFlatpickr());
return $converter->convert($datetime_formats['datepicker']['pattern']);
}
catch (\Exception $e)
{
// Report the error and fall through to the 'custom' format
trigger_error($e->getMessage(), E_USER_WARNING);
}
}
return 'custom';
}
http_headers(array("Content-type: application/x-javascript"),
60*30); // 30 minute expiry
?>
'use strict';
<?php
// Fix for iOS 13 where the User Agent string has been changed.
// See https://github.com/flatpickr/flatpickr/issues/1992
?>
function iPadMobileFix() {
return function(instance) {
return {
onParseConfig: function() {
if (instance.isMobile)
{
return;
}
if (isIos())
{
instance.isMobile = true;
}
}
};
};
}
<?php
// Turn a JavaScript year, month, day (ie with Jan = 0) into an
// ISO format YYYY-MM-DD date string. Can cope with months
// outside the range 0..11, but days must be valid.
?>
function getISODate(year, month, day)
{
<?php // toISOString() converts to UTC, so make the date a UTC date ?>
var date = new Date(Date.UTC(year, month, day));
return date.toISOString().split('T')[0];
}
// Given a JavaScript Date object returns a date string in YYYY-MM-DD
// format. (Note that toISOString() returns a date in UTC time).
function getLocalISODateString(date)
{
var month = (date.getMonth() + 1).toString().padStart(2, '0');
var day = date.getDate().toString().padStart(2, '0');
var year = date.getFullYear().toString();
return [year, month, day].join('-');
}
<?php
// Functions to find the start and end dates of a week and month given a
// date in YYYY-MM-DD format.
// weekStarts is the start day of the week (0 for Sunday, 1 for Monday etc.)
// (Could be implemented by extending the Date class, but extends isn't
// supported by IE11.)
?>
function weekStart(date, weekStarts) {
<?php
// Need to add a time to make sure the date is interpreted as local rather than UTC.
// "When the time zone offset is absent, date-only forms are interpreted as a UTC time and
// date-time forms are interpreted as local time. This is due to a historical spec error
// that was not consistent with ISO 8601 but could not be changed due to web compatibility."
?>
var d = new Date(date + "T00:00:00");
var diff = d.getDay() - weekStarts;
if (diff < 0)
{
diff += 7;
}
d.setDate(d.getDate() - diff);
return getLocalISODateString(d);
}
function weekEnd(date, weekStarts) {
<?php // Need to add a time to make sure the date is interpreted as local rather than UTC. ?>
var d = new Date(weekStart(date, weekStarts) + "T00:00:00");
d.setDate(d.getDate() + 6);
return getLocalISODateString(d);
}
function monthStart(date) {
<?php // Need to add a time to make sure the date is interpreted as local rather than UTC. ?>
var d = new Date(date + "T00:00:00");
d.setDate(1);
return getLocalISODateString(d);
}
function monthEnd(date) {
<?php // Need to add a time to make sure the date is interpreted as local rather than UTC. ?>
var d = new Date(date + "T00:00:00");
<?php
// Set the date to the first of the month, because otherwise we will
// advance by two months when incrementing the month below if we're at the
// end of the month and the next month has fewer days than this one.
?>
d.setDate(1);
d.setMonth(d.getMonth() + 1);
d.setDate(0);
return getLocalISODateString(d);
}
// Returns an array of dates in the range startDate..endDate, optionally
// excluding hidden days.
function datesInRange(startDate, endDate, excludeHiddenDays) {
var result=[];
<?php // Need to add a time to make sure the date is interpreted as local rather than UTC. ?>
var e=new Date(endDate + "T00:00:00");
var hiddenDays = [<?php echo implode(',', $hidden_days)?>];
<?php // dates can be compared using > and < but not == or === ?>
for (var d=new Date(startDate + "T00:00:00"); !(d>e); d.setDate(d.getDate()+1))
{
if (excludeHiddenDays && (hiddenDays.indexOf(d.getDay()) >= 0))
{
continue;
}
result.push(getLocalISODateString(d));
}
return result;
}
$(document).on('page_ready', function() {
const locales = args.langPrefs;
<?php
// Set up datepickers. We convert all inputs of type 'date' into flatpickr
// datepickers. Note that by default flatpickr will use the native datepickers
// on mobile devices because they are generally better.
// Localise the flatpickr
// Could use new URLSearchParams(document.currentScript.src); and get the lang parameter from the
// file's query string, but document.currentScript is not supported by IE (though that probably
// doesn't matter much anymore).
if (null !== ($flatpickr_lang_path = Language::getInstance()->getFlatpickrLangPath()))
{
// Map the flatpickr lang file onto a flatpickr l10ns property and then localize
echo 'flatpickr.localize(flatpickr.l10ns["' . Language::getFlatpickrProperty($flatpickr_lang_path) . '"]);';
}
// Custom date formatter. At the moment, only two format strings are supported:
//
// 'custom' The date is formatted in numeric form in the user's preferred locale,
// as expressed by their browser preferences and subject to any
// overriding config settings. Note that 'custom' is not supported on
// IE10, as it requires Intl.DateTimeFormat(), and so IE10 users
// are given a date in 'Y-m-d' format.
//
// everything All other format strings are treated as 'Y-m-d'.
// else
?>
var formatDate = function(dateObj, formatStr) {
<?php
// If window.Intl is supported then we can format dates in the user's preferred
// locale. Otherwise, in practice just IE10, they have to make do with ISO
// (YYYY-MM-DD) dates.
?>
if (window.Intl && (formatStr === 'custom'))
{
<?php
if ($language_debug)
{
?>
console.debug("MRBS language preferences: " + JSON.stringify(locales));
console.debug("MRBS: locales supported by Intl.DateTimeFormat: " + JSON.stringify(Intl.DateTimeFormat.supportedLocalesOf(locales)));
<?php
}
// Check whether we can use our first choice locale for date and time formatting.
?>
if (!Intl.DateTimeFormat.supportedLocalesOf(locales[0]).length)
{
console.warn("MRBS: Intl.DateTimeFormat in this browser does not support the '" + locales[0] + "' locale.");
}
return (typeof locales === 'undefined') ?
new Intl.DateTimeFormat().format(dateObj) :
new Intl.DateTimeFormat(locales).format(dateObj);
}
return getISODate(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate());
};
var onDayCreate = function(dObj, dStr, fp, dayElem) {
<?php
// If the datepicker is used for navigation, and this is a hidden day, and the user is
// a booking admin, then add a class to the day so that it can be styled differently.
// If they're not a booking admin, and it's a navigation datepicker, the day will be
// disabled - see later on in this file.
?>
const hiddenDays = [<?php echo implode(',', $hidden_days)?>];
if (hiddenDays.length &&
args.isBookAdmin &&
fp.altInput.classList.contains('navigation') &&
(hiddenDays.indexOf(dayElem.dateObj.getDay()) >= 0))
{
dayElem.classList.add('mrbs-hidden');
}
<?php // And add a class if it's a weekend day ?>
const weekDays = [<?php echo implode(',', $weekdays)?>];
if (weekDays.indexOf(dayElem.dateObj.getDay()) < 0) {
dayElem.classList.add('mrbs-weekend');
}
};
<?php
// Sync all the mini-calendars with this instance of one. In other words
// make the mini-calendars show sequential months, aligning with this one.
?>
function syncCals(instance)
{
var thisId = instance.element.attributes.id.nodeValue,
thisIndex = parseInt(thisId.substring(3), 10),
currentMonth = parseInt(instance.currentMonth, 10),
currentYear = parseInt(instance.currentYear, 10);
$.each(minicalendars, function(key, value) {
if (value.element.attributes.id.nodeValue !== thisId)
{
var index = parseInt(value.element.attributes.id.nodeValue.substring(3), 10);
value.jumpToDate(getISODate(currentYear, currentMonth + index - thisIndex, 1));
}
});
}
var onMonthChange = function(selectedDates, dateStr, instance) {
syncCals(instance);
};
var onYearChange = function(selectedDates, dateStr, instance) {
syncCals(instance);
};
var onMinicalChange = function(selectedDates, dateStr, instance) {
<?php
// The order of the query string parameters is important here. It needs to be the
// same as the order in the Prev and Next navigation links so that the pre-fetched
// pages can be used when possible.
?>
var href = 'index.php';
href += '?view=' + args.view;
href += '&page_date=' + dateStr;
href += '&area=' + args.area;
href += '&room=' + args.room;
if (args.site)
{
href += '&site=' + encodeURIComponent(args.site);
}
<?php
// Set the new date in the mini-calendar, in order to avoid the previous one
// still showing as selected.
// TODO: change the date in the other mini-calendar of the date appears there as well?
?>
instance.setDate([selectedDates[0], selectedDates[0]], false);
updateBody(href); <?php // Update the body via an Ajax call to avoid flickering ?>
};
var lastValidDate = {};
<?php
$date_format = get_date_format();
?>
var config = {
plugins: [iPadMobileFix()],
dateFormat: 'Y-m-d',
altInput: true,
altFormat: <?php echo "'" . get_date_format() . "'" ?>,
<?php
if ($date_format == 'custom')
{
echo "formatDate: formatDate,\n";
}
?>
onDayCreate: onDayCreate,
locale: {firstDayOfWeek: <?php echo $weekstarts ?>},
onChange: function(selectedDates, dateStr, instance) {
var element = $(instance.element);
var submit = element.data('submit');
var elementName = element.attr('name');
<?php
// Flatpickr allows the user to delete the date in the input field, even when allowInput
// is false, resulting in an empty string, which then causes problems for edit_entry_handler.php.
// See https://github.com/flatpickr/flatpickr/issues/936 and
// https://github.com/flatpickr/flatpickr/pull/2252 . At the time of writing the PR has not been
// merged. To get round this we substitute an empty string for the last known valid date,
// but only for required inputs.
?>
if (dateStr) {
lastValidDate[elementName] = dateStr;
}
else if (element.prop('required') && lastValidDate[elementName]) {
instance.setDate(lastValidDate[elementName]);
}
<?php // Submit will be set for the datepicker in the banner ?>
if (submit) {
$('#' + submit).trigger('submit');
}
else {
element.trigger('change');
}
},
onReady: function(selectedDates, dateStr, instance) {
if (instance.altInput.ariaLabel === null) {
instance.altInput.ariaLabel = instance.input.ariaLabel;
}
if (dateStr) {
lastValidDate[instance.element.getAttribute('name')] = dateStr;
}
}
};
<?php
// Setting weekNumbers causes flatpickr not to use the native datepickers on mobile
// devices. As these are generally better than flatpickr's, it's probably better
// to have the native datepicker and do without the week numbers.
?>
if (isMobile())
{
$('input.flatpickr-input').width('auto');
}
else
{
<?php // Only display the week number if the MRBS week starts on the first day of the week ?>
config.weekNumbers = <?php echo ($mincals_week_numbers && ($weekstarts == DateTime::firstDayOfWeek($timezone, Language::getInstance()->getWebLocale()))) ? 'true' : 'false' ?>;
}
let fps = flatpickr('input[type="date"]:not(.navigation)', config);
// flatpickr will either return a single instance or an array of instances,
// so convert a single instance to an array so that the iteration works.
fps = (Array.isArray(fps) ? fps : [fps]);
fps.forEach(function(fp) {
<?php
// For dates that have a data-min attribute, set the minDate.
?>
let minDate = $(fp.input).data('min');
if (minDate)
{
fp.set('minDate', minDate);
// Note that although the flatpickr documentation says this should work on mobiles, it doesn't seem to.
// So we manually set the 'min' attribute for mobiles.
$('input[type="hidden"][data-min]').next('.flatpickr-input.flatpickr-mobile').attr('min', minDate);
}
});
<?php
// For datepickers used for navigation we need to modify the config to take account
// of hidden days.
// Disable hidden days, unless the user is a booking admin. (If they're a booking
// admin then they'll still be able to select the date, but it will be given a different
// class so that it can be styled differently - see code above in this file.)
if (!empty($hidden_days))
{
?>
if (!args.isBookAdmin)
{
config.disable = [
function (date) {
return ([<?php echo implode(',', $hidden_days); ?>].indexOf(date.getDay()) >= 0);
}
];
}
<?php
}
?>
flatpickr('input[type="date"].navigation', config);
<?php
// Build the mini-calendars, if required.
if (!empty($display_mincals))
{
?>
if (!isMobile())
{
var div = $('.minicalendars');
if (div.length > 0)
{
for (var i = 0; i < 2; i++)
{
<?php // Add the 'navigation' class so that the JavaScript knows it can use hidden days ?>
div.append($('<span class="minicalendar navigation" id="cal' + i + '"></span>'));
}
config.inline = true;
config.onMonthChange = onMonthChange;
config.onYearChange = onYearChange;
config.onChange = onMinicalChange;
<?php
// Setting a range only works if there are no hidden days: it does not make
// sense to set a start date of a range on a disabled day.
if (empty($hidden_days))
{
?>
config.mode = 'range';
<?php
}
?>
var minicalendars = flatpickr('span.minicalendar', config);
$.each(minicalendars, function (key, value) {
var startDate, endDate;
if (args.view === 'month')
{
startDate = monthStart(args.pageDate);
endDate = monthEnd(args.pageDate);
}
else if (args.view === 'week')
{
startDate = weekStart(args.pageDate, <?php echo $weekstarts?>);
endDate = weekEnd(args.pageDate, <?php echo $weekstarts?>);
}
else
{
startDate = args.pageDate;
endDate = startDate;
}
<?php
if (empty($hidden_days))
{
?>
value.setDate([startDate, endDate]);
<?php
}
else
{
// If we've got hidden days then highlight in the mini-calendars those
// days in the range that are not hidden.
?>
value.setDate(datesInRange(startDate, endDate, true));
<?php
}
?>
value.changeMonth(key);
});
<?php
// Align the top of the mini-calendars with the top of the navigation bar
?>
div.css('margin-top', $('.view_container h2').outerHeight(true) + 'px');
<?php
// Once the calendars are formed then we add the class 'formed' which will
// bring into play CSS media queries. We need to do this because if we
// form them when the media queries are operational then they won't get
// formed if the result of the query is 'display: none', which means that if
// the window is later widened, for example, they still won't appear when they
// should do.
?>
div.addClass('formed');
}
}
<?php
}
// Only show the main table and navigation once the mini-calendars are in place
// in order to avoid the screen jiggling about.
?>
$('.view_container').removeClass('js_hidden');
<?php
// Show the datepicker in the banner, which has been hidden up until now
?>
$('#form_nav').removeClass('js_hidden');
});