Files
人事系统开发 1ba6efd8ed MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、JS空集合保护、
会话过期体验优化(403 JSON)、display_errors 关闭、
固定 key 根治 Integrity check failed 等全部改动

注意:config.inc.php/.htaccess/.user.ini 含敏感信息,
通过 .gitignore 排除,勿推送到公开仓库。
2026-09-09 16:55:02 +08:00

366 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
require "../defaultincludes.inc";
http_headers(array("Content-type: application/x-javascript"),
60*30); // 30 minute expiry
?>
'use strict';
$(document).on('page_ready', function() {
<?php
// Tidy up the presentation of the first header row by merging the cells.
// The div is hidden while we are manipulating it so that it doesn't flicker;
// we have to make it visible when we have finished
?>
var summaryDiv = $('#div_summary'),
summaryHead = summaryDiv.find('thead'),
tableOptions;
summaryHead.find('tr:first th:odd').attr('colspan', '2');
summaryHead.find('tr:first th:even').not(':first').remove();
summaryHead.find('tr:first th:first').attr('rowspan', '2');
summaryHead.find('tr:eq(1) th:first').remove();
summaryDiv.css('visibility', 'visible');
<?php
// We don't support iCal output for the Summary. So if the Summary button is pressed
// disable the iCal button and, if iCal output is checked, check another format. If the
// Report button is pressed then re-enable the iCal button.
?>
$('input[name="output"]').on('change', function() {
var output = $(this).filter(':checked').val(),
formatButtons = $('input[name="output_format"]'),
icalButton = formatButtons.filter('[value="' + <?php echo OUTPUT_ICAL ?> + '"]');
if (output === '<?php echo SUMMARY ?>')
{
icalButton.prop('disabled', true);
if (icalButton.is(':checked'))
{
formatButtons.filter('[value="' + <?php echo OUTPUT_HTML ?> + '"]').attr('checked', 'checked');
}
}
else
{
icalButton.prop('disabled', false);
}
}).trigger('change');
<?php
// Turn the list of users into a dataTable
?>
tableOptions = {};
<?php
// Use an Ajax source - gives much better performance for large tables
// May need to use the FormData emulation (https://github.com/francois2metz/html5-formdata)
// for older browsers
?>
tableOptions.ajax = {
url: 'report.php' + ((args.site) ? '?site=' + args.site : ''),
method: 'POST',
processData: false,
contentType: false,
data: function() {
return new FormData($('#report_form')[0]);
}
};
<?php
// Add in a hidden input to the form so that we can tell if we are using DataTables
// (which will be if JavaScript is enabled). We need to know this because when we're using an
// Ajax data source we don't want to send the HTML version of the table data.
?>
$('<input>').attr({
type: 'hidden',
name: 'datatable',
value: '1'
}).appendTo('#report_form');
var table = $('#report_table'),
reportTable;
<?php
// Get the types and feed those into dataTables
?>
tableOptions.columnDefs = getTypes(table);
<?php
// Add a "Delete entries button", provided that (a) the user is an
// admin and (b) the configuration allows it
if ($auth['show_bulk_delete'])
{
?>
var addDeleteButton = function addDeleteButton() {
var data;
var batches;
var nBatches;
var nEntries;
var progressContainer = $('<div id="progress_container"></div>');
var requests;
var requestsCompleted;
var requestsAborted;
<?php // Initialise the delete button and progress bar ?>
function initDeleteButton()
{
var title = '<?php echo get_js_vocab("deleting_n_entries") ?>';
data = reportTable.rows({filter: 'applied'}).data().toArray();
nEntries = data.length;
requests = [];
requestsCompleted = 0;
requestsAborted = 0;
title = title.replace('%d', nEntries.toLocaleString(args.langPrefs));
progressContainer.find('span').text(title);
}
function reloadReport() {
$('#report_table_processing').css('visibility', 'hidden');
<?php
// Reload the page to get the new dataset. If we're using
// an Ajax data source (for true Ajax data sources, not server
// side processing) and there's no summary table we can be
// slightly more elegant and just reload the Ajax data source.
?>
if (reportTable.ajax.url() &&
!reportTable.page.info().serverSide &&
($('#div_summary').length === 0))
{
reportTable.ajax.reload(function() {
<?php
// Once the report data has been reloaded we also need to update the number of entries,
// hide the progress bar and reinitialise the delete button for the new data.
?>
progressContainer.hide();
initDeleteButton();
$('#n_entries').text(nEntries);
});
}
else
{
window.location.reload();
}
}
<?php
// Add a progress bar, with a title and a cancel button. The title text will be
// added later, once we know how many entries there are to be deleted.
?>
progressContainer.append('<span></span>')
.append('<progress></progress>')
.append('<button id="cancel_deletions"><?php echo get_js_vocab("cancel") ?></button>')
.insertAfter('#report_table_paginate');
<?php // Add a delete button ?>
$('<button id="delete_button"><?php echo get_js_vocab("delete_entries") ?><\/button>')
.on('click', function() {
if (nEntries === 0) {
return;
}
<?php
// Fire off an Ajax POST request. Upon completion fire off another one until there
// are none left.
?>
var postBatch = function(batch) {
var params = {
csrf_token: getCSRFToken(),
<?php // The ids are JSON encoded to avoid hitting the php.ini max_input_vars limit ?>
ids: JSON.stringify(batch)
};
if (args.site) {
params.site = args.site;
}
<?php // Save the XHR request in case we need to abort it ?>
requests.push($.post(
'ajax/del_entries.php',
params,
function (result) {
var isInt = /^\s*\d+\s*$/;
requestsCompleted++;
if (isInt.test(result)) {
totalDeleted += parseInt(result, 10);
progress.val(totalDeleted).text(totalDeleted);
} else {
success = false;
}
<?php // Fire off another request if there is one ?>
var batch = batches.pop();
if (batch !== undefined) {
postBatch(batch);
}
<?php // Otherwise check whether everything has finished ?>
else if (requestsCompleted + requestsAborted >= nBatches) {
<?php
// Log the time it took to delete the entries if we're got $debug set.
if ($debug)
{
?>
console.log((Date.now() - startTime)/1000 + " seconds");
<?php
}
?>
if (!success) {
window.alert("<?php echo get_js_vocab('delete_entries_failed') ?>");
}
reloadReport();
}
}
)
)
}
var message = "<?php echo get_js_vocab('delete_entries_warning') ?>";
message = message.replace('%s', nEntries.toLocaleString(args.langPrefs));
if (!window.confirm(message)) {
return;
}
var progress = progressContainer.find('progress');
var success = true;
var totalDeleted = 0;
<?php
// If $debug is set record how long it takes to delete the entries
if ($debug)
{
?>
var startTime = Date.now();
<?php
}
?>
progress.attr('max', nEntries).val(0).text('0');
progressContainer.show();
<?php
// We're going to split the POST requests into batches because if a
// single POST request is too large we could get a 406 error. The POST
// requests are fired off asynchronously, so we need to count them all
// back before we know that we've finished.
?>
var batchSize = <?php echo $del_entries_ajax_batch_size ?>,
batch = [],
i;
batches = [];
for (i=0; i<nEntries; i++) {
batch.push($(data[i][0]).siblings('a').data('id'));
if (batch.length >= batchSize) {
batches.push(batch);
batch = [];
}
}
if (batch.length > 0) {
batches.push(batch);
}
<?php
// Dispatch the batches (if any) setting off parallel Ajax requests up to the maximum
// number determined by the config settings. When each request completes it will set
// off another one until all the batches have been processed.
?>
nBatches = batches.length;
if (nBatches > 0) {
$('#report_table_processing').css('visibility', 'visible');
for (i=0; i<<?php echo $del_entries_parallel_requests ?>; i++) {
batch = batches.pop();
if (batch !== undefined) {
postBatch(batch);
}
}
}
})
.insertAfter('#report_table_paginate');
<?php // While deletion is in progress disable all interaction except with the cancel button ?>
$(window).on('click keypress', function(e) {
if (progressContainer.is(':visible'))
{
if (e.target.id === 'cancel_deletions')
{
$.each(requests, function(index, request) {
<?php
// Abort all requests that haven't yet been sent, ie all those where the
// readyState hasn't yet reached 2 (HEADERS_RECEIVED). For more details see
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState.
// We don't abort those that have already been sent because we can't stop the
// server processing the deletions, and we want to know the result.
?>
if (request.readyState < 2)
{
request.abort();
requestsAborted++;
if (requestsCompleted + requestsAborted + batches.length >= nBatches)
{
reloadReport();
}
}
});
}
e.preventDefault();
return false;
}
});
initDeleteButton();
};
if (args.isAdmin)
{
tableOptions.initComplete = addDeleteButton;
}
<?php
}
?>
<?php
// If the sort columns are specified then tell DataTables to sort by those columns on
// initialisation, otherwise it will sort by the default column. We also need to
// tell DataTables not to use the saved state, otherwise it will confuse the user
// who has just changed the "Sort by" option on the report form.
?>
var sortColumns = table.data('sortColumns');
if ((sortColumns !== undefined) && (sortColumns.length))
{
tableOptions.order = [];
sortColumns.forEach(function(column) {
tableOptions.order.push([column, 'asc']);
});
tableOptions.stateSave = false;
}
<?php // If we're an admin then add a "Copy email addresses" button ?>
if (args.isAdmin) {
tableOptions.buttons = [
{
<?php // The first button is assumed to be the colvis button ?>
extend: 'colvis'
},
{
<?php
// Add in an extra button to copy email addresses as a unique, sorted, comma separated
// list so that they can be pasted into an address field in an email client.
// Useful for sending messages to those booked on a certain day or in a certain room.
?>
text: '<?php echo get_js_vocab('copy_email_addresses') ?>',
action: function (e, dt, node, config) {
extractEmailAddresses(dt, '#col_create_by', true);
}
}
];
}
reportTable = makeDataTable('#report_table', tableOptions, {leftColumns: 1});
});