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

223 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS\Calendar;
use MRBS\DateTime;
use function MRBS\day_of_MRBS_week;
use function MRBS\escape_html;
use function MRBS\format_iso_date;
use function MRBS\get_end_last_slot;
use function MRBS\get_entries_by_area;
use function MRBS\get_rooms;
use function MRBS\get_start_first_slot;
use function MRBS\get_vocab;
use function MRBS\multisite;
class CalendarMultidayMultiroom extends Calendar
{
private $day_start_interval;
private $n_days;
private $start_dow;
public function __construct(string $view, int $view_all, int $year, int $month, int $day, int $area_id, int $room_id)
{
global $weekstarts, $resolution;
parent::__construct($view, $view_all, $year, $month, $day, $area_id, $room_id);
// Calculate/get:
// the first day of the interval
// how many days there are in it
// the day of the week of the first day in the interval
$time = mktime(12, 0, 0, $this->month, $this->day, $this->year);
switch ($this->view)
{
case 'week':
$skipback = day_of_MRBS_week($time);
$this->day_start_interval = $this->day - $skipback;
$this->n_days = DAYS_PER_WEEK;
$this->start_dow = $weekstarts;
break;
case 'month':
$this->day_start_interval = 1;
$this->n_days = (int) date('t', $time);
$this->start_dow = (int) date('N', mktime(12, 0, 0, $this->month, 1, $this->year));
break;
default:
trigger_error("Unsupported view '$this->view'", E_USER_WARNING);
break;
}
$this->start_date = (new DateTime())->setTimestamp(get_start_first_slot($this->month, $this->day_start_interval, $this->year));
$this->end_date = (new DateTime())->setTimestamp(get_end_last_slot($this->month, $this->day_start_interval + $this->n_days-1, $this->year));
// Get the data. It's much quicker to do a single SQL query getting all the
// entries for the interval in one go, rather than doing a query for each day.
$entries = get_entries_by_area($this->area_id, $this->start_date, $this->end_date);
// We want to build an array containing all the data we want to show and then spit it out.
$this->map = new Map($this->start_date, $this->end_date, $resolution);
$this->map->addEntries($entries);
}
// TODO: Handle the case where there is more than one booking per slot
public function innerHTML(): string
{
global $row_labels_both_sides, $column_labels_both_ends;
global $view_all_always_go_to_day_view;
// It's theoretically possible to display a transposed table with rooms along the top and days
// down the side. However, it doesn't seem a very useful display and so hasn't yet been implemented.
// The problem is that the results don't look good whether you have the flex direction as 'row' or
// 'column'. If you set it to 'row' the bookings are read from left to right within a day, but from
// top to bottom within the interval (week/month), so you have to read the display by snaking down
// the columns, which is potentially confusing. If you set it to 'column' then the bookings are in
// order reading straight down the column, but the text within the bookings is usually clipped unless
// the booking lasts the whole day. When the days are along the top and the text is clipped you can
// at least see the first few characters which is useful, but when the days are down the side you only
// see the top of the line.
//
// As a result $days_along_top is always true, but is here so that there can be stubs in the code in
// case people want a transposed view in future.
$days_along_top = true;
$rooms = get_rooms($this->area_id);
$n_rooms = count($rooms);
// Check to see whether there are any rooms in the area
if ($n_rooms == 0)
{
// Add an 'empty' data flag so that the JavaScript knows whether this is a real table or not
return "<tbody data-empty=1><tr><td><h1>" . get_vocab("no_rooms_for_area") . "</h1></td></tr></tbody>";
}
// TABLE HEADER
$thead = '<thead';
$slots = $this->getSlots($this->month, $this->day_start_interval, $this->year, $this->n_days, true);
if (isset($slots))
{
// Add some data to enable the JavaScript to draw the timeline
$thead .= ' data-slots="' . escape_html(json_encode($slots)) . '"';
$thead .= ' data-timeline-vertical="' . (($days_along_top) ? 'true' : 'false') . '"';
$thead .= ' data-timeline-full="true"';
}
$thead .= ">\n";
if ($days_along_top)
{
$header_inner_rows = $this->multidayHeaderRowsHTML($this->day_start_interval, $this->n_days, $this->start_dow);
}
else
{
// See comment above
trigger_error("Not yet implemented", E_USER_WARNING);
}
$thead .= implode('', $header_inner_rows);
$thead .= "</thead>\n";
// Now repeat the header in a footer if required
$tfoot = ($column_labels_both_ends) ? "<tfoot>\n" . implode('',array_reverse($header_inner_rows)) . "</tfoot>\n" : '';
// TABLE BODY LISTING BOOKINGS
$tbody = "<tbody>\n";
$room_link_vars = [
'view' => $this->view,
'view_all' => 0,
'page_date' => format_iso_date($this->year, $this->month, $this->day),
'area' => $this->area_id
];
if ($days_along_top)
{
// the standard view, with days along the top and rooms down the side
foreach ($rooms as $room)
{
$room_id = $room['id'];
$room_link_vars['room'] = $room_id;
$tbody .= "<tr>\n";
$row_label = $this->roomCellHTML($room, $room_link_vars);
$tbody .= $row_label;
for ($j = 0, $date = clone $this->start_date; $j < $this->n_days; $j++, $date->modify('+1 day'))
{
if ($date->isHiddenDay())
{
continue;
}
// Add classes for weekends, holidays, etc.
$classes = $this->getDateClasses($date);
$tbody .= '<td';
if (!empty($classes))
{
$tbody .= ' class="' . implode(' ', $classes) . '"';
}
$tbody .= '>';
$vars = [
'view_all' => $this->view_all,
'page_date' => $date->getISODate(),
'area' => $this->area_id,
'room' => $room['id']
];
// If there is more than one slot per day, then it can be very difficult to pick
// out an individual one, which could be just one pixel wide, so we go to the
// day view first where it's easier to see what you are doing. Otherwise, we go
// direct to edit_entry.php if the slot is free, or view_entry.php if it is not.
// Note: the structure of the cell, with a single link and multiple flex divs,
// only allows us to direct to the booking if there's only one slot per day.
if ($view_all_always_go_to_day_view || (self::getNTimeSlots() > 1))
{
$page = 'index.php';
$vars['view'] = 'day';
}
else
{
$vars['view'] = $this->view;
$this_slot = $this->map->slot($room_id, $j, Calendar::morningSlotsSeconds());
if (empty($this_slot))
{
$page = 'edit_entry.php';
}
else
{
$page = 'view_entry.php';
$vars['id'] = $this_slot[0]['id'];
}
}
$link = "$page?" . http_build_query($vars, '', '&');
$link = multisite($link);
$tbody .= '<a href="' . escape_html($link) . "\">\n";
$tbody .= $this->flexDivsHTML($room_id, $j, $j);
$tbody .= "</a>\n";
$tbody .= "</td>\n";
}
if ($row_labels_both_sides)
{
$tbody .= $row_label;
}
$tbody .= "</tr>\n";
}
}
else
{
// See comment above
trigger_error("Not yet implemented", E_USER_WARNING);
}
$tbody .= "</tbody>\n";
return $thead . $tfoot . $tbody;
}
}