Files
mrbs-equbao-2026/web/lib/MRBS/Calendar/CalendarMultislot.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

308 lines
9.8 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS\Calendar;
use MRBS\DateTime;
use MRBS\Period;
use MRBS\Periods;
use function MRBS\escape_html;
use function MRBS\get_vocab;
use function MRBS\getWritable;
use function MRBS\hm_before;
use function MRBS\hour_min;
use function MRBS\is_book_admin;
use function MRBS\multisite;
use function MRBS\period_index_nominal;
use function MRBS\session;
abstract class CalendarMultislot extends Calendar
{
protected $timetohighlight;
// $s is nominal seconds
protected function getQueryVars(int $room, int $month, int $day, int $year, int $s) : array
{
global $morningstarts, $morningstarts_minutes;
$result = [];
// check to see if the time is really on the next day
$date = getdate(mktime(0, 0, $s, $month, $day, $year));
if (hm_before($date, ['hours' => $morningstarts, 'minutes' => $morningstarts_minutes]))
{
$date['hours'] += 24;
}
$hour = $date['hours'];
$minute = $date['minutes'];
$period = period_index_nominal($s);
$vars = [
'view' => $this->view,
'year' => $year,
'month' => $month,
'day' => $day,
'area' => $this->area_id
];
$result['booking'] = $vars;
$result['new_periods'] = array_merge($vars, ['room' => $room, 'period' => $period]);
$result['new_times'] = array_merge($vars, ['room' => $room, 'hour' => $hour, 'minute' => $minute]);
return $result;
}
protected function tdHTML(array $cell, array $query_vars, bool $is_invalid = false) : string
{
// Draws a single cell in the main table of the day and week views
//
// $cell is an array of entries that occupy that cell. There can be none, one or many
// bookings in a cell. If there are no bookings, then a blank cell is drawn with a link
// to the edit entry form. If there is one booking, then the booking is shown in that
// cell. If there is more than one booking, then all the bookings are shown.
// $query_vars is an array containing the query vars to be used in the link for the cell.
// It is indexed as follows:
// ['new_periods'] the vars to be used for an empty cell if using periods
// ['new_times'] the vars to be used for an empty cell if using times
// ['booking'] the vars to be used for a full cell
//
// $is_invalid specifies whether the slot actually exists or is one of the non-existent
// slots in the transition to DST
global $enable_periods, $show_plus_link, $prevent_booking_on_holidays, $prevent_booking_on_weekends;
$html = '';
$classes = array();
// Don't put in a <td> cell if the slot contains a single booking whose n_slots is NULL.
// This would mean that it's the second or subsequent slot of a booking and so the
// <td> for the first slot would have had a rowspan that extended the cell down for
// the number of slots of the booking.
if (empty($cell) || !is_null($cell[0]['n_slots']))
{
if (!empty($cell))
{
$classes[] = 'booked';
if (count($cell) > 1)
{
$classes[] = 'multiply';
}
}
elseif ($is_invalid)
{
$classes[] = 'invalid';
}
else
{
$classes[] = 'new';
// Add classes for weekends and holidays
$date = new DateTime();
$date->setDate(
$query_vars['new_times']['year'],
$query_vars['new_times']['month'],
$query_vars['new_times']['day']
);
$classes = array_merge($classes, $this->getDateClasses($date));
}
// If there's no booking, or if there are multiple bookings, then make the slot one unit long
$slots = (count($cell) == 1) ? $cell[0]['n_slots'] : 1;
$html .= $this->tdOpeningTagHTML($classes, $slots);
// If the room isn't booked, then allow it to be booked
if (empty($cell))
{
// Don't provide a link if the slot doesn't really exist or if the user is logged in, but not a booking admin,
// and it's a holiday/weekend and bookings on holidays/weekends are not allowed. (We provide a link if they
// are not logged in because they might want to click and login as an admin).
if ($is_invalid ||
((null !== session()->getCurrentUser()) && !is_book_admin($query_vars['new_times']['room']) &&
(($prevent_booking_on_holidays && in_array('holiday', $classes)) ||
($prevent_booking_on_weekends && in_array('weekend', $classes)))))
{
$html .= '<span class="not_allowed"></span>';
}
else
{
$vars = ($enable_periods) ? $query_vars['new_periods'] : $query_vars['new_times'];
$query = http_build_query($vars, '', '&');
$html .= '<a href="' . escape_html(multisite("edit_entry.php?$query")) . '"' .
' aria-label="' . escape_html(get_vocab('create_new_booking')) . "\">";
if ($show_plus_link)
{
$html .= "<img src=\"images/new.gif\" alt=\"New\" width=\"10\" height=\"10\">";
}
$html .= "</a>";
}
}
else // if it is booked, then show the booking
{
foreach ($cell as $booking)
{
$vars = $query_vars['booking'];
$vars['id'] = $booking['id'];
$query = http_build_query($vars, '', '&');
// We have to wrap the booking in a <div> because we want the booking itself to be given
// an absolute position, and we can't use position relative on a <td> in IE11 and below.
// We also need the bookings in a container because jQuery UI resizable has problems
// with border-box (see https://stackoverflow.com/questions/18344272). And we need
// border-box for the bookings because we are using padding on the bookings and we want
// 'width: 100%' and 'height: 100%' to fill the table-cell with the entire booking
// including content.
$classes = $this->getEntryClasses($booking);
$classes[] = 'booking';
if ($booking['is_multiday_start'])
{
$classes[] = 'multiday_start';
}
if ($booking['is_multiday_end'])
{
$classes[] = 'multiday_end';
}
// Tell JavaScript to make bookings resizable
if ((count($cell) == 1) &&
getWritable($booking['create_by'], $booking['room_id']))
{
$classes[] = 'writable';
}
$html .= '<div class="' . implode(' ', $classes) . '">';
$html .= '<a href="' . escape_html(multisite("view_entry.php?$query")) . '"' .
' title="' . escape_html($booking['description'] ?? '') . '"' .
' class="' . $booking['type'] . '"' .
' data-id="' . $booking['id'] . '"' .
' data-type="' . $booking['type'] . '">';
$html .= escape_html($booking['name']) . '</a>';
$html .= "</div>";
}
}
$html .= "</td>\n";
}
return $html;
}
// Output a start table cell tag <td> with class of $classes.
// $classes can be either a string or an array of classes
// empty or row_highlight if highlighted.
// $slots is the number of time slots high that the cell should be
//
// $data is an optional third parameter which if set passes an
// associative array of name-value pairs to be used in data attributes
private function tdOpeningTagHTML(array $classes, int $slots, ?array $data=null) : string
{
global $times_along_top;
$html = '<td';
if (!empty($classes))
{
$html.= ' class="' . implode(' ', $classes) . '"';
}
if ($slots > 1)
// No need to output more HTML than necessary
{
$html .= (($times_along_top) ? ' colspan' : ' rowspan') . "=\"$slots\"";
}
if (isset($data))
{
foreach ($data as $name => $value)
{
$html .= " data-$name=\"$value\"";
}
}
$html .= ">";
return $html;
}
// Draw a time cell to be used in the first and last columns of the day and week views
// $s the number of seconds since the start of the day (nominal - not adjusted for DST)
// $url the url to form the basis of the link in the time cell
function tbodyThTimeCellHTML(int $s, string $url) : string
{
global $enable_periods, $resolution;
$html = '';
$html .= "<th data-seconds=\"$s\">";
$html .= '<a href="' . escape_html($url) . '" title="' . get_vocab("highlight_line") . "\">";
if ($enable_periods)
{
$periods = Periods::getForArea($this->area_id);
$html .= escape_html($periods->offsetGetByNominalSeconds($s)->name);
}
else
{
$html .= escape_html($this->timeslotText($s, $resolution));
}
$html .= "</a></th>\n";
return $html;
}
protected function theadThTimeCellsHTML(int $start, int $end, int $increment) : string
{
global $enable_periods;
$html = '';
for ($s = $start; $s <= $end; $s += $increment)
{
// Put the number of seconds since the start of the day (nominal, ignoring DST)
// in a data attribute so that JavaScript can pick it up
$html .= "<th data-seconds=\"$s\">";
// We need the span so that we can apply some padding. We can't apply it
// to the <th> because that is used by jQuery.offset() in resizable bookings
$html .= "<span>";
if ( $enable_periods )
{
$periods = Periods::getForArea($this->area_id);
$html .= escape_html($periods->offsetGetByNominalSeconds($s)->name);
}
else
{
$html .= escape_html($this->timeslotText($s, $increment));
}
$html .= "</span>";
$html .= "</th>\n";
}
return $html;
}
private function timeslotText(int $s, int $resolution) : string
{
global $show_slot_endtime;
$result = hour_min($s);
if ($show_slot_endtime)
{
$result .= '-' . hour_min($s + $resolution);
}
return $result;
}
}