包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、登录页JS修复等全部改动
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Calendar;
|
||||
|
||||
use MRBS\DateTime;
|
||||
use function MRBS\datetime_format;
|
||||
use function MRBS\day_past_midnight;
|
||||
use function MRBS\escape_html;
|
||||
use function MRBS\format_iso_date;
|
||||
use function MRBS\get_vocab;
|
||||
use function MRBS\is_hidden_day;
|
||||
use function MRBS\multisite;
|
||||
|
||||
abstract class Calendar
|
||||
{
|
||||
protected $day;
|
||||
protected $month;
|
||||
protected $year;
|
||||
protected $area_id;
|
||||
protected $room_id;
|
||||
protected $view;
|
||||
protected $view_all;
|
||||
protected $start_date;
|
||||
protected $end_date;
|
||||
protected $map;
|
||||
|
||||
|
||||
public function __construct(string $view, int $view_all, int $year, int $month, int $day, int $area_id, int $room_id)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->view_all = $view_all;
|
||||
$this->year = $year;
|
||||
$this->month = $month;
|
||||
$this->day = $day;
|
||||
$this->area_id = $area_id;
|
||||
$this->room_id = $room_id;
|
||||
}
|
||||
|
||||
abstract public function innerHTML() : string;
|
||||
|
||||
|
||||
// Get a series of flex divs for a room in a day index interval for the map.
|
||||
protected function flexDivsHTML(int $room_id, int $start_day_index, int $end_day_index) : string
|
||||
{
|
||||
global $resolution;
|
||||
|
||||
$html = '';
|
||||
|
||||
// Get the time slots
|
||||
$n_time_slots = self::getNTimeSlots();
|
||||
$morning_slot_seconds = self::morningSlotsSeconds();
|
||||
$evening_slot_seconds = $morning_slot_seconds + (($n_time_slots - 1) * $resolution);
|
||||
|
||||
// Loop through the days in the interval
|
||||
for ($i=$start_day_index; $i<=$end_day_index; $i++)
|
||||
{
|
||||
$s = $morning_slot_seconds;
|
||||
|
||||
// Loop through the slots in the day
|
||||
while ($s <= $evening_slot_seconds)
|
||||
{
|
||||
// Get the entry for this slot
|
||||
$this_slot = $this->map->slot($room_id, $i, $s);
|
||||
// Start a FlexDiv if we haven't got one
|
||||
if (!isset($flex_div))
|
||||
{
|
||||
$flex_div = new FlexDiv($this_slot[0]['id'] ?? null);
|
||||
// If it's a booking, set the properties
|
||||
if (!empty($this_slot))
|
||||
{
|
||||
$this_entry = $this_slot[0];
|
||||
$flex_div->setClasses($this->getEntryClasses($this_entry));
|
||||
$flex_div->setLength($this_entry['n_slots']);
|
||||
$flex_div->setName($this_entry['name']);
|
||||
}
|
||||
// Work out how many slots to advance
|
||||
$n = $flex_div->getLength();
|
||||
}
|
||||
// Otherwise, look to see whether this is a continuation of the stored entry,
|
||||
// or else a change, in which case output the stored entry and reset.
|
||||
else
|
||||
{
|
||||
// Another free slot
|
||||
if (empty($this_slot) && !isset($flex_div->id))
|
||||
{
|
||||
$n = 1;
|
||||
$flex_div->addLength($n);
|
||||
}
|
||||
// A continuation of an existing booking
|
||||
elseif (!empty($this_slot) && isset($flex_div->id) && ($flex_div->id == $this_slot[0]['id']))
|
||||
{
|
||||
$n = $this_slot[0]['n_slots'];
|
||||
$flex_div->addLength($n);
|
||||
}
|
||||
// There's been a change. Output the FlexDiv and reset
|
||||
else
|
||||
{
|
||||
$html .= $flex_div->html();
|
||||
unset($flex_div);
|
||||
$n = 0;
|
||||
}
|
||||
}
|
||||
$s = $s + ($n * $resolution); // Advance n slots
|
||||
}
|
||||
}
|
||||
|
||||
// Output the final FlexDiv
|
||||
if (isset($flex_div))
|
||||
{
|
||||
$html .= $flex_div->html();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
protected function getDate(int $t) : string
|
||||
{
|
||||
global $datetime_formats;
|
||||
|
||||
if (in_array($this->view, ['month', 'year']))
|
||||
{
|
||||
return datetime_format(['pattern' => 'd'], $t);
|
||||
}
|
||||
else
|
||||
{
|
||||
return datetime_format($datetime_formats['view_week_day_month'], $t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getDay(int $t) : string
|
||||
{
|
||||
// In the month view use a pattern which will tend to give a narrower result, to save space.
|
||||
$pattern = ($this->view == 'month') ? 'cccccc' : 'ccc';
|
||||
|
||||
return datetime_format(['pattern' => $pattern], $t);
|
||||
}
|
||||
|
||||
|
||||
public static function morningSlotsSeconds() : int
|
||||
{
|
||||
global $morningstarts, $morningstarts_minutes;
|
||||
|
||||
return (($morningstarts * 60) + $morningstarts_minutes) * 60;
|
||||
}
|
||||
|
||||
|
||||
// Gets the number of time slots between the beginning and end of the booking
|
||||
// day. (This is the normal number on a non-DST transition day)
|
||||
public static function getNTimeSlots() : int
|
||||
{
|
||||
global $eveningends, $eveningends_minutes;
|
||||
global $resolution;
|
||||
|
||||
$start_first = self::morningSlotsSeconds(); // seconds
|
||||
$end_last = ((($eveningends * 60) + $eveningends_minutes) * 60) + $resolution; // seconds
|
||||
$end_last = $end_last % SECONDS_PER_DAY;
|
||||
if (day_past_midnight())
|
||||
{
|
||||
$end_last += SECONDS_PER_DAY;
|
||||
}
|
||||
|
||||
// Force the result to be an int. It normally will be, but might not be if, say,
|
||||
// $force_resolution is set.
|
||||
return intval(($end_last - $start_first)/$resolution);
|
||||
}
|
||||
|
||||
|
||||
// If we're not using periods, construct an array describing the slots to pass to the JavaScript so that
|
||||
// it can calculate where the timeline should be drawn. (If we are using periods then the timeline is
|
||||
// meaningless because we don't know when periods begin and end.)
|
||||
// $month, $day, $year the start of the interval
|
||||
// $n_days the number of days in the interval
|
||||
// $day_cells if the columns/rows represent a full day (as in the week/month all rooms views)
|
||||
protected function getSlots(int $month, int $day, int $year, int $n_days=1, bool $day_cells=false) : ?array
|
||||
{
|
||||
global $enable_periods, $morningstarts, $morningstarts_minutes, $resolution;
|
||||
|
||||
if ($enable_periods)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$slots = array();
|
||||
|
||||
$n_time_slots = self::getNTimeSlots();
|
||||
$morning_slot_seconds = self::morningSlotsSeconds();
|
||||
$evening_slot_seconds = $morning_slot_seconds + (($n_time_slots - 1) * $resolution);
|
||||
|
||||
for ($j = 0; $j < $n_days; $j++)
|
||||
{
|
||||
$d = $day + $j;
|
||||
|
||||
// If there's more than one day in the interval then don't include the hidden days in the array, because
|
||||
// they don't appear in the DOM. If there's only one day then we've managed to display the hidden day.
|
||||
if (($n_days > 1) &&
|
||||
is_hidden_day(intval(date('w', mktime($morningstarts, $morningstarts_minutes, 0, $month, $d, $year)))))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$this_day = array();
|
||||
|
||||
if ($day_cells)
|
||||
{
|
||||
$this_day[] = mktime(0, 0, $morning_slot_seconds, $month, $d, $year);
|
||||
// Need to do mktime() again for the end of the slot as we can't assume that the end slot is $resolution
|
||||
// seconds after the start of the slot because of the possibility of DST transitions
|
||||
$this_day[] = mktime(0, 0, $evening_slot_seconds + $resolution, $month, $d, $year);
|
||||
}
|
||||
else
|
||||
{
|
||||
for ($s = $morning_slot_seconds;
|
||||
$s <= $evening_slot_seconds;
|
||||
$s += $resolution)
|
||||
{
|
||||
$this_slot = array();
|
||||
$this_slot[] = mktime(0, 0, $s, $month, $d, $year);
|
||||
// Need to do mktime() again for the end of the slot as we can't assume that the end slot is $resolution
|
||||
// seconds after the start of the slot because of the possibility of DST transitions
|
||||
$this_slot[] = mktime(0, 0, $s + $resolution, $month, $d, $year);
|
||||
$this_day[] = $this_slot;
|
||||
}
|
||||
}
|
||||
$slots[] = $this_day;
|
||||
}
|
||||
|
||||
if ($day_cells)
|
||||
{
|
||||
$slots = array($slots);
|
||||
}
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
|
||||
// Get classes for weekends, holidays, etc.
|
||||
protected function getDateClasses(DateTime $date) : array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
if ($date->isWeekend())
|
||||
{
|
||||
$result[] = 'weekend';
|
||||
}
|
||||
if ($date->isHoliday())
|
||||
{
|
||||
$result[] = 'holiday';
|
||||
}
|
||||
if ($date->isToday())
|
||||
{
|
||||
$result[] = 'today';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// Returns an array of classes to be used for the entry
|
||||
protected function getEntryClasses(array $entry) : array
|
||||
{
|
||||
global $approval_enabled, $confirmation_enabled;
|
||||
|
||||
$classes = array($entry['type']);
|
||||
|
||||
if ($entry['private'])
|
||||
{
|
||||
$classes[] = 'private';
|
||||
}
|
||||
|
||||
if ($approval_enabled && ($entry['awaiting_approval']))
|
||||
{
|
||||
$classes[] = 'awaiting_approval';
|
||||
}
|
||||
|
||||
if ($confirmation_enabled && ($entry['tentative']))
|
||||
{
|
||||
$classes[] = 'tentative';
|
||||
}
|
||||
|
||||
if (isset($entry['repeat_id']))
|
||||
{
|
||||
$classes[] = 'series';
|
||||
}
|
||||
|
||||
if ($entry['allow_registration'])
|
||||
{
|
||||
if ($entry['registrant_limit_enabled'] &&
|
||||
($entry['n_registered'] >= $entry['registrant_limit']))
|
||||
{
|
||||
$classes[] = 'full';
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes[] = 'spaces';
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
|
||||
protected function multidayHeaderRowsHTML(int $day_start_interval, int $n_days, int $start_dow, string $label='') : array
|
||||
{
|
||||
global $row_labels_both_sides;
|
||||
|
||||
$result = array();
|
||||
$n_rows = 2;
|
||||
// Loop through twice: one row for the days of the week, the next for the date.
|
||||
for ($i = 0; $i < $n_rows; $i++)
|
||||
{
|
||||
$result[$i] = "<tr>\n";
|
||||
|
||||
// Could use a rowspan here, but we'd need to make sure the sticky cells work
|
||||
// and change the JavaScript in refresh.js.php
|
||||
$text = ($i == 0) ? '' : $label;
|
||||
$first_last_html = '<th class="first_last">' . escape_html($text) . "</th>\n";
|
||||
$result[$i] .= $first_last_html;
|
||||
|
||||
$vars = [
|
||||
'view' => 'day',
|
||||
'view_all' => $this->view_all,
|
||||
'area' => $this->area_id,
|
||||
'room' => $this->room_id
|
||||
];
|
||||
|
||||
// the standard view, with days along the top and rooms down the side
|
||||
for ($j = 0; $j < $n_days; $j++)
|
||||
{
|
||||
if (is_hidden_day(($j + $start_dow) % DAYS_PER_WEEK))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$vars['page_date'] = format_iso_date($this->year, $this->month, $day_start_interval + $j);
|
||||
$link = "index.php?" . http_build_query($vars, '', '&');
|
||||
$link = multisite($link);
|
||||
$t = mktime(12, 0, 0, $this->month, $day_start_interval + $j, $this->year);
|
||||
$text = ($i === 0) ? $this->getDay($t) : $this->getDate($t);
|
||||
$date = new DateTime();
|
||||
$date->setTimestamp($t);
|
||||
$classes = $this->getDateClasses($date);
|
||||
$result[$i] .= '<th' .
|
||||
// Add the date for JavaScript. Only really necessary for the first row in
|
||||
// the week view when not viewing all the rooms, but just add it always.
|
||||
' data-date="' . escape_html($date->getISODate()) . '"' .
|
||||
((!empty($classes)) ? ' class="' . implode(' ', $classes) . '"' : '') .
|
||||
'><a href="' . escape_html($link) . '">' . escape_html($text) . "</a></th>\n";
|
||||
}
|
||||
|
||||
// next line to display rooms on right side
|
||||
if ($row_labels_both_sides)
|
||||
{
|
||||
$result[$i] .= $first_last_html;
|
||||
}
|
||||
|
||||
$result[$i] .= "</tr>\n";
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// Draw a room cell to be used in the header rows/columns of the calendar views
|
||||
// $room contains the room details
|
||||
// $vars an associative array containing the variables to be used to build the link
|
||||
protected function roomCellHTML(array $room, array $vars) : string
|
||||
{
|
||||
$link = 'index.php?' . http_build_query($vars, '', '&');
|
||||
$link = multisite($link);
|
||||
|
||||
switch ($vars['view'])
|
||||
{
|
||||
case 'day':
|
||||
$tag = 'viewday';
|
||||
break;
|
||||
case 'week':
|
||||
$tag = 'viewweek';
|
||||
break;
|
||||
case 'month':
|
||||
$tag = 'viewmonth';
|
||||
break;
|
||||
case 'year':
|
||||
$tag = 'viewyear';
|
||||
break;
|
||||
default:
|
||||
trigger_error("Unknown view '" . $vars['view'] . "'", E_USER_NOTICE);
|
||||
$tag = 'viewweek';
|
||||
break;
|
||||
}
|
||||
|
||||
$title = get_vocab($tag) . "\n\n" . $room['description'];
|
||||
$html = '';
|
||||
$html .= '<th data-room="' . escape_html($room['id']) . '">';
|
||||
$html .= '<a href="' . escape_html($link) . '"' .
|
||||
' title = "' . escape_html($title) . '">';
|
||||
$html .= escape_html($room['room_name']);
|
||||
// Put the capacity in a span to give flexibility in styling
|
||||
$html .= '<span class="capacity';
|
||||
if ($room['capacity'] == 0)
|
||||
{
|
||||
$html .= ' zero';
|
||||
}
|
||||
$html .= '">' . escape_html($room['capacity']);
|
||||
$html .= '</span>';
|
||||
$html .= '</a>';
|
||||
$html .= "</th>\n";
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user