Files
mrbs-equbao-2026/lib/MRBS/ICalendar/Series.php
T
人事系统开发 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

194 lines
6.1 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS\ICalendar;
use MRBS\DateTime;
use MRBS\Exception;
use MRBS\RepeatRule;
use function MRBS\entry_has_registrants;
use function MRBS\get_repeat;
require_once MRBS_ROOT . '/mrbs_sql.inc';
class Series
{
public $repeat_id;
private $data;
private $repeat;
private $expected_start_times;
private $actual_start_times;
private $tzid;
// Constructs a new Series object and adds $row to it.
// $limit is the limiting UNIX timestamp for the series. This may be before the actual end
// of the series. Defaults to null, ie no limit. This enables the series extract to be truncated.
public function __construct(array $row, ?string $tzid=null, ?int $limit=null)
{
$this->tzid = $tzid;
$row = self::fixUpRow($row);
$this->data = array();
$this->repeat_id = $row['repeat_id'];
// Get the repeat data and save it, so that we can construct the repeat event later
$this->repeat = get_repeat($this->repeat_id);
if (!isset($this->repeat))
{
throw new Exception("Repeat data not available");
}
// Add in the area and room names, which we can get from this row
$this->repeat['area_name'] = $row['area_name'];
$this->repeat['room_name'] = $row['room_name'];
// Copy the registration settings from this row (they won't necessarily be correct if
// this is a changed entry, but we'll look out for an original row later)
// TODO: the registration settings should really be in the repeat table to begin with
$this->repeat = self::copyRegistrationSettings($this->repeat, $row);
$this->repeat['entry_type'] = $row['entry_type'];
// Limit the series (before we create the repeat rule)
if (isset($limit))
{
$this->repeat['end_date'] = min($limit, $this->repeat['end_date']);
}
// Create the repeat rule
$this->repeat = self::addRepeatRule($this->repeat);
// Construct an array of the start times we'd expect to see in this series so that
// we can check whether any are missing.
$this->expected_start_times = $this->repeat['repeat_rule']->getRepeatStartTimes($this->repeat['start_time']);
// And keep an array of all the start times we actually see
$this->actual_start_times = array();
// And finally add the row to the series
$this->addRow($row);
}
// Add a row to the series
public function addRow(array $row)
{
$row = self::fixUpRow($row);
// Add the row to the data array
$this->data[] = $row;
// If this is an original entry, add its start time to the array of ones we've seen
if ($row['entry_type'] == ENTRY_RPT_ORIGINAL)
{
$this->actual_start_times[] = $row['start_time'];
}
// And if we haven't yet seen an original row, and this is one, then grab
// the registration settings. (If we never see an original row it doesn't matter,
// because all the rows will be changed rows and have their own registration settings.)
if (($this->repeat['entry_type'] == ENTRY_RPT_CHANGED) &&
($row['entry_type'] == ENTRY_RPT_ORIGINAL))
{
$this->repeat = self::copyRegistrationSettings($this->repeat, $row);
$this->repeat['entry_type'] = ENTRY_RPT_ORIGINAL;
}
}
/**
* Convert the series to an array of iCalendar events
*
* @param string $method the METHOD, eg 'PUBLISH'
* @return Event[]
*/
public function toEvents(string $method) : array
{
$events = array();
$this->repeat['skip_list'] = array_diff($this->expected_start_times, $this->actual_start_times);
$events = array_merge($events, Event::createFromData($method, $this->repeat, $this->tzid, null, true));
// Then iterate through the series looking for changed entries
foreach($this->data as $entry)
{
if ($entry['entry_type'] == ENTRY_RPT_CHANGED)
{
$events = array_merge($events, Event::createFromData($method, $entry, $this->tzid));
}
}
return $events;
}
// Temporary fix-up to add a repeat_rule key to the $row array
// TODO: fix this properly
private static function addRepeatRule(array $row) : array
{
// Construct the repeat rule and add it to the row
$repeat_rule = new RepeatRule();
$repeat_rule->setType((int)$row['rep_type']);
$repeat_rule->setInterval((int)$row['rep_interval']);
$repeat_end_date = new DateTime();
$repeat_end_date->setTimestamp((int)$row['end_date']);
$repeat_rule->setEndDate($repeat_end_date);
$repeat_rule->setDaysFromOpt($row['rep_opt']);
if ($repeat_rule->getType() == RepeatRule::MONTHLY)
{
if (isset($row['month_absolute'])) {
$repeat_rule->setMonthlyAbsolute($row['month_absolute']);
$repeat_rule->setMonthlyType(RepeatRule::MONTHLY_ABSOLUTE);
}
elseif (isset($row['month_relative'])) {
$repeat_rule->setMonthlyRelative($row['month_relative']);
$repeat_rule->setMonthlyType(RepeatRule::MONTHLY_RELATIVE);
}
else {
throw new Exception("The repeat type is monthly but both the absolute and relative days are null.");
}
}
$row['repeat_rule'] = $repeat_rule;
return $row;
}
private static function copyRegistrationSettings(array $to, array $from) : array
{
$keys = array(
'allow_registration',
'registrant_limit',
'registrant_limit_enabled',
'registration_opens',
'registration_opens_enabled',
'registration_closes',
'registration_closes_enabled'
);
foreach ($keys as $key)
{
$to[$key] = $from[$key];
}
return $to;
}
private static function fixUpRow(array $row) : array
{
// Temporary fix-up
$row = self::addRepeatRule($row);
// Another fix-up: if the entry has registrants then treat it like
// a changed entry so that it appears as individual event in the calendar
// and can therefore have registrants associated with it.
// TODO: fix this properly. (Maybe entries with registrants should be
// TODO: ENTRY_RPT_CHANGED in the database in the first place? That would
// TODO: also solve the problem of not being able to edit series with registrants.)
if (entry_has_registrants($row['id']))
{
$row['entry_type'] = ENTRY_RPT_CHANGED;
}
return $row;
}
}