Files
人事系统开发 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

1822 lines
60 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
use MRBS\Errors\Errors;
use MRBS\Form\ElementDiv;
use MRBS\Form\ElementFieldset;
use MRBS\Form\ElementInputCheckbox;
use MRBS\Form\ElementInputDate;
use MRBS\Form\ElementInputHidden;
use MRBS\Form\ElementInputRadio;
use MRBS\Form\ElementInputSubmit;
use MRBS\Form\ElementLabel;
use MRBS\Form\ElementSelect;
use MRBS\Form\ElementSpan;
use MRBS\Form\Field;
use MRBS\Form\FieldDiv;
use MRBS\Form\FieldInputCheckbox;
use MRBS\Form\FieldInputCheckboxGroup;
use MRBS\Form\FieldInputDate;
use MRBS\Form\FieldInputNumber;
use MRBS\Form\FieldInputRadioGroup;
use MRBS\Form\FieldSelect;
use MRBS\Form\FieldTimeWithUnits;
use MRBS\Form\Form;
use MRBS\ICalendar\RFC5545;
// If you want to add some extra columns to the entry and repeat tables to
// record extra details about bookings then you can do so and this page should
// automatically recognise them and handle them. NOTE: if you add a column to
// the entry table you must add an identical column to the repeat table.
//
// At the moment support is limited to the following column types:
//
// MySQL PostgreSQL Form input type
// ----- ---------- ---------------
// bigint bigint number
// int integer number
// mediumint number
// smallint smallint checkbox
// tinyint checkbox
// decimal decimal number
// numeric numeric number
// text text textarea
// tinytext textarea
// character varying textarea
// varchar(n) character varying(n) text/textarea, depending on the value of n
// character text
// char(n) character(n) text/textarea, depending on the value of n
//
// NOTE 1: For char(n) and varchar(n) fields, a text input will be presented if
// n is less than or equal to $text_input_max, otherwise a textarea box will be
// presented.
//
// NOTE 2: PostgreSQL booleans are not supported, due to difficulties in
// handling the fields in a database independent way (a PostgreSQL boolean
// will return a PHP boolean type when read by a PHP query, whereas a MySQL
// tinyint returns an int). In order to have a boolean field in the room
// table you should use a smallint in PostgreSQL or a smallint or a tinyint
// in MySQL.
//
// You can put a description of the column that will be used as the label in
// the form in the $vocab_override variable in the config file using the tag
// 'entry.[columnname]'. (Note that it is not necessary to add a
// 'repeat.[columnname]' tag. The entry tag is sufficient.)
//
// For example if you want to add a column recording the number of participants
// you could add a column to the entry and repeat tables called 'participants'
// of type int. Then in the appropriate lang file(s) you would add the line
//
// $vocab_override['en']['entry.participants'] = "Participants"; // or appropriate translation
//
// If MRBS can't find an entry for the field in the lang file or $vocab_override,
// then it will use the fieldname, eg 'coffee_machine'.
require 'defaultincludes.inc';
require_once 'mrbs_sql.inc';
require_once 'functions_mail.inc';
$fields = db()->field_info(_tbl('entry'));
$custom_fields = array();
// Fill $edit_entry_field_order with not yet specified entries.
$entry_fields = array('create_by', 'name', 'description', 'start_time', 'end_time', 'room_id',
'type', 'confirmation_status', 'privacy_status');
foreach ($entry_fields as $field)
{
if (!in_array($field, $edit_entry_field_order))
{
$edit_entry_field_order[] = $field;
}
}
$custom_fields_map = array();
foreach ($fields as $field)
{
$key = $field['name'];
if (!in_array($key, $standard_fields['entry']))
{
$custom_fields_map[$key] = $field;
if (!in_array($key, $edit_entry_field_order))
{
$edit_entry_field_order[] = $key;
}
}
}
function get_field_create_by(string $create_by, bool $disabled=false) : Field
{
$params = array('label' => get_vocab('createdby'),
'name' => 'create_by',
'field' => 'entry.create_by',
'value' => $create_by,
'required' => true,
'disabled' => $disabled);
return get_user_field($params);
}
function get_field_name(string $value, bool $disabled=false) : Field
{
$params = array('label' => get_vocab('namebooker'),
'name' => 'name',
'field' => 'entry.name',
'value' => $value,
'required' => true,
'disabled' => $disabled);
return get_field_entry_input($params);
}
function get_field_description(string $value, int $area_id, bool $disabled=false) : Field
{
$params = array('label' => get_vocab('fulldescription'),
'name' => 'description',
'field' => 'entry.description',
'value' => $value,
'required' => is_mandatory_field('entry.description', $area_id),
'disabled' => $disabled);
return get_field_entry_input($params);
}
// Generate a time or period selector starting with $first and ending with $last.
// $time is a full Unix timestamp and is the current value. The selector returns
// the start time in seconds since the beginning of the day for the start of that slot.
// Note that these are nominal seconds and do not take account of any DST changes that
// may have happened earlier in the day. (It's this way because we don't know what day
// it is as that's controlled by the date selector - and we can't assume that we have
// JavaScript enabled to go and read it)
//
// $display_none parameter sets the display style of the <select> to "none"
// $disabled parameter disables the input and also generate a hidden input, provided
// that $display_none is FALSE. (This prevents multiple inputs
// of the same name)
// $is_start Boolean. Whether this is the start selector. Default FALSE
function get_slot_selector(array $area, string $id, string $name, int $current_s, bool $display_none=false, bool $disabled=false, bool $is_start=false) : ElementSelect
{
// Check that $resolution is positive to avoid an infinite loop below.
// (Shouldn't be possible, but just in case ...)
if (empty($area['resolution']) || ($area['resolution'] < 0))
{
throw new \Exception("Internal error - resolution is NULL or <= 0");
}
// Build the options
$options = array();
$first = $area['first'];
// If we're using periods then the last slot is actually the start of the last period,
// or if we're using times and this is the start selector, then we don't show the last
// time
if ($area['enable_periods'] || $is_start)
{
$last = $area['last'] - $area['resolution'];
}
else
{
$last = $area['last'];
}
for ($s = $first; $s <= $last; $s += $area['resolution'])
{
$options[$s] = ($area['enable_periods']) ? $area['periods']->offsetGetByNominalSeconds($s)->name : hour_min($s);
}
// Make sure that the selected option is within the range of available options.
$selected = max($current_s, $first);
$selected = min($selected, $last);
$field = new ElementSelect();
$field->setAttributes(array('id' => $id,
'name' => $name,
'disabled' => $disabled || $display_none))
->addSelectOptions($options, $selected, true);
if ($disabled)
{
// If $disabled is set, give the element a class so that the JavaScript
// knows to keep it disabled
$field->addClass('keep_disabled');
}
if ($display_none)
{
$field->addClass('none');
}
if ($disabled && !$display_none)
{
$hidden = new ElementInputHidden();
$hidden->setAttributes(array('name' => $name,
'value' => $selected));
$field->next($hidden);
}
return $field;
}
// Generate the All Day checkbox for an area
function get_all_day(array $area, string $input_id, string $input_name, bool $display_none=false, bool $disabled=false) : ElementDiv
{
global $drag, $id;
$element = new ElementDiv();
if ($display_none || !$area['show_all_day'])
{
$element->addClass('none');
}
// (1) If $display_none or $disabled are set then we'll also disable the select so
// that there is only one select passing through the variable to the handler.
// (2) If this is an existing booking that we are editing or copying, then we do
// not want the default duration applied
$disable_field = $disabled || $display_none || !$area['show_all_day'];
$checkbox = new ElementInputCheckbox();
$checkbox->setAttributes(array('name' => $input_name,
'id' => $input_id,
'data-show' => ($area['show_all_day']) ? '1' : '0',
'disabled' => $disable_field))
->setChecked($area['default_duration_all_day'] && !isset($id) && !$drag);
if ($disable_field)
{
// and if $disabled is set, give the element a class so that the JavaScript
// knows to keep it disabled
$checkbox->addClass('keep_disabled');
}
$label = new ElementLabel();
$label->setText(get_vocab('all_day'))
->setAttribute('class', 'no_suffix');
$label->addElement($checkbox);
$element->addElement($label);
return $element;
}
function get_field_start_time(int $value, bool $disabled=false) : FieldDiv
{
global $areas, $area_id;
$date = getbookingdate($value);
$start_date = format_iso_date($date['year'], $date['mon'], $date['mday']);
$current_s = (($date['hours'] * 60) + $date['minutes']) * 60;
$label = get_vocab('start');
$field = new FieldDiv();
// Generate the live slot selector and all day checkbox
$element_date = new ElementInputDate();
$element_date->setAttributes(array(
'id' => 'start_date',
'name' => 'start_date',
'value' => $start_date,
'aria-label' => $label,
'disabled' => $disabled,
'required' => true)
);
$field->setAttribute('class', 'start_end')
->setLabel($label)
->addControlElement($element_date)
->addControlElement(get_slot_selector($areas[$area_id],
'start_seconds',
'start_seconds',
$current_s,
false,
$disabled,
true))
->addControlElement(get_all_day($areas[$area_id],
'all_day',
'all_day',
false,
$disabled));
// Generate the templates for each area
foreach ($areas as $a)
{
$field->addControlElement(get_slot_selector($a,
'start_seconds' . $a['id'],
'start_seconds',
$current_s,
true,
true,
true))
->addControlElement(get_all_day($a,
'all_day' . $a['id'],
'all_day',
true,
true));
}
return $field;
}
function get_field_end_time(int $value, bool $disabled=false) : FieldDiv
{
global $areas, $area_id;
global $multiday_allowed;
$date = getbookingdate($value, true);
$end_date = format_iso_date($date['year'], $date['mon'], $date['mday']);
$current_s = (($date['hours'] * 60) + $date['minutes']) * 60;
$label = get_vocab('end');
$field = new FieldDiv();
// Generate the live slot selector
// If we're using periods the booking model is slightly different,
// so subtract one period because the "end" period is actually the beginning
// of the last period booked
$element_date = new ElementInputDate();
$element_date->setAttributes(array(
'id' => 'end_date',
'name' => 'end_date',
'value' => $end_date,
'aria-label' => $label,
'disabled' => $disabled,
'required' => true
));
// Don't show the end date if multi-day bookings are not allowed
if (!$multiday_allowed)
{
$element_date->setAttribute('disabled', true)
->addClass('hidden_field');
}
$a = $areas[$area_id];
$this_current_s = ($a['enable_periods']) ? $current_s - $a['resolution'] : $current_s;
$field->setAttribute('class', 'start_end')
->setLabel($label)
->addControlElement($element_date)
->addControlElement(get_slot_selector($areas[$area_id],
'end_seconds',
'end_seconds',
$this_current_s,
false,
$disabled,
false));
// Generate the templates
foreach ($areas as $a)
{
$this_current_s = ($a['enable_periods']) ? $current_s - $a['resolution'] : $current_s;
$field->addControlElement(get_slot_selector($a,
'end_seconds' . $a['id'],
'end_seconds',
$this_current_s,
true,
true,
false));
}
// An empty <span> to hold JavaScript messages
$span = new ElementSpan();
$span->setAttributes(array('id' => 'end_time_error',
'class' => 'error'));
$field->addControlElement($span);
return $field;
}
function get_field_areas(int $value, bool $disabled=false) : ?FieldSelect
{
global $areas;
// No point in being able to choose an area if there aren't more
// than one of them.
if (count($areas) < 2)
{
return null;
}
$field = new FieldSelect();
$options = array();
// go through the areas and create the options
foreach ($areas as $a)
{
$options[$a['id']] = $a['area_name'];
}
// We will set the display to none and then turn it on in the JavaScript. That's
// because if there's no JavaScript we don't want to display it because we won't
// have any means of changing the rooms if the area is changed.
$field->setAttributes(array('id' => 'div_areas'))
->addClass('none')
->setLabel(get_vocab('area'))
->setControlAttributes(array('name' => 'area',
'disabled' => $disabled))
->addSelectOptions($options, $value, true);
return $field;
}
// $value can be a scalar or an array
function get_field_rooms($value, bool $disabled=false) : FieldSelect
{
global $multiroom_allowed, $area_id, $areas, $rooms;
// First of all generate the rooms for this area
$field = new FieldSelect();
$field->setLabel(get_vocab('rooms'));
// No point telling them how to select multiple rooms if the input
// is disabled or they aren't allowed to
if ($multiroom_allowed && !$disabled)
{
$field->setLabelAttribute('title', get_vocab('ctrl_click'));
}
$field->setAttributes(array('class' => 'multiline',
'id' => 'div_rooms'))
->setControlAttributes(array('id' => 'rooms',
'name' => 'rooms[]',
'multiple' => $multiroom_allowed, // If multiple is not set then required is unnecessary
'required' => $multiroom_allowed, // and also causes an HTML5 validation error
'disabled' => $disabled,
'size' => '5'))
->addSelectOptions($rooms[$area_id], $value, true);
// Then generate templates for all the rooms
foreach ($rooms as $a => $area_rooms)
{
$room_ids = array_keys($area_rooms);
$select = new ElementSelect();
$select->setAttributes(array('id' => 'rooms' . $a,
'name' => 'rooms[]',
'multiple' => $multiroom_allowed, // If multiple is not set then required is unnecessary
'required' => $multiroom_allowed, // and also causes an HTML5 validation error
'disabled' => true,
'size' => '5'))
->addClass('none')
->addSelectOptions($area_rooms, $room_ids[0], true);
// Put in some data about the area for use by the JavaScript
$select->setAttributes(array(
'data-enable_periods' => ($areas[$a]['enable_periods']) ? 1 : 0,
'data-n_periods' => count($areas[$a]['periods']),
'data-default_duration' => (isset($areas[$a]['default_duration']) && ($areas[$a]['default_duration'] != 0)) ? $areas[$a]['default_duration'] : SECONDS_PER_HOUR,
'data-default_duration_all_day' => ($areas[$a]['default_duration_all_day']) ? 1 : 0,
'data-max_duration_enabled' => ($areas[$a]['max_duration_enabled']) ? 1 : 0,
'data-max_duration_secs' => $areas[$a]['max_duration_secs'],
'data-max_duration_periods' => $areas[$a]['max_duration_periods'],
'data-max_duration_qty' => $areas[$a]['max_duration_qty'],
'data-max_duration_units' => $areas[$a]['max_duration_units'],
'data-timezone' => $areas[$a]['timezone']
));
$field->addElement($select);
} // foreach
return $field;
}
function get_field_type(string $value, int $area_id, bool $disabled=false) : ?FieldSelect
{
// Get the options
$options = get_type_options(is_book_admin());
// Don't bother with types if there's only one of them (or even none)
// for the current user.
if (count($options) < 2)
{
return null;
}
$required = is_mandatory_field('entry.type', $area_id);
// If it's a mandatory field, add a blank option to force a selection
if ($required)
{
$options = array('' => get_type_vocab('')) + $options;
}
$field = new FieldSelect();
$field->setLabel(get_vocab('type'))
->setControlAttributes(array('name' => 'type',
'disabled' => $disabled,
'required' => $required))
->addSelectOptions($options, $value, true);
return $field;
}
function get_field_confirmation_status(bool $value, bool $disabled=false) : ?FieldInputRadioGroup
{
global $confirmation_enabled;
if (!$confirmation_enabled)
{
return null;
}
$options = array(0 => get_vocab('tentative'),
1 => get_vocab('confirmed'));
$value = ($value) ? 0 : 1;
$field = new FieldInputRadioGroup();
$field->setLabel(get_vocab('confirmation_status'))
->addRadioOptions($options, 'confirmed', $value, true, $disabled);
return $field;
}
function get_field_privacy_status(bool $value, bool $disabled=false) : ?FieldInputRadioGroup
{
global $private_enabled, $private_mandatory;
if (!$private_enabled)
{
return null;
}
$options = array(0 => get_vocab('public'),
1 => get_vocab('private'));
$value = ($value) ? 1 : 0;
// Admins are allowed to do what they want
$disable_field = (!is_book_admin() && $private_mandatory) || $disabled;
$field = new FieldInputRadioGroup();
$field->setLabel(get_vocab('privacy_status'))
->addRadioOptions($options, 'private', $value, true, $disable_field);
return $field;
}
function get_field_custom(string $key, int $area_id, bool $disabled=false)
{
global $custom_fields, $custom_fields_map;
// TODO: have a common way of generating custom fields for all tables
// First check that the custom field exists. It normally will, but won't if
// $edit_entry_field_order contains a value for which a field doesn't exist.
if (!isset($custom_fields_map[$key]))
{
return null;
}
$custom_field = $custom_fields_map[$key];
$required = is_mandatory_field('entry.' . $key, $area_id);
// Output a checkbox if it's a boolean or integer <= 2 bytes (which we will
// assume are intended to be booleans)
if (($custom_field['nature'] == 'boolean') ||
(($custom_field['nature'] == 'integer') && isset($custom_field['length']) && ($custom_field['length'] <= 2)) )
{
$class = 'FieldInputCheckbox';
}
elseif ($custom_field['type'] == 'date')
{
$class = 'FieldInputDate';
}
// Otherwise check if it's an integer field
elseif ((($custom_field['nature'] == 'integer') && ($custom_field['length'] > 2)) ||
($custom_field['nature'] == 'decimal'))
{
$class = 'FieldInputNumber';
}
// Otherwise it's a text input of some kind (which includes <select>s,
// <datalist>s and <textarea>s)
else
{
$params = array('label' => get_loc_field_name(_tbl('entry'), $key),
'name' => VAR_PREFIX . $key,
'field' => "entry.$key",
'value' => (isset($custom_fields[$key])) ? $custom_fields[$key] : NULL,
'required' => $required,
'disabled' => $disabled);
return get_field_entry_input($params);
}
$full_class = __NAMESPACE__ . "\\Form\\$class";
$field = new $full_class();
$field->setLabel(get_loc_field_name(_tbl('entry'), $key))
->setControlAttributes(array('name' => VAR_PREFIX . $key,
'disabled' => $disabled,
'required' => $required));
if ($custom_field['nature'] == 'decimal')
{
list( , $decimal_places) = explode(',', $custom_field['length']);
$decimal_places = (int) $decimal_places;
$step = pow(10, -$decimal_places);
$step = number_format($step, $decimal_places);
$field->setControlAttribute('step', $step);
}
if ($class == 'FieldTextarea')
{
if (isset($custom_fields[$key]))
{
$field->setControlText($custom_fields[$key]);
}
if (null !== ($maxlength = maxlength("entry.$key")))
{
$field->setControlAttribute('maxlength', $maxlength);
}
}
elseif ($class == 'FieldInputCheckbox')
{
$field->setControlChecked(!empty($custom_fields[$key]));
}
else
{
$field->setControlAttribute('value', (isset($custom_fields[$key])) ? $custom_fields[$key] : null);
}
return $field;
}
// Repeat type
function get_field_rep_type(RepeatRule $repeat_rule, bool $disabled=false) : FieldDiv
{
$field = new FieldDiv();
$field->setAttributes(array('id' => 'rep_type',
'class' => 'multiline'))
->setLabel(get_vocab('rep_type'));
foreach (RepeatRule::REPEAT_TYPES as $i)
{
$options[$i] = get_vocab("rep_type_$i");
}
$radio_group = new ElementDiv();
$radio_group->setAttribute('class', 'group long')
->addRadioOptions($options, 'rep_type', $repeat_rule->getType(), true);
$field->addControlElement($radio_group);
// No point in showing anything more if the repeat fields are disabled
// and the repeat type is None
if (!$disabled || ($repeat_rule->getType() != RepeatRule::NONE))
{
// And no point in showing the weekly repeat details if the repeat
// fields are disabled and the repeat type is not a weekly repeat
if (!$disabled || ($repeat_rule->getType() == RepeatRule::WEEKLY))
{
$field->addControlElement(get_fieldset_rep_weekly_details($repeat_rule, $disabled));
}
// And no point in showing the monthly repeat details if the repeat
// fields are disabled and the repeat type is not a monthly repeat
if (!$disabled || ($repeat_rule->getType() == RepeatRule::MONTHLY))
{
$field->addControlElement(get_fieldset_rep_monthly_details($repeat_rule, $disabled));
}
}
return $field;
}
// Repeat day
function get_field_rep_days(RepeatRule $repeat_rule, bool $disabled=false) : FieldInputCheckboxGroup
{
global $weekstarts, $datetime_formats;
for ($i = 0; $i < DAYS_PER_WEEK; $i++)
{
// Display day name checkboxes according to language and preferred weekday start.
$wday = ($i + $weekstarts) % DAYS_PER_WEEK;
// We need to ensure the index is a string to force the array to be associative
$options[$wday] = day_name($wday, $datetime_formats['day_name_edit']);
}
$field = new FieldInputCheckboxGroup();
$field->setAttribute('id', 'rep_day')
->setLabel(get_vocab('rep_rep_day'))
->addCheckboxOptions($options, 'rep_day[]', $repeat_rule->getDays(), true, $disabled);
return $field;
}
function get_fieldset_rep_weekly_details(RepeatRule $repeat_rule, bool $disabled=false) : ElementFieldset
{
$fieldset = new ElementFieldset();
$fieldset->setAttributes(array('class' => 'rep_type_details js_none',
'id' => 'rep_weekly'));
$fieldset->addElement(get_field_rep_days($repeat_rule, $disabled));
return $fieldset;
}
// MONTH ABSOLUTE (eg Day 15 of every month)
function get_fieldset_month_absolute(RepeatRule $repeat_rule, bool $disabled=false) : ElementFieldset
{
$fieldset = new ElementFieldset();
$label = new ElementLabel();
$label->setAttribute('class', 'no_suffix')
->setText(get_vocab('month_absolute'));
$radio = new ElementInputRadio();
$radio->setAttributes(array('name' => 'month_type',
'value' => RepeatRule::MONTHLY_ABSOLUTE,
'checked' => ($repeat_rule->getMonthlyType() == RepeatRule::MONTHLY_ABSOLUTE),
'disabled' => $disabled));
$label->addElement($radio);
$fieldset->addElement($label);
// We could in the future allow -1 to -31, meaning "the nth last day of
// the month", but for the moment we'll keep it simple
$options = array();
for ($i=1; $i<=31; $i++)
{
$options[] = $i;
}
$select = new ElementSelect();
$select->setAttributes(array('name' => 'month_absolute',
'disabled' => $disabled))
->addSelectOptions($options, $repeat_rule->getMonthlyAbsolute(), false);
$fieldset->addElement($select);
return $fieldset;
}
// MONTH RELATIVE (eg the second Thursday of every month)
function get_fieldset_month_relative(RepeatRule $repeat_rule, bool $disabled=false) : ElementFieldset
{
global $weekstarts;
$fieldset = new ElementFieldset();
$label = new ElementLabel();
$label->setAttribute('class', 'no_suffix')
->setText(get_vocab('month_relative'));
$radio = new ElementInputRadio();
$radio->setAttributes(array('name' => 'month_type',
'value' => RepeatRule::MONTHLY_RELATIVE,
'checked' => ($repeat_rule->getMonthlyType() == RepeatRule::MONTHLY_RELATIVE),
'disabled' => $disabled));
$label->addElement($radio);
$fieldset->addElement($label);
// Note: the select box order does not internationalise very well and could
// do with revisiting. It assumes all languages have the same order as English
// eg "the second Wednesday" which is probably not true.
list('ordinal' => $month_relative_ord, 'day' => $month_relative_day) = RFC5545::parseByday($repeat_rule->getMonthlyRelative());
$options = array();
foreach (array('1', '2', '3', '4', '5', '-1', '-2', '-3', '-4', '-5') as $i)
{
$options[$i] = get_vocab("ord_" . $i);
}
$select = new ElementSelect();
$select->setAttributes(array('name' => 'month_relative_ord',
'disabled' => $disabled))
->addSelectOptions($options, $month_relative_ord, true);
$fieldset->addElement($select);
$options = array();
for ($i=0; $i<DAYS_PER_WEEK; $i++)
{
$i_offset = ($i + $weekstarts)%DAYS_PER_WEEK;
$options[RFC5545::DAYS[$i_offset]] = day_name($i_offset);
}
$select = new ElementSelect();
$select->setAttributes(array('name' => 'month_relative_day',
'disabled' => $disabled))
->addSelectOptions($options, $month_relative_day, true);
$fieldset->addElement($select);
return $fieldset;
}
function get_fieldset_rep_monthly_details(RepeatRule $repeat_rule, bool $disabled=false) : ElementFieldset
{
$fieldset = new ElementFieldset();
$fieldset->setAttributes(array('class' => 'rep_type_details js_none',
'id' => 'rep_monthly'));
$fieldset->addElement(get_fieldset_month_absolute($repeat_rule, $disabled))
->addElement(get_fieldset_month_relative($repeat_rule, $disabled));
return $fieldset;
}
// $min is the minimum date for the repeat end date
function get_field_rep_end_date(RepeatRule $repeat_rule, bool $disabled=false, ?DateTime $min=null) : FieldInputDate
{
$field = new FieldInputDate();
$field->setLabel(get_vocab('rep_end_date'))
->setControlAttributes(array('name' => 'rep_end_date',
'value' => $repeat_rule->getEndDate()->getISODate(),
'disabled' => $disabled));
if (isset($min))
{
// Make it a data-min, rather than a min, attribute because we need to be able to change it using
// JavaScript. If we use min and JavaScript is not running, we are stuck with it.
$field->setControlAttribute('data-min', $min->getISODate());
}
return $field;
}
function get_field_rep_interval(RepeatRule $repeat_rule, bool $disabled=false) : FieldInputNumber
{
$field = new FieldInputNumber();
$span = new ElementSpan();
$span->setAttribute('id', 'interval_units')
->setText($repeat_rule->getIntervalUnits());
$field->setLabel(get_vocab('rep_interval'))
->setControlAttributes(array('name' => 'rep_interval',
'min' => 1,
'value' => $repeat_rule->getInterval(),
'disabled' => $disabled))
->addElement($span);
return $field;
}
function get_field_skip_conflicts(bool $disabled=false) : ?FieldInputCheckbox
{
global $skip_default;
if ($disabled)
{
return null;
}
$field = new FieldInputCheckbox();
$field->setLabel(get_vocab('skip_conflicts'))
->setControlAttribute('name', 'skip')
->setChecked(!empty($skip_default));
return $field;
}
function get_fieldset_registration() : ?ElementFieldset
{
global $enable_registration, $enable_registration_users;
global $allow_registration, $registrant_limit_enabled, $registrant_limit;
global $registration_opens, $registration_opens_enabled;
global $registration_closes, $registration_closes_enabled;
global $enable_periods, $periods_booking_opens;
if (!$enable_registration || (!$enable_registration_users && !is_book_admin()))
{
return null;
}
$fieldset = new ElementFieldset();
$fieldset->setAttribute('id', 'registration');
$field = new FieldInputCheckbox();
$field->setLabel(get_vocab('allow_registration'))
->setControlAttributes(array(
'name' => 'allow_registration'))
->setChecked($allow_registration);
$fieldset->addElement($field);
$field = new FieldInputCheckbox();
$field->setLabel(get_vocab('registrant_limit_enabled'))
->setControlAttributes(array(
'name' => 'registrant_limit_enabled',
'class' => 'enabler'))
->setChecked($registrant_limit_enabled);
$fieldset->addElement($field);
$field = new FieldInputNumber();
$field->setLabel(get_vocab('registrant_limit'))
->setControlAttributes(array(
'id' => 'registrant_limit',
'name' => 'registrant_limit',
'min' => '0',
'value' => $registrant_limit
)
);
$fieldset->addElement($field);
// Registration opens and closes
// The suffix text for registration opens and closes depends on whether
// periods are being used.
if ($enable_periods)
{
$time = strtotime($periods_booking_opens);
$time = datetime_format(hour_min_format(), $time);
$in_advance_vocab = get_vocab('in_advance_periods', $time);
}
else
{
$in_advance_vocab = get_vocab('in_advance');
}
// Registration opens
$param_names = array(
'enabler' => 'registration_opens_enabled',
'quantity' => 'registration_opens_value',
'units' => 'registration_opens_units',
);
$field = new FieldTimeWithUnits(
$param_names,
$registration_opens_enabled,
$registration_opens,
get_vocab($in_advance_vocab),
array('min' => 0) // no point in opening registration after the event starts
);
$field->setLabel(get_vocab('registration_opens'));
$fieldset->addElement($field);
// Registration closes
$param_names = array(
'enabler' => 'registration_closes_enabled',
'quantity' => 'registration_closes_value',
'units' => 'registration_closes_units',
);
$field = new FieldTimeWithUnits(
$param_names,
$registration_closes_enabled,
$registration_closes,
get_vocab($in_advance_vocab)
);
$field->setLabel(get_vocab('registration_closes'));
$fieldset->addElement($field);
return $fieldset;
}
// $min is the minimum date for the repeat end date
function get_fieldset_repeat(RepeatRule $repeat_rule, ?DateTime $min=null) : ElementFieldset
{
global $repeats_allowed;
// If repeats aren't allowed or this is not a series then disable
// the repeat fields - they're for information only
// (NOTE: when repeat bookings are restricted to admins, an ordinary user
// would not normally be able to get to the stage of trying to edit a series.
// But we have to cater for the possibility because it could happen if (a) the
// series was created before the policy was introduced or (b) the user has
// been demoted since the series was created).
$disabled = !$repeats_allowed;
$fieldset = new ElementFieldset();
$fieldset->setAttribute('id', 'rep_info');
$fieldset->addElement(get_field_rep_type($repeat_rule, $disabled))
->addElement(get_field_rep_interval($repeat_rule, $disabled))
->addElement(get_field_rep_end_date($repeat_rule, $disabled, $min))
->addElement(get_field_skip_conflicts($disabled));
return $fieldset;
}
function get_fieldset_booking_controls() : ElementFieldset
{
global $mail_settings;
$fieldset = new ElementFieldset();
$fieldset->setAttribute('id', 'booking_controls');
$field = new FieldInputCheckbox();
$field->setLabel(get_vocab('no_mail'))
->setControlAttribute('name', 'no_mail')
->setChecked($mail_settings['no_mail_default']);
$fieldset->addElement($field);
return $fieldset;
}
function get_fieldset_submit_buttons() : ElementFieldset
{
$fieldset = new ElementFieldset();
// The back and submit buttons
$field = new FieldDiv();
$back = new ElementInputSubmit();
$back->setAttributes(array('name' => 'back_button',
'value' => get_vocab('back'),
'formnovalidate' => true));
$submit = new ElementInputSubmit();
$submit->setAttributes(array('class' => 'default_action',
'name' => 'save_button',
'value' => get_vocab('save')));
// div to hold the results of the Ajax checking of the booking
$div = new ElementDiv();
$span_conflict = new ElementSpan();
$span_conflict->setAttribute('id', 'conflict_check');
$span_policy = new ElementSpan();
$span_policy->setAttribute('id', 'policy_check');
$div->setAttribute('id', 'checks')
->addElement($span_conflict)
->addElement($span_policy);
$field->setAttribute('class', 'submit_buttons')
->addLabelClass('no_suffix')
->addLabelElement($back)
->addControlElement($submit)
->addControlElement($div);
$fieldset->addElement($field);
return $fieldset;
}
// Returns the booking date for a given time. If the booking day spans midnight and
// $t is in the interval between midnight and the end of the day then the booking date
// is really the day before.
//
// If $is_end is set then this is the end time and so if the booking day happens to
// last exactly 24 hours, when there will be two possible answers, we want the later
// one.
function getbookingdate(int $t, bool $is_end=false) : array
{
global $eveningends, $eveningends_minutes, $resolution;
$date = getdate($t);
$t_secs = (($date['hours'] * 60) + $date['minutes']) * 60;
$e_secs = (((($eveningends * 60) + $eveningends_minutes) * 60) + $resolution) % SECONDS_PER_DAY;
if (day_past_midnight())
{
if (($t_secs < $e_secs) ||
(($t_secs == $e_secs) && $is_end))
{
$date = getdate(mktime($date['hours'], $date['minutes'], $date['seconds'],
$date['mon'], $date['mday'] -1, $date['year']));
$date['hours'] += 24;
}
}
return $date;
}
// Get non-standard form variables
$hour = get_form_var('hour', 'int');
$minute = get_form_var('minute', 'int');
$period = get_form_var('period', 'int');
$id = get_form_var('id', 'int');
$copy = get_form_var('copy', 'bool');
$edit_series = get_form_var('edit_series', 'bool');
$returl = get_form_var('returl', 'url_local');
// The following variables are used when coming via a JavaScript drag select
$drag = get_form_var('drag', 'int');
$start_seconds = get_form_var('start_seconds', 'int');
$end_seconds = get_form_var('end_seconds', 'int');
$selected_rooms = get_form_var('rooms', '[int]');
$start_date = get_form_var('start_date', 'string');
$end_date = get_form_var('end_date', 'string');
// And this comes from edit_entry_handler.php
$back_button = get_form_var('back_button', 'string');
// Check the CSRF token.
// Only check the token if the page is accessed via a POST request. Therefore
// this page should not take any action, but only display data.
Form::checkToken(true);
// Get the return URL. Need to do this before checkAuthorised().
// We might be going through edit_entry more than once, for example if we have to log on on the way. We
// still need to preserve the original calling page so that once we've completed edit_entry_handler we can
// go back to the page we started at (rather than going to the default view). If this is the first time
// through, then $referrer holds the original caller. If this is the second time through
// we will have stored it in $returl.
if (!isset($returl))
{
$referrer = session()->getReferrer();
$returl = $referrer ?? '';
}
// Check the user is authorised for this page
checkAuthorised(this_page());
$mrbs_user = session()->getCurrentUser();
$mrbs_username = (isset($mrbs_user)) ? $mrbs_user->username : null;
// You're only allowed to make repeat bookings if you're an admin
// or else if $auth['only_admin_can_book_repeat'] is not set
$repeats_allowed = is_book_admin() || empty($auth['only_admin_can_book_repeat']);
// Similarly for multi-day
$multiday_allowed = is_book_admin() || empty($auth['only_admin_can_book_multiday']);
// Similarly for multiple room selection
$multiroom_allowed = is_book_admin() || empty($auth['only_admin_can_select_multiroom']);
if (isset($start_seconds))
{
$minutes = intval($start_seconds/60);
if ($enable_periods)
{
$period = $minutes - (12*60);
}
else
{
$hour = intval($minutes/60);
$minute = $minutes%60;
}
}
if (isset($start_date))
{
// We'll only have got here from a drag select. If the end date is not the same
// as the start date then it's from the week view and will be a repeat.
list($year, $month, $day) = array_map('intval', explode('-', $start_date));
if (isset($end_date) && ($start_date != $end_date) && $repeats_allowed)
{
// The end date that came through from the drag select is actually the repeat end
// date, and the real end date will actually be the start date.
$rep_type = RepeatRule::DAILY;
$rep_end_date = DateTime::createFromFormat(DateTime::ISO8601_DATE, $end_date);
$end_date = $start_date;
}
}
else
{
$start_date = format_iso_date($year, $month, $day);
}
// This page will either add or modify a booking
// We need to know:
// Name of booker
// Description of meeting
// Date (option select box for day, month, year)
// Time
// Duration
// Internal/External
// Firstly we need to know if this is a new booking or modifying an old one
// and if it's a modification we need to get all the old data from the db.
// If we had $id passed in then it's a modification.
if (isset($id))
{
$entry = get_entry_by_id($id);
if (is_null($entry))
{
Errors::fatalError(get_vocab("entryid") . $id . get_vocab("not_found"));
}
// We've possibly got a new room and area, so we need to update the settings
// for this area.
$area = get_area($entry['room_id']);
get_area_settings($area);
$private = $entry['private'];
if ($private_mandatory && !is_book_admin($entry['room_id']))
{
$private = $private_default;
}
// Need to clear some data if entry is private and user
// does not have permission to edit/view details
$keep_private = $copy && is_private_event($private) && !getWritable($entry['create_by'], $entry['room_id']);
// default settings
$rep_days = array();
$rep_type = RepeatRule::NONE;
$rep_interval = 1;
foreach ($entry as $column => $value)
{
switch ($column)
{
// Don't bother with these columns
case 'id':
case 'timestamp':
case 'reminded':
case 'info_time':
case 'info_user':
case 'info_text':
case 'private': // We have already done private above
break;
// These columns cannot be made private
case 'room_id':
// We need to preserve the original room_id for existing bookings and pass
// it through to edit_entry_handler. We need this because we need to know
// in edit_entry_handler which room contains the original booking. It's
// possible in this form to select multiple rooms, or even change the room.
// We will need to know which booking is the "original booking" because the
// original booking will keep the same ical_uid and have the ical_sequence
// incremented, whereas new bookings will have a new ical_uid and start with
// an ical_sequence of 0. (If there is more than one room when we get to
// edit_entry_handler and the original room isn't among them, then we will
// just have to make an arbitrary choice as to which is the room containing
// the original booking.)
// NOTE: We do not set the original_room_id if we are copying an entry,
// because when we are copying we are effectively making a new entry and
// so we want edit_entry_handler to assign a new UID, etc.
if (!$copy)
{
$original_room_id = $entry['room_id'];
}
case 'ical_uid':
case 'ical_sequence':
case 'ical_recur_id':
case 'entry_type':
case 'tentative':
case 'allow_registration':
case 'registrant_limit':
case 'registrant_limit_enabled':
case 'registration_opens':
case 'registration_opens_enabled':
case 'registration_closes':
case 'registration_closes_enabled':
$$column = $entry[$column];
break;
// These columns can be made private [not sure about 'type' though - haven't
// checked whether it makes sense/works to make the 'type' column private]
case 'name':
case 'description':
case 'type':
$$column = ($keep_private && isset($is_private_field["entry.$column"]) && $is_private_field["entry.$column"]) ? '' : $entry[$column];
break;
case 'repeat_id':
$rep_id = $entry['repeat_id'];
break;
case 'create_by':
// If we're copying an existing entry then we need to change the create_by (they could be
// different if it's an admin doing the copying)
$create_by = ($copy) ? $mrbs_username : $entry['create_by'];
break;
case 'start_time':
$start_time = $entry['start_time'];
break;
case 'end_time':
$end_time = $entry['end_time'];
$duration = $entry['end_time'] - $entry['start_time'] - cross_dst($entry['start_time'], $entry['end_time']);
break;
default:
$custom_fields[$column] = ($keep_private && isset($is_private_field["entry.$column"]) && $is_private_field["entry.$column"]) ? '' : $entry[$column];
break;
}
}
$area_id = get_area($room_id);
if(($entry_type == ENTRY_RPT_ORIGINAL) || ($entry_type == ENTRY_RPT_CHANGED))
{
$repeat = get_repeat($rep_id);
if (!isset($repeat))
{
Errors::fatalError(get_vocab("repeat_id") . $rep_id . get_vocab("not_found"));
}
$rep_type = $repeat['rep_type'];
if (!isset($rep_type))
{
$rep_type = RepeatRule::NONE;
}
// If it's a repeating entry get the repeat details
if ($rep_type != RepeatRule::NONE)
{
$rep_interval = $repeat['rep_interval'];
// If we're editing the series we want the start_time and end_time to be the
// start and of the first entry of the series, not the start of this entry
if ($edit_series)
{
$start_time = $repeat['start_time'];
$end_time = $repeat['end_time'];
}
$rep_end_date = new DateTime();
$rep_end_date->setTimestamp($repeat['end_date']);
switch ($rep_type)
{
case RepeatRule::WEEKLY:
for ($i=0; $i<DAYS_PER_WEEK; $i++)
{
if ($repeat['rep_opt'][$i])
{
$rep_days[] = $i;
}
}
break;
case RepeatRule::MONTHLY:
if (isset($repeat['month_absolute']))
{
$month_type = RepeatRule::MONTHLY_ABSOLUTE;
$month_absolute = $repeat['month_absolute'];
}
elseif (isset($repeat['month_relative']))
{
$month_type = RepeatRule::MONTHLY_RELATIVE;
$month_relative = $repeat['month_relative'];
}
else
{
trigger_error("Invalid monthly repeat", E_USER_WARNING);
}
break;
default:
break;
}
}
}
}
else
{
// It is a new booking. The data comes from whichever button the user clicked
if ($default_name_display_name)
{
$name = (isset($mrbs_user)) ? $mrbs_user->display_name : '';
}
else
{
$name = $default_name;
}
$create_by = $mrbs_username;
$description = $default_description;
$room_id = $room;
$area_id = get_area($room_id);
$type = (is_mandatory_field('entry.type', $area_id)) ? '' : $default_type;
$private = $private_default;
$tentative = !$confirmed_default;
$allow_registration = (bool) $allow_registration_default;
$registrant_limit = (int) $registrant_limit_default;
$registrant_limit_enabled = (bool) $registrant_limit_enabled_default;
$registration_opens = (int) $registration_opens_default;
$registration_opens_enabled = (bool) $registration_opens_enabled_default;
$registration_closes = (int) $registration_closes_default;
$registration_closes_enabled = (bool) $registration_closes_enabled_default;
// Get the hour and minute, converting a period to its MRBS time
// Set some sensible defaults
if ($enable_periods)
{
if (isset($period))
{
$hour = 12 + intval($period/60);
$minute = $period % 60;
}
else
{
$hour = 0;
$minute = 0;
}
}
else
{
if (!isset($hour) || !isset($minute))
{
$hour = $morningstarts;
$minute = $morningstarts_minutes;
}
}
$start_time = mktime($hour, $minute, 0, $month, $day, $year);
// If the start time is not on a slot boundary, then make it so. (It's just possible that it won't be
// if (a) somebody messes with the query string or (b) somebody changes morningstarts or the
// resolution in another browser window and then this page is refreshed with the same query string).
$start_first_slot = get_start_first_slot($month, $day, $year);
$start_time = max(round_t_down($start_time, $resolution, $start_first_slot), $start_first_slot);
if (isset($end_seconds))
{
$end_minutes = intval($end_seconds/60);
$end_hour = intval($end_minutes/60);
$end_minute = $end_minutes%60;
if (!isset($end_date))
{
$end_date = $start_date;
}
list($end_year, $end_month, $end_day) = array_map('intval', explode('-', $end_date));
$end_time = mktime($end_hour, $end_minute, 0, $end_month, $end_day, $end_year);
$duration = $end_time - $start_time - cross_dst($start_time, $end_time);
}
else
{
// Set the duration
if ($enable_periods)
{
$duration = ($default_duration_periods ?? 1) * SECONDS_PER_MINUTE; // One minute is one period
}
else
{
$duration = $default_duration ?? SECONDS_PER_HOUR;
}
// Make sure the duration doesn't exceed the maximum
if (!is_book_admin() && $max_duration_enabled)
{
$duration = min($duration, (($enable_periods) ? $max_duration_periods : $max_duration_secs));
}
// If the duration is not an integral number of slots, then make
// it so. And make the duration at least one slot long.
if ($duration%$resolution != 0)
{
$duration = intval(round($duration/$resolution));
$duration = max(1, $duration);
$duration = $duration * $resolution;
}
$end_time = $start_time + $duration;
// Make sure the end_time falls within a booking day. So if there are no
// restrictions, bring it back to the nearest booking day. If the user is not
// allowed multi-day bookings then make sure it is on the first booking day.
if (is_book_admin() || !$auth['only_admin_can_book_multiday'])
{
$end_time = fit_to_booking_day($end_time);
}
else
{
$end_time = min($end_time, get_end_last_slot($month, $day, $year));
}
}
$rep_id = 0;
if (!isset($rep_type)) // We might have set it through a drag selection
{
$rep_type = RepeatRule::NONE;
$rep_end_date = new DateTime();
$rep_end_date->setDate($year, $month, $day);
}
$rep_days = array(date('w', $start_time));
$rep_interval = 1;
$month_type = RepeatRule::MONTHLY_ABSOLUTE;
// Populate the default values for the custom fields
$columns = Columns::getInstance(_tbl('entry'));
foreach ($columns as $column)
{
if (!in_array($column->name, $standard_fields['entry']) && (null !== ($default = $column->getDefault())))
{
$custom_fields[$column->name] = $default;
}
}
}
$repeat_rule = new RepeatRule();
$repeat_rule->setType($rep_type);
$repeat_rule->setInterval($rep_interval);
$repeat_rule->setDays($rep_days);
$repeat_rule->setMonthlyType($month_type ?? RepeatRule::MONTHLY_ABSOLUTE);
$repeat_rule->setMonthlyAbsolute($month_absolute ?? (int) date('j', $start_time));
$repeat_rule->setMonthlyRelative($month_relative ?? date_byday($start_time));
if (isset($rep_end_date))
{
$repeat_rule->setEndDate($rep_end_date);
}
$start_hour = date('H', $start_time);
$start_min = date('i', $start_time);
$enable_periods ? toPeriodString($start_min, $duration, $dur_units) : toTimeString($duration, $dur_units);
// $selected_rooms will be populated if we've come from a drag selection
if (empty($selected_rooms))
{
$selected_rooms = array($room_id);
}
// Now that we know all the data to fill the form with we start drawing it
// First of all, check that the user has write permission for these rooms.
// Remove any rooms from the selection that they don't have permission for.
foreach ($selected_rooms as $selected_room)
{
if (!getWritable($create_by, $selected_room))
{
$key = array_search($selected_room, $selected_rooms);
unset($selected_rooms[$key]);
}
}
// If there are no rooms left then they are not allowed to write to any of them
if (empty($selected_rooms))
{
showAccessDenied($view, $view_all, $year, $month, $day, $area, isset($room) ? $room : null);
exit;
}
$context = array(
'view' => $view,
'view_all' => $view_all,
'year' => $year,
'month' => $month,
'day' => $day,
'area' => $area,
'room' => isset($room) ? $room : null
);
print_header($context);
// Get the details of all the enabled rooms
$rooms = array();
$sql = "SELECT R.id, R.room_name, R.area_id
FROM " . _tbl('room') . " R, " . _tbl('area') . " A
WHERE R.area_id = A.id
AND R.disabled=0
AND A.disabled=0
ORDER BY R.area_id, R.sort_key";
$res = db()->query($sql);
while (false !== ($row = $res->next_row_keyed()))
{
row_cast_columns($row, 'room');
// Only use rooms which are visible and for which the user has write access
if (getWritable($create_by, $row['id']) && is_visible($row['id']))
{
$rooms[$row['area_id']][$row['id']] = $row['room_name'];
}
}
// Get the details of all the enabled areas
$areas = array();
$sql = "SELECT id, area_name, resolution, default_duration, default_duration_all_day,
enable_periods, periods, timezone,
morningstarts, morningstarts_minutes, eveningends , eveningends_minutes,
max_duration_enabled, max_duration_secs, max_duration_periods
FROM " . _tbl('area') . "
WHERE disabled=0
ORDER BY sort_key";
$res = db()->query($sql);
while (false !== ($row = $res->next_row_keyed()))
{
// Cast the columns to their correct types
row_cast_columns($row, 'area');
// We don't want areas that have no enabled rooms because it doesn't make sense
// to try and select them for a booking.
if (empty($rooms[$row['id']]))
{
continue;
}
// Periods are JSON encoded in the database
$row['periods'] = Periods::fromDbValue($row['id'], $row['periods']);
// Make sure we've got the correct resolution when using periods (it's
// probably OK anyway, but just in case)
if ($row['enable_periods'])
{
$row['resolution'] = 60;
}
// Generate some derived settings
$row['max_duration_qty'] = $row['max_duration_secs'];
toTimeString($row['max_duration_qty'], $row['max_duration_units']);
// Get the start and end of the booking day
if ($row['enable_periods'])
{
$first = 12*SECONDS_PER_HOUR;
// If we're using periods we just go to the end of the last slot
$last = $first + (count($row['periods']) * $row['resolution']);
}
else
{
$first = (($row['morningstarts'] * 60) + $row['morningstarts_minutes']) * 60;
$last = ((($row['eveningends'] * 60) + $row['eveningends_minutes']) * 60) + $row['resolution'];
// If the end of the day is the same as or before the start time, then it's really on the next day
if ($first >= $last)
{
$last += SECONDS_PER_DAY;
}
}
$row['first'] = $first;
$row['last'] = $last;
// We don't show the all day checkbox if it's going to result in bookings that
// contravene the policy - ie if max_duration is enabled and an all day booking
// would be longer than the maximum duration allowed.
$row['show_all_day'] = is_book_admin() ||
!$row['max_duration_enabled'] ||
( ($row['enable_periods'] && ($row['max_duration_periods'] >= count($row['periods']))) ||
(!$row['enable_periods'] && ($row['max_duration_secs'] >= ($last - $first))) );
// Clean up the settings, getting rid of any nulls and casting boolean fields into bools
$row = clean_area_row($row);
// Now assign the row to the area
$areas[$row['id']] = $row;
}
// Check that the area for this room actually exists. This will happen if the room id in the query
// string is invalid, usually as a result of using an out-of-date bookmark.
if (empty($area_id) || !isset($areas[$area_id]))
{
echo "<h1>" . get_vocab('error') . "</h1>\n";
echo "<p>" . get_vocab('edit_entry_nonexistent_room') . "</p>\n";
echo '<p><a href="' . multisite('index.php') . '">' . get_vocab('home') . "</a></p>\n";
// Print footer and exit
print_footer(true);
}
if (!isset($id))
{
$token = "addentry";
}
elseif ($copy)
{
$token = ($edit_series) ? 'copyseries' : 'copyentry';
}
else
{
$token = ($edit_series) ? 'editseries' : 'editentry';
}
$form = new Form(Form::METHOD_POST);
$form->setAttributes(array('class' => 'standard js_hidden',
'id' => 'main',
'action' => multisite('edit_entry_handler.php')));
if (!empty($back_button))
{
// Add a data attribute so that the JavaScript can tell where we've come from
$form->setAttribute('data-back', 1);
}
$hidden_inputs = array('returl' => $returl,
'rep_id' => $rep_id,
'edit_series' => $edit_series);
// If we're going back to the index page then add any scroll positions to the
// hidden inputs so that the JavaScript can scroll back to the same position.
if ('index.php' == basename(parse_url($returl, PHP_URL_PATH)))
{
foreach (['top', 'left'] as $var)
{
$$var = get_form_var($var, 'string');
if (isset($$var))
{
$hidden_inputs[$var] = $$var;
}
}
}
$form->addHiddenInputs($hidden_inputs);
// The original_room_id will only be set if this was an existing booking.
// If it is an existing booking then edit_entry_handler needs to know the
// original room id and the ical_uid and the ical_sequence, because it will
// have to keep the ical_uid and increment the ical_sequence for the room that
// contained the original booking. If it's a new booking it will generate a new
// ical_uid and start the ical_sequence at 0.
if (isset($original_room_id))
{
$form->addHiddenInputs(array('original_room_id' => $original_room_id,
'ical_uid' => $ical_uid,
'ical_sequence' => $ical_sequence,
'ical_recur_id' => $ical_recur_id));
}
if(isset($id) && !$copy)
{
$form->addHiddenInput('id', $id);
}
$fieldset = new ElementFieldset();
$fieldset->addLegend(get_vocab($token));
foreach ($edit_entry_field_order as $key)
{
switch ($key)
{
case 'create_by':
// Add in the create_by hidden input, unless the user is a booking admin
// and we're allowing admins to make bookings on behalf of other users, in
// which case we'll have an explicit form field to specify the user.
if (!is_book_admin() || $auth['admin_can_only_book_for_self'])
{
$form->addHiddenInput('create_by', $create_by);
}
else
{
$fieldset->addElement(get_field_create_by($create_by));
}
break;
case 'name':
$fieldset->addElement(get_field_name($name));
break;
case 'description':
$fieldset->addElement(get_field_description($description, $area_id));
break;
case 'start_time':
$fieldset->addElement(get_field_start_time($start_time));
break;
case 'end_time':
$fieldset->addElement(get_field_end_time($end_time));
break;
case 'room_id':
$fieldset->addElement(get_field_areas($area_id));
$fieldset->addElement(get_field_rooms($selected_rooms));
break;
case 'type':
$fieldset->addElement(get_field_type($type, $area_id));
break;
case 'confirmation_status':
$fieldset->addElement(get_field_confirmation_status($tentative));
break;
case 'privacy_status':
$fieldset->addElement(get_field_privacy_status($private));
break;
default:
$fieldset->addElement(get_field_custom($key, $area_id));
break;
} // switch
} // foreach
$form->addElement($fieldset);
$form->addElement(get_fieldset_registration());
// Show the repeat fields if it's (a) it's an existing booking and a series or (b)
// a new booking and repeats are allowed.
if ((isset($id) && $edit_series) || (!isset($id) && $repeats_allowed))
{
$form->addElement(get_fieldset_repeat($repeat_rule, (new DateTime())->setTimestamp($start_time)));
}
// Checkbox for no email
if (need_to_send_mail() &&
($mail_settings['allow_no_mail'] || (is_book_admin() && $mail_settings['allow_admins_no_mail'])))
{
$form->addElement(get_fieldset_booking_controls());
}
$form->addElement(get_fieldset_submit_buttons());
$form->render();
print_footer();