包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、JS空集合保护、 会话过期体验优化(403 JSON)、display_errors 关闭、 固定 key 根治 Integrity check failed 等全部改动 注意:config.inc.php/.htaccess/.user.ini 含敏感信息, 通过 .gitignore 排除,勿推送到公开仓库。
387 lines
12 KiB
PHP
387 lines
12 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace MRBS\Calendar;
|
|
|
|
use MRBS\DateTime;
|
|
use MRBS\Language;
|
|
use function MRBS\datetime_format;
|
|
use function MRBS\day_name;
|
|
use function MRBS\escape_html;
|
|
use function MRBS\get_end_last_slot;
|
|
use function MRBS\get_entries_by_room;
|
|
use function MRBS\get_room_name;
|
|
use function MRBS\get_start_first_slot;
|
|
use function MRBS\get_vocab;
|
|
use function MRBS\hour_min_format;
|
|
use function MRBS\is_book_admin;
|
|
use function MRBS\is_hidden_day;
|
|
use function MRBS\is_visible;
|
|
use function MRBS\is_weekend;
|
|
use function MRBS\multisite;
|
|
use function MRBS\period_time_string;
|
|
use function MRBS\session;
|
|
|
|
class CalendarMonthOneRoom extends Calendar
|
|
{
|
|
|
|
public function innerHTML(): string
|
|
{
|
|
global $weekstarts, $view_week_number, $show_plus_link, $monthly_view_entries_details;
|
|
global $enable_periods, $morningstarts, $morningstarts_minutes;
|
|
global $prevent_booking_on_holidays, $prevent_booking_on_weekends;
|
|
|
|
// Check that we've got a valid, enabled room
|
|
if (is_null(get_room_name($this->room_id)) || !is_visible($this->room_id))
|
|
{
|
|
// No rooms have been created yet, or else they are all disabled
|
|
// 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>";
|
|
}
|
|
|
|
$html = '';
|
|
|
|
// Month view start time. This ignores morningstarts/eveningends because it
|
|
// doesn't make sense to not show all entries for the day, and it messes
|
|
// things up when entries cross midnight.
|
|
$month_start = mktime(0, 0, 0, $this->month, 1, $this->year);
|
|
// What column the month starts in: 0 means $weekstarts weekday.
|
|
$weekday_start = (date("w", $month_start) - $weekstarts + DAYS_PER_WEEK) % DAYS_PER_WEEK;
|
|
$last_day_of_month = (int) date("t", $month_start);
|
|
|
|
$html .= $this->theadHTML();
|
|
|
|
// Main body
|
|
$html .= "<tbody>\n";
|
|
$html .= "<tr>\n";
|
|
|
|
// Skip days in week before the start of the month:
|
|
for ($weekcol = 0; $weekcol < $weekday_start; $weekcol++)
|
|
{
|
|
$html .= $this->tdBlankDayHTML($weekcol);
|
|
}
|
|
|
|
$start_date = (new DateTime())->setTimestamp(get_start_first_slot($this->month, 1, $this->year));
|
|
$end_date = (new DateTime())->setTimestamp(get_end_last_slot($this->month, $last_day_of_month, $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_room($this->room_id, $start_date, $end_date);
|
|
|
|
// Draw the days of the month:
|
|
for ($d = 1, $date = clone $start_date; $d <= $last_day_of_month; $d++, $date->modify('+1 day'))
|
|
{
|
|
// Get the slot times
|
|
$start_first_slot = get_start_first_slot($this->month, $d, $this->year);
|
|
$end_last_slot = get_end_last_slot($this->month, $d, $this->year);
|
|
|
|
// if we're at the start of the week (and it's not the first week), start a new row
|
|
if (($weekcol == 0) && ($d > 1))
|
|
{
|
|
$html .= "</tr><tr>\n";
|
|
}
|
|
|
|
// output the day cell
|
|
if ($date->isHiddenDay())
|
|
{
|
|
// These days are to be hidden in the display (as they are hidden, just give the
|
|
// day of the week in the header row)
|
|
$html .= "<td class=\"hidden_day\">\n";
|
|
$html .= "<div class=\"cell_container\">\n";
|
|
$html .= "<div class=\"cell_header\">\n";
|
|
// first put in the day of the month
|
|
$html .= "<span>$d</span>\n";
|
|
$html .= "</div>\n";
|
|
$html .= "</div>\n";
|
|
$html .= "</td>\n";
|
|
}
|
|
else
|
|
{
|
|
// Add classes for weekends and holidays
|
|
$classes = $this->getDateClasses($date);
|
|
|
|
$html .= '<td' . ((empty($classes)) ? '' : ' class="' . implode(' ', $classes) . '"') . ">\n";
|
|
$html .= "<div class=\"cell_container\">\n";
|
|
|
|
$html .= "<div class=\"cell_header\">\n";
|
|
|
|
$vars = [
|
|
'page_date' => $date->getISODate(),
|
|
'area' => $this->area_id,
|
|
'room' => $this->room_id
|
|
];
|
|
|
|
// If it's the first day of the week, show the week number
|
|
if ($view_week_number && $date->isFirstDayOfWeek(Language::getInstance()->getWebLocale()))
|
|
{
|
|
$vars['view'] = 'week';
|
|
$query = http_build_query($vars, '', '&');
|
|
$html .= '<a class="week_number" href="' . escape_html(multisite("index.php?$query")) . '">';
|
|
$html .= $date->format('W');
|
|
$html .= "</a>\n";
|
|
}
|
|
// then put in the day of the month
|
|
$vars['view'] = 'day';
|
|
$query = http_build_query($vars, '', '&');
|
|
$html .= '<a class="monthday" href="' . escape_html(multisite("index.php?$query")) . "\">$d</a>\n";
|
|
|
|
$html .= "</div>\n";
|
|
|
|
// Then the link to make a new booking.
|
|
// 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 ((null !== session()->getCurrentUser()) && !is_book_admin($this->room_id) &&
|
|
(($prevent_booking_on_holidays && in_array('holiday', $classes)) ||
|
|
($prevent_booking_on_weekends && in_array('weekend', $classes))))
|
|
{
|
|
$html .= '<span class="not_allowed"></span>';
|
|
}
|
|
else
|
|
{
|
|
$vars['view'] = $this->view;
|
|
|
|
if ($enable_periods)
|
|
{
|
|
$vars['period'] = 0;
|
|
}
|
|
else
|
|
{
|
|
$vars['hour'] = $morningstarts;
|
|
$vars['minute'] = $morningstarts_minutes;
|
|
}
|
|
|
|
$query = http_build_query($vars, '', '&');
|
|
|
|
$html .= '<a class="new_booking" href="' . escape_html(multisite("edit_entry.php?$query")) . '"' .
|
|
' aria-label="' . escape_html(get_vocab('create_new_booking')) . "\">\n";
|
|
if ($show_plus_link)
|
|
{
|
|
$html .= "<img src=\"images/new.gif\" alt=\"New\" width=\"10\" height=\"10\">\n";
|
|
}
|
|
$html .= "</a>\n";
|
|
}
|
|
|
|
// then any bookings for the day
|
|
$html .= "<div class=\"booking_list\">\n";
|
|
// Show the start/stop times, 1 or 2 per line, linked to view_entry.
|
|
foreach ($entries as $entry)
|
|
{
|
|
// We are only interested in this day's entries
|
|
if (($entry['start_time'] >= $end_last_slot) ||
|
|
($entry['end_time'] <= $start_first_slot))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$entry = Map::prepareEntry($entry);
|
|
|
|
$classes = $this->getEntryClasses($entry);
|
|
$classes[] = $monthly_view_entries_details;
|
|
|
|
$html .= '<div class="' . implode(' ', $classes) . '">';
|
|
|
|
$vars = [
|
|
'id' => $entry['id'],
|
|
'year' => $this->year,
|
|
'month' => $this->month,
|
|
'day' => $d
|
|
];
|
|
|
|
$query = http_build_query($vars, '', '&');
|
|
$booking_link = multisite("view_entry.php?$query");
|
|
$slot_text = $this->bookingSummaryHTML(
|
|
$entry['start_time'],
|
|
$entry['end_time'],
|
|
$start_first_slot,
|
|
$end_last_slot
|
|
);
|
|
$description_text = mb_substr($entry['name'], 0, 255);
|
|
$full_text = $slot_text . " " . $description_text;
|
|
switch ($monthly_view_entries_details)
|
|
{
|
|
case "description":
|
|
{
|
|
$display_text = $description_text;
|
|
break;
|
|
}
|
|
case "slot":
|
|
{
|
|
$display_text = $slot_text;
|
|
break;
|
|
}
|
|
case "both":
|
|
{
|
|
$display_text = $full_text;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
$html .= "error: unknown parameter";
|
|
}
|
|
}
|
|
$title_text = $full_text;
|
|
if (isset($entry['description']) && ($entry['description'] !== ''))
|
|
{
|
|
$title_text .= "\n\n" . $entry['description'];
|
|
}
|
|
$html .= '<a href="' . escape_html($booking_link) . '"' .
|
|
' title="' . escape_html($title_text) . '">';
|
|
$html .= escape_html($display_text) . '</a>';
|
|
$html .= "</div>\n";
|
|
}
|
|
$html .= "</div>\n";
|
|
|
|
$html .= "</div>\n";
|
|
$html .= "</td>\n";
|
|
}
|
|
|
|
// increment the day of the week counter
|
|
if (++$weekcol == DAYS_PER_WEEK)
|
|
{
|
|
$weekcol = 0;
|
|
}
|
|
|
|
} // end of for loop going through valid days of the month
|
|
|
|
// Skip from end of month to end of week:
|
|
if ($weekcol > 0)
|
|
{
|
|
for (; $weekcol < DAYS_PER_WEEK; $weekcol++)
|
|
{
|
|
$html .= $this->tdBlankDayHTML($weekcol);
|
|
}
|
|
}
|
|
|
|
$html .= "</tr>\n";
|
|
$html .= "</tbody>\n";
|
|
|
|
return $html;
|
|
}
|
|
|
|
|
|
// Describe the start and end time, accounting for "all day"
|
|
// and for entries starting before/ending after today.
|
|
// There are 9 cases, for start time < = or > midnight this morning,
|
|
// and end time < = or > midnight tonight.
|
|
private function bookingSummaryHTML(int $start, int $end, int $day_start, int $day_end) : string
|
|
{
|
|
global $enable_periods, $area;
|
|
|
|
// Use ~ (not -) to separate the start and stop times, because MSIE
|
|
// will incorrectly line break after a -.
|
|
$separator = '~';
|
|
$after_today = "==>";
|
|
$before_today = "<==";
|
|
$midnight = "24:00"; // need to fix this so it works with AM/PM configurations (and for that matter 24h)
|
|
$all_day = get_vocab('all_day');
|
|
|
|
if ($enable_periods)
|
|
{
|
|
$start_str = escape_html(period_time_string($start, $area));
|
|
$end_str = escape_html(period_time_string($end, $area, true));
|
|
}
|
|
else
|
|
{
|
|
$start_str = escape_html(datetime_format(hour_min_format(), $start));
|
|
$end_str = escape_html(datetime_format(hour_min_format(), $end));
|
|
}
|
|
|
|
switch (self::cmp3($start, $day_start) . self::cmp3($end, $day_end + 1))
|
|
{
|
|
case "> < ": // Starts after midnight, ends before midnight
|
|
case "= < ": // Starts at midnight, ends before midnight
|
|
$result = $start_str;
|
|
// Don't bother showing the end if it's the same as the start period
|
|
if ($end_str !== $start_str)
|
|
{
|
|
$result .= $separator . $end_str;
|
|
}
|
|
break;
|
|
case "> = ": // Starts after midnight, ends at midnight
|
|
$result = $start_str . $separator . $midnight;
|
|
break;
|
|
case "> > ": // Starts after midnight, continues tomorrow
|
|
$result = $start_str . $separator . $after_today;
|
|
break;
|
|
case "= = ": // Starts at midnight, ends at midnight
|
|
$result = $all_day;
|
|
break;
|
|
case "= > ": // Starts at midnight, continues tomorrow
|
|
$result = $all_day . $after_today;
|
|
break;
|
|
case "< < ": // Starts before today, ends before midnight
|
|
$result = $before_today . $separator . $end_str;
|
|
break;
|
|
case "< = ": // Starts before today, ends at midnight
|
|
$result = $before_today . $all_day;
|
|
break;
|
|
case "< > ": // Starts before today, continues tomorrow
|
|
$result = $before_today . $all_day . $after_today;
|
|
break;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
// 3-value compare: Returns result of compare as "< " "= " or "> ".
|
|
private static function cmp3(int $a, int $b) : string
|
|
{
|
|
if ($a < $b)
|
|
{
|
|
return "< ";
|
|
}
|
|
if ($a == $b)
|
|
{
|
|
return "= ";
|
|
}
|
|
return "> ";
|
|
}
|
|
|
|
|
|
private function tdBlankDayHTML(int $col) : string
|
|
{
|
|
global $weekstarts;
|
|
|
|
$td_class = (is_hidden_day(($col + $weekstarts) % DAYS_PER_WEEK)) ? 'hidden_day' : 'invalid';
|
|
return "<td class=\"$td_class\"><div class=\"cell_container\"> </div></td>\n";
|
|
}
|
|
|
|
|
|
// Gets the table head for the single room month view
|
|
private function theadHTML() : string
|
|
{
|
|
global $weekstarts;
|
|
|
|
$html = '';
|
|
|
|
// Weekday name header row:
|
|
$html .= "<thead>\n";
|
|
$html .= "<tr>\n";
|
|
for ($i = 0; $i< DAYS_PER_WEEK; $i++)
|
|
{
|
|
$classes = [];
|
|
$dow = ($i + $weekstarts) % DAYS_PER_WEEK;
|
|
if (is_hidden_day($dow))
|
|
{
|
|
$classes[] = 'hidden_day';
|
|
}
|
|
if (is_weekend($dow))
|
|
{
|
|
$classes[] = 'weekend';
|
|
}
|
|
$html .= '<th';
|
|
if (!empty($classes))
|
|
{
|
|
$html .= ' class="' . implode(' ', $classes) . '"';
|
|
}
|
|
$html .= '>' . day_name(($i + $weekstarts)%DAYS_PER_WEEK) . '</th>';
|
|
}
|
|
$html .= "\n</tr>\n";
|
|
$html .= "</thead>\n";
|
|
|
|
return $html;
|
|
}
|
|
|
|
}
|