aboutsummaryrefslogtreecommitdiff
path: root/app/controllers
diff options
context:
space:
mode:
authorMoritz Strohm <strohm@data-quest.de>2026-01-14 10:29:35 +0000
committerMoritz Strohm <strohm@data-quest.de>2026-01-14 10:29:35 +0000
commit78e46de33b3f205aae375d1ea6d4fe088e0e5124 (patch)
tree4b305bf3f7b5d066ac28f011fe752e98901e714c /app/controllers
parentf637e7ae2d086941a11297ccc29ac273ad6759b0 (diff)
allow booking separable rooms in courses, closes #639
Closes #639 Merge request studip/studip!4039
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/course/block_appointments.php347
-rw-r--r--app/controllers/course/dates.php7
-rw-r--r--app/controllers/course/timesrooms.php858
-rw-r--r--app/controllers/resources/admin.php39
-rw-r--r--app/controllers/resources/room_request.php17
5 files changed, 708 insertions, 560 deletions
diff --git a/app/controllers/course/block_appointments.php b/app/controllers/course/block_appointments.php
index 88d7378..9e6b9c2 100644
--- a/app/controllers/course/block_appointments.php
+++ b/app/controllers/course/block_appointments.php
@@ -40,75 +40,74 @@ class Course_BlockAppointmentsController extends AuthenticatedController
}
- protected function setAvailableRooms()
- {
- $this->room_search = null;
- $this->selectable_rooms = [];
- if (Config::get()->RESOURCES_ENABLE) {
- //Check for how many rooms the user has booking permissions.
- //In case these permissions exist for more than 50 rooms
- //show a quick search. Otherwise show a select field
- //with the list of rooms.
-
- $current_user = User::findCurrent();
- $current_user_is_resource_admin = ResourceManager::userHasGlobalPermission(
- $current_user,
- 'admin'
- );
-
- $rooms_with_booking_permissions = 0;
- if ($current_user_is_resource_admin) {
- $rooms_with_booking_permissions = Room::countAll();
- } else {
- $user_rooms = RoomManager::getUserRooms($current_user);
- foreach ($user_rooms as $room) {
- if ($room->userHasBookingRights($current_user)) {
- $rooms_with_booking_permissions++;
- $this->selectable_rooms[] = $room;
- }
- }
- }
-
- if ($rooms_with_booking_permissions > 50) {
- $room_search_type = new RoomSearch();
- $room_search_type->setAcceptedPermissionLevels(
- ['autor', 'tutor', 'admin']
- );
- $room_search_type->setAdditionalDisplayProperties(
- ['seats']
- );
- $this->room_search = new QuickSearch(
- 'room_id',
- $room_search_type
- );
- } else {
- if (ResourceManager::userHasGlobalPermission($current_user, 'admin')) {
- $this->selectable_rooms = Room::findAll();
- }
- }
- }
- }
-
-
/**
* Display the block appointments
*/
public function index_action()
{
- if (!Request::isXhr() && Navigation::hasItem('/course/admin/timesrooms')) {
+ if (Navigation::hasItem('/course/admin/timesrooms')) {
Navigation::activateItem('/course/admin/timesrooms');
}
+ PageLayout::setTitle(_('Neuen Blocktermin anlegen'));
+
$this->linkAttributes = ['fromDialog' => Request::int('fromDialog') ? 1 : 0];
$this->start_ts = strtotime('this monday');
$this->request = $this->flash['request'] ?? $_SESSION['block_appointments'] ?? [];
- $this->confirm_many = isset($this->flash['confirm_many']) ? $this->flash['confirm_many'] : false;
- $this->lecturers = CourseMember::findByCourseAndStatus(
+ $this->lecturers = CourseMember::findByCourseAndStatus(
$this->course_id,
'dozent'
);
- if (Config::get()->RESOURCES_ENABLE) {
- $this->setAvailableRooms();
+ $this->start = null;
+ $this->end = null;
+ $this->date_types = [];
+ foreach ($GLOBALS['TERMIN_TYP'] as $id => $data) {
+ $this->date_types[] = [
+ 'id' => $id,
+ 'name' => $data['name']
+ ];
+ }
+ $this->available_lecturers = [];
+ $course = Course::find($this->course_id);
+ $lecturers = $course->getMembersWithStatus('dozent');
+ foreach ($lecturers as $lecturer) {
+ $this->available_lecturers[$lecturer->user_id] = $lecturer->getUserFullname();
+ }
+ $this->selected_lecturer_ids = [];
+ $this->selected_date_type = 0;
+ $this->dow = ['all'];
+ $this->preparation_time = 0;
+ $this->subsequent_time = 0;
+
+ if ($this->request instanceof Request) {
+ $this->start = $this->request->getDateTime('start_date', 'd.m.Y', 'start_time', 'H:i');
+ $this->end = $this->request->getDateTime('end_date', 'd.m.Y', 'end_time', 'H:i');
+ $this->selected_lecturer_ids = $this->request->getArray('lecturers');
+ $this->selected_date_type = $this->request->int('date_type');
+ $this->dow = $this->request->getArray('dow');
+ $this->preparation_time = $this->request->int('preparation_time', 0);
+ $this->subsequent_time = $this->request->int('subsequent_time', 0);
+ } elseif (is_array($this->request)) {
+ $this->start = $this->request['start'] ?? null;
+ $this->end = $this->request['end'] ?? null;
+ $this->selected_date_type = $this->request['date_type'] ?? 0;
+ $this->dow = $this->request['dow'] ?? ['all'];
+ $this->preparation_time = $this->request['preparation_time'] ?? 0;
+ $this->subsequent_time = $this->request['subsequent_time'] ?? 0;
}
+ if (!$this->start || !$this->end) {
+ //Provide some default values:
+ $this->start = new DateTime();
+ $this->start = $this->start->add(new DateInterval('PT1H'));
+ $this->start->setTime(intval($this->start->format('H')), 0, 0);
+ $this->end = clone $this->start;
+ $this->end = $this->end->add(new DateInterval('PT30M'));
+ }
+
+ $this->allow_multiple_room_bookings = ResourceManager::userHasGlobalPermission(
+ User::findCurrent(),
+ Config::get()->ROOM_PERMISSIONS_FOR_MULTIPLE_BOOKINGS_PER_COURSE_DATE
+ );
+ $this->max_preparation_time = intval(Config::get()->RESOURCES_MAX_PREPARATION_TIME) ?? 999;
}
/**
@@ -120,59 +119,50 @@ class Course_BlockAppointmentsController extends AuthenticatedController
{
$errors = [];
- $start_day = strtotime(Request::get('block_appointments_start_day'));
- $end_day = strtotime(Request::get('block_appointments_end_day'));
- $start_time = null;
- $end_time = null;
- if (!($start_day && $end_day && $start_day <= $end_day)) {
+ $start = Request::getDateTime('start_date', 'd.m.Y', 'start_time', 'H:i');
+ $end = Request::getDateTime('end_date', 'd.m.Y', 'end_time', 'H:i');
+ if (!$start || !$end || $start >= $end) {
$errors[] = _('Bitte geben Sie korrekte Werte für Start- und Enddatum an!');
- } else {
- $start_time = strtotime(Request::get('block_appointments_start_time'), $start_day);
- $end_time = strtotime(Request::get('block_appointments_end_time'), $end_day);
-
- if (!($start_time && $end_time && (strtotime(Request::get('block_appointments_start_time')) < strtotime(Request::get('block_appointments_end_time'))))) {
- $errors[] = _('Bitte geben Sie korrekte Werte für Start- und Endzeit an!');
- }
}
- //Calculate the duration if a minimum booking time is set:
- if (Config::get()->RESOURCES_MIN_BOOKING_TIME) {
- $fake_start_time = strtotime(Request::get('block_appointments_start_time'), $start_day);
- $fake_end_time = strtotime(Request::get('block_appointments_end_time'), $start_day);
- $duration = $fake_end_time - $fake_start_time;
+ $room_choice = Request::get('room');
+ $preparation_time = 0;
+ $subsequent_time = 0;
+ $room_name = '';
+ if ($room_choice === 'room' && Config::get()->RESOURCES_MIN_BOOKING_TIME) {
+ //Calculate the duration if a minimum booking time is set
+ //and one or more rooms shall be booked:
+ $fake_start_time = clone $start;
+ $fake_end_time = clone $start;
+ $fake_end_time->setTime(intval($end->format('H')), intval($end->format('i')), 0);
+ $duration = $fake_end_time->getTimestamp() - $fake_start_time->getTimestamp();
if ($duration < Config::get()->RESOURCES_MIN_BOOKING_TIME * 60) {
$errors[] = sprintf(
ngettext(
- 'Die minimale Dauer eines Termins von einer Minute wurde unterschritten.',
- 'Die minimale Dauer eines Termins von %u Minuten wurde unterschritten.',
+ 'Die minimale Dauer einer Raumbuchung von einer Minute wurde unterschritten.',
+ 'Die minimale Dauer einer Raumbuchung von %u Minuten wurde unterschritten.',
Config::get()->RESOURCES_MIN_BOOKING_TIME
),
Config::get()->RESOURCES_MIN_BOOKING_TIME
);
}
+ $preparation_time = Request::int('preparation_time', 0);
+ $subsequent_time = Request::int('subsequent_time', 0);
+ } elseif ($room_choice === 'freetext') {
+ $room_name = Request::get('room_name');
}
- $termin_typ = Request::int('block_appointments_termin_typ', 0);
- $free_room_text = Request::get('block_appointments_room_text');
- $date_count = Request::int('block_appointments_date_count');
- $days = Request::getArray('block_appointments_days');
- $lecturer_ids = Request::getArray('lecturers');
-
- $lecturers = User::findBySql(
- "INNER JOIN seminar_user USING (user_id)
- WHERE seminar_id = :course_id
- AND seminar_user.user_id IN (:lecturer_ids)
- AND seminar_user.status = 'dozent'",
- [
- 'course_id' => $this->course_id,
- 'lecturer_ids' => $lecturer_ids,
- ]
- );
-
- if (!is_array($days)) {
+ $date_type = Request::int('date_type', 0);
+ $dow = Request::getArray('dow');
+ if (empty($dow)) {
$errors[] = _('Bitte wählen Sie mindestens einen Tag aus!');
}
+ $date_count = Request::int('date_count');
+ if ($date_count < 1) {
+ $errors[] = _('Bitte setzen Sie die Menge der zu erstellenden Termine mindestens auf 1.');
+ }
+
if (count($errors)) {
$this->flash['request'] = Request::getInstance();
PageLayout::postMessage(MessageBox::error(_('Bitte korrigieren Sie Ihre Eingaben:'), $errors));
@@ -180,87 +170,124 @@ class Course_BlockAppointmentsController extends AuthenticatedController
return;
}
- $dates = [];
- /*
- * Recalculate end hour of last day to first day, so we don't run
- * into problems with daylight saving time which would add or
- * remove an hour.
- */
- $delta = (strtotime(Request::get('block_appointments_start_day') . ' ' .
- Request::get('block_appointments_end_time')) - $start_time) % (24 * 60 * 60);
- $last_day = strtotime(Request::get('block_appointments_start_time'), $end_day);
-
- if (in_array('everyday', $days)) {
- $days = range(1, 7);
+ $lecturer_ids = Request::getArray('assigned_lecturers');
+ $lecturers = [];
+ if ($lecturer_ids) {
+ $lecturers = User::findBySql(
+ "INNER JOIN seminar_user USING (user_id)
+ WHERE seminar_id = :course_id
+ AND seminar_user.user_id IN (:lecturer_ids)
+ AND seminar_user.status = 'dozent'",
+ [
+ 'course_id' => $this->course_id,
+ 'lecturer_ids' => $lecturer_ids,
+ ]
+ );
}
- if (in_array('weekdays', $days)) {
- $days = range(1, 5);
+
+ if (in_array('all', $dow)) {
+ $dow = ['1', '2', '3', '4', '5', '6', '7'];
+ } elseif (in_array('mon_fri', $dow)) {
+ $dow = ['1', '2', '3', '4', '5'];
}
- $t = $start_time;
- while ($t <= $last_day) {
- if (in_array(date('N', $t), $days)) {
- for ($i = 1; $i <= $date_count; $i++) {
- $date = new CourseDate();
- $date->range_id = $course_id;
- $date->date_typ = $termin_typ;
- $date->raum = $free_room_text;
- $date->date = $t;
- $date->end_time = $t + $delta;
+ $dates = [];
+ $t = clone $start;
+ $i = 1;
+ while ($t < $end && $i <= $date_count) {
+ if (in_array($t->format('N'), $dow)) {
+ $date_end = clone $t;
+ $date_end->setTime(intval($end->format('H')), intval($end->format('i')), 0);
+ $date = new CourseDate();
+ $date->range_id = $course_id;
+ $date->date_typ = $date_type;
+ $date->raum = $room_name;
+ $date->date = $t->getTimestamp();
+ $date->end_time = $date_end->getTimestamp();
+ if ($lecturers) {
$date->dozenten = $lecturers;
- $dates[] = $date;
}
+ $dates[] = $date;
+ $i++;
}
- $t = strtotime('+1 day', $t);
+ $t = $t->add(new DateInterval('P1D'));
}
- if (count($dates) > 100 && !Request::int('confirmed')) {
- $this->flash['request'] = Request::getInstance();
- $this->flash['confirm_many'] = count($dates);
- $this->redirect('course/block_appointments/index');
- return;
- } elseif (count($dates)) {
- if (Request::submitted('preview')) {
- //TODO
+ //Store the last used values in the session as default values.
+ $_SESSION['block_appointments'] = [
+ 'start' => $start,
+ 'end' => $end,
+ 'date_type' => $date_type,
+ 'room_name' => $room_name,
+ 'date_count' => $date_count,
+ 'dow' => $dow
+ ];
+ $partially_booked_dates = [];
+ $dates_created = array_filter(array_map(function ($d) use ($room_choice, $preparation_time, $subsequent_time, &$partially_booked_dates) {
+ $result = $d->store();
+ $room_ids = [];
+ if ($room_choice === 'room') {
+ $room_ids = Request::getArray('room_ids');
}
+ //Process the room-IDs: If a separable room is selected, set all its room parts as room-IDs.
+ //Remove the prefix in all other cases.
+ $processed_room_ids = [];
+ foreach ($room_ids as $room_id) {
+ $id_parts = explode('-', $room_id);
+ if (count($id_parts) !== 2) {
+ //Invalid ID.
+ continue;
+ }
- if (Request::submitted('save')) {
- // store last used values in session as defaults
- $_SESSION['block_appointments'] = [
- 'block_appointments_start_day' => date('d.m.Y', $start_day),
- 'block_appointments_end_day' => date('d.m.Y', $end_day),
- 'block_appointments_start_time' => date('H:i', $start_time),
- 'block_appointments_end_time' => date('H:i', $end_time),
- 'block_appointments_termin_typ' => $termin_typ,
- 'block_appointments_room_text' => $free_room_text,
- 'block_appointments_date_count' => $date_count,
- 'block_appointments_days' => $days
- ];
- $dates_created = array_filter(array_map(function ($d) use ($free_room_text) {
- if (!Request::get('room_id')) {
- $d->raum = $free_room_text;
- $result = $d->store();
- } else {
- $result = $d->store();
- $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance();
- $d->bookRoom($room);
+ if ($id_parts[0] === 'separable_room') {
+ //A separable room was selected.
+ $separable_room = SeparableRoom::find($id_parts[1]);
+ if ($separable_room) {
+ foreach ($separable_room->parts as $part) {
+ $processed_room_ids[] = $part->room_id;
+ }
}
- return $result ? $d->getFullName() : null;
- }, $dates));
- if ($date_count > 1) {
- $dates_created = array_count_values($dates_created);
- $dates_created = array_map(function ($k, $v) {
- return $k . ' (' . $v . 'x)';
- }, array_keys($dates_created), array_values($dates_created));
+ } elseif ($id_parts[0] === 'room') {
+ //An ordinary room.
+ $processed_room_ids[] = $id_parts[1];
}
- PageLayout::postSuccess(_('Folgende Termine wurden erstellt:'), $dates_created);
-
}
- } else {
- $this->flash['request'] = Request::getInstance();
- PageLayout::postError(_('Keiner der ausgewählten Tage liegt in dem angegebenen Zeitraum!'));
- $this->redirect('course/block_appointments/index');
- return;
+ $room_ids = $processed_room_ids;
+ if ($room_ids) {
+ $resources = Resource::findMany($room_ids);
+ $rooms = [];
+ foreach ($resources as $resource) {
+ $rooms[] = $resource->getDerivedClassInstance();
+ }
+ $booking_failures = 0;
+ foreach ($rooms as $room) {
+ try {
+ $r = $d->bookRoom($room, $preparation_time * 60, $subsequent_time * 60);
+ if (!$r) {
+ $booking_failures++;
+ }
+ } catch (ResourceBookingException|ResourceBookingOverlapException $e) {
+ $booking_failures++;
+ }
+ }
+ if ($result && $booking_failures) {
+ //Not all selected rooms for the date could be booked:
+ $partially_booked_dates[] = $d->getFullName();
+ }
+ }
+
+ return $result ? $d->getFullName() : null;
+ }, $dates));
+
+ if ($date_count > 1) {
+ $dates_created = array_count_values($dates_created);
+ $dates_created = array_map(function ($k, $v) {
+ return $k . ' (' . $v . 'x)';
+ }, array_keys($dates_created), array_values($dates_created));
+ }
+ PageLayout::postSuccess(_('Folgende Termine wurden erstellt:'), $dates_created);
+ if (!empty($partially_booked_dates)) {
+ PageLayout::postWarning(_('Für folgende Termine konnten nicht alle ausgewählten Räume gebucht werden:'), $partially_booked_dates);
}
if (Request::int('fromDialog')) {
diff --git a/app/controllers/course/dates.php b/app/controllers/course/dates.php
index 1a766a9..488f4f5 100644
--- a/app/controllers/course/dates.php
+++ b/app/controllers/course/dates.php
@@ -442,7 +442,7 @@ class Course_DatesController extends AuthenticatedController
'start' => $singledate->date,
'related_persons' => $singledate->dozenten,
'groups' => $singledate->statusgruppen,
- 'room' => (string) ($singledate->getRoom() ?? $singledate->raum),
+ 'room' => $singledate->getRoomNames(),
'type' => $GLOBALS['TERMIN_TYP'][$singledate->date_typ]['name'],
];
} elseif ($singledate instanceof CourseExDate && $singledate->content) {
@@ -545,8 +545,9 @@ class Course_DatesController extends AuthenticatedController
})
);
- $room = $date->getRoom();
- if ($room) {
+ $rooms = $date->getRooms();
+ if (!empty($rooms)) {
+ $room = reset($rooms);
$row[] = $room->name;
$row[] = $room->description;
$row[] = $room->seats;
diff --git a/app/controllers/course/timesrooms.php b/app/controllers/course/timesrooms.php
index 8ee11ac..ae2c567 100644
--- a/app/controllers/course/timesrooms.php
+++ b/app/controllers/course/timesrooms.php
@@ -120,6 +120,7 @@ class Course_TimesroomsController extends AuthenticatedController
}
+
protected function bookingTooShort(int $start_time, int $end_time)
{
return Config::get()->RESOURCES_MIN_BOOKING_TIME &&
@@ -173,20 +174,27 @@ class Course_TimesroomsController extends AuthenticatedController
$this->cycle_dates[$cycle->metadate_id]['dates'][$sem->id] = [];
}
$this->cycle_dates[$cycle->metadate_id]['dates'][$sem->id][] = $val;
- if ($val->getRoom()) {
- $this->cycle_dates[$cycle->metadate_id]['room_request'][] = $val->getRoom();
+ if ($rooms = $val->getRooms()) {
+ $first_room = reset($rooms);
+ if ($first_room) {
+ $this->cycle_dates[$cycle->metadate_id]['room_request'][] = $first_room;
+ }
}
$matched[] = $val->termin_id;
- //Check if a room is booked for the date:
- if (($val->room_booking instanceof ResourceBooking)
- && !$cycle_has_multiple_rooms) {
- $date_room = $val->room_booking->resource->name;
- if (isset($this->cycle_room_names[$cycle->id])) {
- if ($date_room && $date_room != $this->cycle_room_names[$cycle->id]) {
- $cycle_has_multiple_rooms = true;
+ if ($val instanceof CourseDate) {
+ //Check if a room is booked for the date:
+ foreach ($val->room_bookings as $room_booking) {
+ if (($room_booking instanceof ResourceBooking)
+ && !$cycle_has_multiple_rooms) {
+ $date_room = $room_booking->resource->name;
+ if (isset($this->cycle_room_names[$cycle->id])) {
+ if ($date_room && $date_room != $this->cycle_room_names[$cycle->id]) {
+ $cycle_has_multiple_rooms = true;
+ }
+ } elseif ($date_room) {
+ $this->cycle_room_names[$cycle->id] = $date_room;
+ }
}
- } elseif ($date_room) {
- $this->cycle_room_names[$cycle->id] = $date_room;
}
}
}
@@ -333,7 +341,7 @@ class Course_TimesroomsController extends AuthenticatedController
}
/**
- * Primary function to edit date-informations
+ * Action to edit a single date.
*
* @param string $termin_id
*/
@@ -341,47 +349,144 @@ class Course_TimesroomsController extends AuthenticatedController
{
PageLayout::setTitle(_('Einzeltermin bearbeiten'));
$this->date = CourseDate::find($termin_id) ?: CourseExDate::find($termin_id);
- $this->attributes = [];
-
- $request = RoomRequest::findByDate($this->date->id);
- if ($request) {
- $this->params = ['request_id' => $request->id];
- } else {
- $this->params = ['new_room_request_type' => 'date_' . $this->date->id];
+ $this->date_types = [];
+ foreach ($GLOBALS['TERMIN_TYP'] as $id => $data) {
+ $this->date_types[] = [
+ 'id' => $id,
+ 'name' => $data['name']
+ ];
}
- $this->only_bookable_rooms = Request::submitted('only_bookable_rooms');
+ $this->selected_room_ids = [];
if (Config::get()->RESOURCES_ENABLE) {
- $room_booking_id = '';
- if ($this->date->room_booking instanceof ResourceBooking) {
- $room_booking_id = $this->date->room_booking->id;
- $room = $this->date->room_booking->resource;
- if ($room instanceof Resource) {
- $room = $room->getDerivedClassInstance();
- if (!$room->userHasBookingRights(User::findCurrent())) {
- PageLayout::postWarning(
- _('Die Raumbuchung zu diesem Termin wird bei der Verlängerung des Zeitbereiches gelöscht, da sie keine Buchungsrechte am Raum haben!')
+ //Collect all room bookings:
+ $booked_rooms = [];
+ $separable_rooms = [];
+ if ($this->date->room_bookings) {
+ foreach ($this->date->room_bookings as $booking) {
+ $room = $booking->resource;
+ if ($room instanceof Resource) {
+ $room = $room->getDerivedClassInstance();
+ if (!$room->userHasBookingRights(User::findCurrent())) {
+ PageLayout::postWarning(
+ studip_interpolate(
+ _('Die Buchung des Raumes %{room_name} zu diesem Termin wird bei der Verlängerung des Zeitbereiches gelöscht, da sie keine Buchungsrechte an dem Raum haben!'),
+ ['room_name' => $room->name]
+ )
+ );
+ }
+ //Check if the room is part of a separable room:
+ if (count($room->separable_room)) {
+ $sr = $room->separable_room[0];
+ $separable_rooms[$sr->id] = $sr;
+ }
+
+ $booked_rooms[strval($booking->resource->id)] = $booking->resource->getDerivedClassInstance();
+ }
+ }
+
+ //Loop over all separable rooms and check if the IDs of all of their parts
+ //are present in the $booked_room_ids array:
+ foreach ($separable_rooms as $separable_room) {
+ $room_part_ids = [];
+ foreach ($separable_room->parts as $part) {
+ if (!in_array($part->room_id, array_keys($booked_rooms))) {
+ //The separable room is not fully booked and can be skipped.
+ continue 2;
+ }
+ $room_part_ids[] = $part->room_id;
+ }
+ //At this point, all the parts of the separable room are booked
+ //so that the separable room can be added to the $assigned_room_ids array
+ //and its parts can be removed from the $booked_room_ids array.
+ $this->selected_room_ids[] = [
+ 'id' => 'separable_room-' . $separable_room->id,
+ 'label' => sprintf(
+ '%1$s (%2$s)',
+ $separable_room->name,
+ _('Teilbarer Raum'),
+ ),
+ 'separable_room_id' => $separable_room->id,
+ 'info_text' => sprintf('%1$s: %2$s', $separable_room->name, $separable_room->description)
+ ];
+ //Filter out the room parts from the list of booked rooms:
+ $booked_rooms = array_filter(
+ $booked_rooms,
+ function ($item) use ($room_part_ids) {
+ return !in_array($item->id, $room_part_ids);
+ }
+ );
+ }
+
+ //All the remaining entries in $booked_room_ids are separable rooms that are
+ //only partially booked or ordinary rooms:
+ foreach ($booked_rooms as $room) {
+ $room_data = [
+ 'id' => 'room-' . $room->id,
+ 'label' => studip_interpolate(
+ _('%{room_name} (%{seats} Sitzplätze)'),
+ [
+ 'room_name' => $room->getFullName(),
+ 'seats' => $room->seats
+ ]
+ )
+ ];
+ if (count($room->separable_room) > 0) {
+ $first_separable_room = $room->separable_room[0];
+ $room_data['label'] = studip_interpolate(
+ _('%{room_name} (%{seats} Sitzplätze) [Teil von %{separable_room_name}]'),
+ [
+ 'room_name' => $room->getFullName(),
+ 'seats' => $room->seats,
+ 'separable_room_name' => $first_separable_room->name
+ ]
+ );
+ $room_data['separable_room_id'] = $first_separable_room->id;
+ $room_data['info_text'] = sprintf(
+ '%1$s: %2$s',
+ $first_separable_room->name,
+ $first_separable_room->description
);
}
+ $this->selected_room_ids[] = $room_data;
}
}
- $this->setAvailableRooms([$this->date], [$room_booking_id], $this->only_bookable_rooms);
}
- $this->teachers = $this->course->getMembersWithStatus('dozent');
- $this->assigned_teachers = $this->date->dozenten;
-
- $this->groups = $this->course->statusgruppen;
- $this->assigned_groups = $this->date->statusgruppen;
+ $this->available_lecturers = [];
+ $this->assigned_lecturers = [];
+ $this->available_groups = [];
+ $this->assigned_groups = [];
+ $lecturers = $this->course->getMembersWithStatus('dozent');
+ foreach ($lecturers as $lecturer) {
+ $this->available_lecturers[$lecturer->user_id] = $lecturer->getUserFullname();
+ }
+ foreach ($this->date->dozenten as $assigned_lecturer) {
+ $this->assigned_lecturers[] = $assigned_lecturer->user_id;
+ }
+ foreach ($this->course->statusgruppen as $group) {
+ $this->available_groups[$group->id] = $group->name;
+ }
+ foreach ($this->date->statusgruppen as $assigned_group) {
+ $this->assigned_groups[] = $assigned_group->id;
+ }
- if ($this->date->room_booking instanceof ResourceBooking) {
- $this->preparation_time = $this->date->room_booking->preparation_time / 60;
- $this->subsequent_time = $this->date->room_booking->subsequent_time / 60;
- } else {
- $this->preparation_time = 0;
- $this->subsequent_time = 0;
+ $first_booking = null;
+ if (count($this->date->room_bookings) > 0) {
+ $first_booking = $this->date->room_bookings[0];
}
- $this->max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
+ $this->preparation_time = $first_booking instanceof ResourceBooking
+ ? intval(floor($first_booking->preparation_time / 60))
+ : 0;
+ $this->subsequent_time = $first_booking instanceof ResourceBooking
+ ? intval(floor($first_booking->subsequent_time / 60))
+ : 0;
+ $this->max_preparation_time = intval(Config::get()->RESOURCES_MAX_PREPARATION_TIME);
+
+ $this->allow_multiple_room_bookings = ResourceManager::userHasGlobalPermission(
+ User::findCurrent(),
+ Config::get()->ROOM_PERMISSIONS_FOR_MULTIPLE_BOOKINGS_PER_COURSE_DATE
+ );
}
@@ -419,21 +524,38 @@ class Course_TimesroomsController extends AuthenticatedController
*
* @throws Trails\Exceptions\DoubleRenderError
*/
- public function saveDate_action($termin_id)
+ public function saveDate_action($termin_id = '')
{
// TODO :: TERMIN -> SINGLEDATE
CSRFProtection::verifyUnsafeRequest();
- $termin = CourseDate::find($termin_id);
- $date = strtotime(sprintf('%s %s:00', Request::get('date'), Request::get('start_time')));
- $end_time = strtotime(sprintf('%s %s:00', Request::get('date'), Request::get('end_time')));
+ $termin = null;
+ if ($termin_id) {
+ $termin = CourseDate::find($termin_id);
+ } else {
+ $termin = new CourseDate();
+ $termin->range_id = $this->course->id;
+ }
+ $start = Request::getDateTime('date', 'd.m.Y', 'start_time', 'H:i');
+ $end = Request::getDateTime('date', 'd.m.Y', 'end_time', 'H:i');
+
$max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
- if ($date === false || $end_time === false || $date > $end_time) {
- $date = $termin->date;
- $end_time = $termin->end_time;
+ if ($start === false || $end === false || $start > $end) {
+ if (!$termin->isNew()) {
+ $date = new DateTime();
+ $end = new DateTime();
+ $date->setTimestamp($termin->date);
+ $end->setTimestamp($termin->end_time);
+ }
PageLayout::postError(_('Die Zeitangaben sind nicht korrekt. Bitte überprüfen Sie diese!'));
}
- if ($this->bookingTooShort($date, $end_time)) {
+
+ if ($termin->isNew()) {
+ $termin->date = $start->getTimestamp();
+ $termin->end_time = $end->getTimestamp();
+ }
+
+ if ($this->bookingTooShort($start->getTimestamp(), $end->getTimestamp())) {
PageLayout::postError(
sprintf(
ngettext(
@@ -448,7 +570,16 @@ class Course_TimesroomsController extends AuthenticatedController
return;
}
- $time_changed = ($date != $termin->date || $end_time != $termin->end_time);
+ $time_changed = !$termin->isNew() && ($start->getTimestamp() != $termin->date || $end->getTimestamp() != $termin->end_time);
+ $preparation_time = Request::int('preparation_time', 0);
+ $subsequent_time = Request::int('subsequent_time', 0);
+ $preparation_time_changed = false;
+ $subsequent_time_changed = false;
+ $first_booking = reset($termin->room_bookings);
+ if ($first_booking) {
+ $preparation_time_changed = $first_booking->preparation_time !== $preparation_time * 60 ;
+ $subsequent_time_changed = $first_booking->subsequent_time !== $subsequent_time * 60 ;
+ }
if ($time_changed) {
if ($termin->metadate_id != '') {
//time changed for regular date. create normal singledate and cancel the regular date
@@ -466,53 +597,56 @@ class Course_TimesroomsController extends AuthenticatedController
$termin = new CourseDate();
unset($termin_values['metadate_id']);
$termin->setData($termin_values);
- $termin->date = $date;
- $termin->end_time = $end_time;
+ $termin->date = $start->getTimestamp();
+ $termin->end_time = $end->getTimestamp();
$termin->setId($termin->getNewId());
} else {
//Time changed for single date.
- $termin->date = $date;
- $termin->end_time = $end_time;
+ $termin->date = $start->getTimestamp();
+ $termin->end_time = $end->getTimestamp();
}
}
- $termin->date_typ = Request::get('course_type');
+ $termin->date_typ = Request::get('date_type');
// Set assigned teachers
- $assigned_teachers = Request::optionArray('assigned_teachers');
+ $assigned_lecturers = Request::optionArray('assigned_lecturers');
$dozenten = $this->course->getMembersWithStatus('dozent');
- if (count($assigned_teachers) === count($dozenten) || empty($assigned_teachers)) {
+ if (count($assigned_lecturers) === count($dozenten) || empty($assigned_lecturers)) {
//The amount of lecturers of the course date is the same as the amount of lecturers of the course
//or no lecturers are assigned to the course date.
$termin->dozenten = [];
} else {
//The assigned lecturers (amount or persons) have been changed in the form.
//In those cases, the lecturers of the course date have to be set.
- $termin->dozenten = User::findMany($assigned_teachers);
+ $termin->dozenten = User::findMany($assigned_lecturers);
}
// Set assigned groups
- $assigned_groups = Request::optionArray('assigned-groups');
+ $assigned_groups = Request::optionArray('assigned_groups');
$termin->statusgruppen = Statusgruppen::findMany($assigned_groups);
if (Config::get()->ENABLE_NUMBER_OF_PARTICIPANTS) {
$termin->number_of_participants = strlen(Request::get('number_of_participants')) && Request::int('number_of_participants') >= 0 ? Request::int('number_of_participants') : null;
}
+ $new_date = $termin->isNew();
+
$termin->store();
- if ($time_changed) {
+ if ($new_date || $time_changed) {
NotificationCenter::postNotification('CourseDidChangeSchedule', $this->course);
}
- // Set Room
- $old_room_id = $termin->room_booking->resource_id ?? '';
+ // Set Rooms
+ $old_room_ids = [];
+ foreach ($termin->room_bookings as $booking) {
+ $old_room_ids[] = $booking->resource_id;
+ }
if ((Request::option('room') == 'room') || Request::option('room') == 'nochange') {
- $room_id = null;
- $preparation_time = Request::int('preparation_time', 0);
- $subsequent_time = Request::int('subsequent_time', 0);
+ $room_ids = [];
if (Request::option('room') == 'room') {
- $room_id = Request::get('room_id');
+ $room_ids = Request::getArray('room_ids');
if ($preparation_time > $max_preparation_time || $subsequent_time > $max_preparation_time) {
PageLayout::postError(
sprintf(
@@ -521,128 +655,189 @@ class Course_TimesroomsController extends AuthenticatedController
)
);
}
+ //Process the room-IDs: If a separable room is selected, set all its room parts as room-IDs.
+ //Remove the prefix in all other cases.
+ $processed_room_ids = [];
+ foreach ($room_ids as $room_id) {
+ $id_parts = explode('-', $room_id);
+
+ if ($id_parts[0] === 'separable_room') {
+ //A separable room was selected.
+ $separable_room = SeparableRoom::find($id_parts[1]);
+ if ($separable_room) {
+ foreach ($separable_room->parts as $part) {
+ $processed_room_ids[] = $part->room_id;
+ }
+ }
+ } elseif ($id_parts[0] === 'room') {
+ //An ordinary room.
+ $processed_room_ids[] = $id_parts[1];
+ }
+ }
+ $room_ids = $processed_room_ids;
} elseif (Request::option('room') == 'nochange') {
//Use the ID of the current room as room-ID:
- $room_id = $old_room_id;
+ $room_ids = $old_room_ids;
}
- if ($room_id) {
- $room = Resource::find($room_id)?->getDerivedClassInstance();
- if ($room_id !== $old_room_id || $time_changed) {
- $failure = false;
- if ($room instanceof Room) {
- try {
- $failure = !$termin->bookRoom($room, $preparation_time, $subsequent_time);
- } catch (ResourceBookingException|ResourceBookingOverlapException $e) {
- $course = $e->getRange();
- $message_links = [];
-
- if ($course instanceof Course) {
- if ($course->isEditableByUser()) {
- //Link to the times/rooms page:
- $link = new LinkElement(
- _('Direkt zur Veranstaltung'),
- URLHelper::getURL('dispatch.php/course/timesrooms/index', ['cid' => $course->id]),
- Icon::create('link-intern')
- );
- $message_links[] = $link->render();
- } elseif ($course->isAccessibleToUser()) {
- //Link to the details page:
- $link = new LinkElement(
- _('Direkt zur Veranstaltung'),
- URLHelper::getURL('course/details/index', ['cid' => $course->id]),
- Icon::create('link-intern')
+ if ($room_ids) {
+ $resources = Resource::findMany($room_ids);
+ $rooms = [];
+ foreach ($resources as $resource) {
+ $rooms[] = $resource->getDerivedClassInstance();
+ }
+ if ($time_changed || $preparation_time_changed) {
+ //Remove the old bookings first.
+ ResourceBooking::deleteBySQL(
+ '`range_id` = :date_id AND `resource_id` IN ( :room_ids )',
+ ['date_id' => $termin->id, 'room_ids' => $room_ids]
+ );
+ }
+ if ($room_ids !== $old_room_ids || $time_changed) {
+ if (count($room_ids) > 1) {
+ //Check if the user has sufficient permissions to book multiple rooms.
+ $min_perms = Config::get()->ROOM_PERMISSIONS_FOR_MULTIPLE_BOOKINGS_PER_COURSE_DATE;
+ if (!ResourceManager::userHasGlobalPermission(User::findCurrent(), $min_perms)) {
+ PageLayout::postError(
+ _('Ihre globalen Berechtigungen in der Raumverwaltung reichen nicht aus, um mehrere Räume für einen Termin buchen zu können.')
+ );
+ $this->relocate('course/timesrooms/index', ['contentbox_open' => $termin->metadate_id]);
+ return;
+ }
+ }
+ $unbooked_room_ids = $old_room_ids;
+ foreach ($rooms as $room) {
+ if (in_array($room->id, $old_room_ids)) {
+ $unbooked_room_ids = array_diff($unbooked_room_ids, [$room->id]);
+ }
+ $failure = false;
+ if ($room instanceof Room) {
+ try {
+ $failure = !$termin->bookRoom($room, $preparation_time, $subsequent_time);
+ } catch (ResourceBookingException|ResourceBookingOverlapException $e) {
+ $course = $e->getRange();
+ $message_links = [];
+
+ if ($course instanceof Course) {
+ if ($course->isEditableByUser()) {
+ //Link to the times/rooms page:
+ $link = new LinkElement(
+ _('Direkt zur Veranstaltung'),
+ URLHelper::getURL('dispatch.php/course/timesrooms/index', ['cid' => $course->id]),
+ Icon::create('link-intern')
+ );
+ $message_links[] = $link->render();
+ } elseif ($course->isAccessibleToUser()) {
+ //Link to the details page:
+ $link = new LinkElement(
+ _('Direkt zur Veranstaltung'),
+ URLHelper::getURL('course/details/index', ['cid' => $course->id]),
+ Icon::create('link-intern')
+ );
+ $message_links[] = $link->render();
+ }
+ }
+ if ($room->userHasBookingRights(User::findCurrent())) {
+ $room_link = new LinkElement(
+ _('Zum Belegungsplan'),
+ $room->getActionURL('booking_plan')
);
- $message_links[] = $link->render();
+ $message_links[] = $room_link->render();
}
- }
- if ($room->userHasBookingRights(User::findCurrent())) {
- $room_link = new LinkElement(
- _('Zum Belegungsplan'),
- $room->getActionURL('booking_plan')
- );
- $message_links[] = $room_link->render();
- }
- if ($e instanceof ResourceBookingException) {
- PageLayout::postError(
- sprintf(
- _('Der angegebene Raum konnte für den Termin %1$s nicht gebucht werden: %2$s'),
- '<strong>' . htmlReady($termin->getFullName()) . '</strong>',
- $e->getMessage()
- ),
- $message_links
- );
- } else {
- //$e is a ResourceBookingOverlapException
- if ($course instanceof Course) {
+ if ($e instanceof ResourceBookingException) {
PageLayout::postError(
- studip_interpolate(
- _('Der Raum %{room_name} wird an dem Termin %{date} bereits durch die Veranstaltung %{course_name} belegt.'),
- [
- 'room_name' => $room->name,
- 'date' => $termin->getFullName(),
- 'course_name' => $course->name
- ]
+ sprintf(
+ _('Der angegebene Raum konnte für den Termin %1$s nicht gebucht werden: %2$s'),
+ '<strong>' . htmlReady($termin->getFullName()) . '</strong>',
+ $e->getMessage()
),
$message_links
);
} else {
- PageLayout::postError(
- studip_interpolate(
- _('Der Raum %{room_name} wird an dem Termin %{date} bereits anderweitig belegt.'),
- [
- 'room_name' => $room->name,
- 'date' => $termin->getFullName()
- ]
- ),
- $message_links
- );
+ //$e is a ResourceBookingOverlapException
+ if ($course instanceof Course) {
+ PageLayout::postError(
+ studip_interpolate(
+ _('Der Raum %{room_name} wird an dem Termin %{date} bereits durch die Veranstaltung %{course_name} belegt.'),
+ [
+ 'room_name' => $room->name,
+ 'date' => $termin->getFullName(),
+ 'course_name' => $course->name
+ ]
+ ),
+ $message_links
+ );
+ } else {
+ PageLayout::postError(
+ studip_interpolate(
+ _('Der Raum %{room_name} wird an dem Termin %{date} bereits anderweitig belegt.'),
+ [
+ 'room_name' => $room->name,
+ 'date' => $termin->getFullName()
+ ]
+ ),
+ $message_links
+ );
+ }
}
}
}
+ if ($failure) {
+ PageLayout::postError(sprintf(
+ _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'),
+ '<strong>' . htmlReady($termin->getFullName()) . '</strong>'
+ ));
+ }
}
- if ($failure) {
- PageLayout::postError(sprintf(
- _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'),
- '<strong>' . htmlReady($termin->getFullName()) . '</strong>'
- ));
+ //Delete the bookings of the delesected rooms:
+ ResourceBooking::deleteBySQL(
+ '`range_id` = :date_id AND `resource_id` IN ( :unbooked_room_ids )',
+ ['date_id' => $termin->id, 'unbooked_room_ids' => $unbooked_room_ids]
+ );
+ } elseif ($preparation_time_changed || $subsequent_time_changed) {
+ foreach ($rooms as $room) {
+ if ($room instanceof Room) {
+ $termin->bookRoom($room, $preparation_time * 60, $subsequent_time * 60);
+ }
}
- } elseif ($room instanceof Room
- && (
- $termin->room_booking->preparation_time != ($preparation_time * 60)
- || $termin->room_booking->subsequent_time != ($subsequent_time * 60))) {
- $termin->bookRoom($room, $preparation_time, $subsequent_time);
}
- } else if ($old_room_id && empty($termin->room_booking->resource_id)) {
+ } else if ($old_room_ids && empty($termin->room_bookings)) {
PageLayout::postInfo(
sprintf(
- _('Die Raumbuchung für den Termin %s wurde aufgehoben, da die neuen Zeiten außerhalb der alten liegen!'),
+ _('Die Raumbuchungen für den Termin %s wurden aufgehoben, da die neuen Zeiten außerhalb der alten liegen!'),
'<strong>'.htmlReady($termin->getFullName()) .'</strong>'
));
} else if (Request::get('room_id_parameter')) {
PageLayout::postInfo(
- _('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
+ _('Um Raumbuchungen durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
);
}
- } elseif (Request::option('room') == 'freetext') {
- $termin->raum = Request::get('freeRoomText_sd');
- if ($termin->room_booking) {
- $termin->room_booking->delete();
+ } elseif (Request::option('room') === 'freetext') {
+ $termin->raum = Request::get('room_name');
+ if ($termin->room_bookings) {
+ $termin->room_bookings->each(function($b){$b->delete();});
}
$termin->store();
- PageLayout::postSuccess(sprintf(
- _('Der Termin %s wurde geändert, Raumbuchungen zu diesem Termin wurden entfernt und stattdessen der angegebene Freitext eingetragen!'),
- '<strong>' . htmlReady($termin->getFullName()) . '</strong>'
- ));
+ if (!$new_date) {
+ PageLayout::postSuccess(sprintf(
+ _('Der Termin %s wurde geändert, Raumbuchungen zu diesem Termin wurden entfernt und stattdessen der angegebene Freitext eingetragen!'),
+ '<strong>' . htmlReady($termin->getFullName()) . '</strong>'
+ ));
+ }
} elseif (Request::option('room') == 'noroom') {
$termin->raum = '';
- if ($termin->room_booking) {
- $termin->room_booking->delete();
+ if ($termin->room_bookings) {
+ $termin->room_bookings->each(function($b){$b->delete();});
}
$termin->store();
- PageLayout::postSuccess(sprintf(
- _('Der Termin %s wurde geändert, freie Ortsangaben und Raumbuchungen an diesem Termin wurden entfernt.'),
- '<strong>' . htmlReady($termin) . '</strong>'
- ));
+ if (!$new_date) {
+ PageLayout::postSuccess(sprintf(
+ _('Der Termin %s wurde geändert, freie Ortsangaben und Raumbuchungen an diesem Termin wurden entfernt.'),
+ '<strong>' . htmlReady($termin) . '</strong>'
+ ));
+ }
+ }
+ if ($new_date) {
+ PageLayout::postSuccess(_('Der Termin wurde gespeichert.'));
}
$this->redirect('course/timesrooms/index', ['contentbox_open' => $termin->metadate_id]);
}
@@ -659,91 +854,28 @@ class Course_TimesroomsController extends AuthenticatedController
$_SESSION['last_single_date'] ?? null
);
- if (Config::get()->RESOURCES_ENABLE) {
- $this->setAvailableRooms(null);
- }
- $this->teachers = $this->course->getMembersWithStatus('dozent');
- $this->groups = Statusgruppen::findBySeminar_id($this->course->id);
- }
-
- /**
- * Save Single Date
- *
- * @throws Trails\Exceptions\DoubleRenderError
- */
- public function saveSingleDate_action()
- {
- CSRFProtection::verifyUnsafeRequest();
-
- $start_time = strtotime(sprintf('%s %s:00', Request::get('date'), Request::get('start_time')));
- $end_time = strtotime(sprintf('%s %s:00', Request::get('date'), Request::get('end_time')));
-
- if ($start_time === false || $end_time === false || $start_time > $end_time) {
- $this->storeRequest();
-
- PageLayout::postError(_('Die Zeitangaben sind nicht korrekt. Bitte überprüfen Sie diese!'));
- $this->redirect('course/timesrooms/createSingleDate');
-
- return;
- }
- if ($this->bookingTooShort($start_time, $end_time)) {
- PageLayout::postError(
- sprintf(
- ngettext(
- 'Die minimale Dauer eines Termins von einer Minute wurde unterschritten.',
- 'Die minimale Dauer eines Termins von %u Minuten wurde unterschritten.',
- Config::get()->RESOURCES_MIN_BOOKING_TIME
- ),
- Config::get()->RESOURCES_MIN_BOOKING_TIME
- )
- );
- $this->redirect('course/timesrooms/createSingleDate');
- return;
- }
-
- $termin = new CourseDate();
- $termin->termin_id = $termin->getNewId();
- $termin->range_id = $this->course->id;
- $termin->date = $start_time;
- $termin->end_time = $end_time;
- $termin->autor_id = $GLOBALS['user']->id;
- $termin->date_typ = Request::get('dateType');
-
- $current_count = CourseMember::countByCourseAndStatus($this->course->id, 'dozent');
- $related_ids = Request::optionArray('related_teachers');
- if ($related_ids && count($related_ids) !== $current_count) {
- $termin->dozenten = User::findMany($related_ids);
- }
-
- $groups = Statusgruppen::findBySeminar_id($this->course->id);
- $related_groups = Request::getArray('related_statusgruppen');
- if ($related_groups && count($related_groups) !== count($groups)) {
- $termin->statusgruppen = Statusgruppen::findMany($related_groups);
+ $this->date_types = [];
+ foreach ($GLOBALS['TERMIN_TYP'] as $id => $data) {
+ $this->date_types[] = [
+ 'id' => $id,
+ 'name' => $data['name']
+ ];
}
-
- if (!Request::get('room_id')) {
- $termin->raum = Request::get('freeRoomText');
- $termin->store();
- } else {
- $termin->store();
- $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance();
- if ($room instanceof Room) {
- $termin->bookRoom($room);
- }
+ $lecturer_objects = $this->course->getMembersWithStatus('dozent');
+ $group_objects = Statusgruppen::findBySeminar_id($this->course->id);
+ $this->available_lecturers = [];
+ $this->available_groups = [];
+ foreach ($lecturer_objects as $lecturer) {
+ $this->available_lecturers[$lecturer->user_id] = $lecturer->getUserFullname();
}
- if (Request::get('room_id_parameter')) {
- PageLayout::postInfo(
- _('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
- );
+ foreach ($group_objects as $group) {
+ $this->available_groups[$group->id] = $group->name;
}
- // store last created date in session
- $_SESSION['last_single_date'] = Request::getInstance();
-
- PageLayout::postSuccess(sprintf(_('Der Termin %s wurde hinzugefügt!'), htmlReady($termin->getFullname())));
- $this->course->store();
-
- $this->relocate('course/timesrooms/index');
+ $this->allow_multiple_room_bookings = ResourceManager::userHasGlobalPermission(
+ User::findCurrent(),
+ Config::get()->ROOM_PERMISSIONS_FOR_MULTIPLE_BOOKINGS_PER_COURSE_DATE
+ );
}
/**
@@ -825,47 +957,58 @@ class Course_TimesroomsController extends AuthenticatedController
$this->teachers = $this->course->getMembersWithStatus('dozent');
$this->gruppen = Statusgruppen::findBySeminar_id($this->course->id);
$checked_course_dates = CourseDate::findMany($_SESSION['_checked_dates']);
- $this->only_bookable_rooms = Request::submitted('only_bookable_rooms');
-
- $date_booking_ids = [];
- if ($this->only_bookable_rooms) {
- $date_ids = [];
- foreach ($checked_course_dates as $date) {
- $date_ids[] = $date->termin_id;
- }
- $db = DBManager::get();
- $stmt = $db->prepare(
- "SELECT DISTINCT `id` FROM `resource_bookings` WHERE `range_id` IN ( :date_ids )"
- );
- $stmt->execute(['date_ids' => $date_ids]);
- $date_booking_ids = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
- }
- $this->setAvailableRooms($checked_course_dates, $date_booking_ids, $this->only_bookable_rooms);
- /*
- * Extract a single date for start and end time
- * (all cycle dates have the same start and end time,
- * so it doesn't matter which date we get).
- */
- $this->date = CourseDate::findOneByMetadate_id($cycle_id);
$this->checked_dates = $_SESSION['_checked_dates'];
$this->selected_lecturer_ids = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
return $date->dozenten->pluck('user_id');
});
- $this->selected_room_id = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
- return $date->room_booking->resource_id ?? '';
+ $this->selected_room_ids = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
+ $ids = [];
+ foreach ($date->room_bookings as $booking) {
+ $ids[] = $booking->resource->id;
+ }
+ return $ids;
});
$this->selected_room_name = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
return $date->raum ?? '';
});
$preparation_time = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
- return $date->room_booking->preparation_time ?? 0;
+ $first_booking = null;
+ if (count($date->room_bookings) > 0) {
+ $first_booking = $date->room_bookings[0];
+ }
+ return $first_booking->preparation_time ?? 0;
+ });
+ $subsequent_time = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
+ $first_booking = null;
+ if (count($date->room_bookings) > 0) {
+ $first_booking = $date->room_bookings[0];
+ }
+ return $first_booking->subsequent_time ?? 0;
+ });
+ $this->time_ranges = [];
+ foreach ($checked_course_dates as $course_date) {
+ $this->time_ranges[] = [
+ 'start' => $course_date->date,
+ 'end' => $course_date->end_time
+ ];
+ }
+ $this->max_preparation_time = intval(Config::get()->RESOURCES_MAX_PREPARATION_TIME);
+ $this->preparation_time = intval($preparation_time) / 60;
+ $this->subsequent_time = intval($subsequent_time) / 60;
+ $this->allow_multiple_room_bookings = ResourceManager::userHasGlobalPermission(
+ User::findCurrent(),
+ Config::get()->ROOM_PERMISSIONS_FOR_MULTIPLE_BOOKINGS_PER_COURSE_DATE
+ );
+ $this->selected_room_ids = $this->getSameFieldValue($checked_course_dates, function (CourseDate $date) {
+ $room_ids = [];
+ foreach ($date->room_bookings as $booking) {
+ $room_ids[] = $booking->resource_id;
+ }
+ return $room_ids;
});
- $this->preparation_time = floor($preparation_time / 60);
-
- $this->max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
$this->render_template('course/timesrooms/editStack');
}
@@ -987,11 +1130,6 @@ class Course_TimesroomsController extends AuthenticatedController
public function saveStack_action($cycle_id = '')
{
CSRFProtection::verifyUnsafeRequest();
- if (Request::submitted('only_bookable_rooms')) {
- //Redirect to the editStack method and do not save anything.
- $this->editStack($cycle_id);
- return;
- }
switch (Request::get('method')) {
case 'edit':
$this->saveEditedStack();
@@ -1129,17 +1267,44 @@ class Course_TimesroomsController extends AuthenticatedController
PageLayout::postSuccess(_('Zugewiesene Gruppen für die Termine wurden geändert.'));
}
- if (in_array(Request::get('action'), ['room', 'freetext', 'noroom']) || Request::get('course_type')) {
+ if (in_array(Request::get('room'), ['room', 'freetext', 'noroom']) || Request::get('course_type')) {
$success_cases = 0;
$success_messages = [];
$error_messages = [];
+ $room_ids = Request::getArray('room_ids');
+ $rooms = [];
+ //Collect all rooms and distinguish between real rooms and separable rooms:
+ foreach ($room_ids as $room_id) {
+ $id_parts = explode('-', $room_id);
+ if (count($id_parts) != 2) {
+ //Invalid ID.
+ continue;
+ }
+ if ($id_parts[0] === 'room') {
+ //The ID belongs to a real room.
+ $room = Room::find($id_parts[1]);
+ if ($room) {
+ $rooms[$room->id] = $room;
+ }
+ } elseif ($id_parts[0] === 'separable_room') {
+ //The ID belongs to a separable room: Get all the room parts.
+ $separable_room = SeparableRoom::find($id_parts[1]);
+ foreach ($separable_room->parts as $part) {
+ if ($part->room instanceof Room) {
+ $rooms[$part->room->id] = $part->room;
+ }
+ }
+ }
+ }
+
foreach ($singledates as $singledate) {
if ($singledate instanceof CourseExDate) {
continue;
}
- if (Request::option('action') == 'room') {
- $preparation_time = Request::get('preparation_time');
- $max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
+ if (Request::get('room') === 'room') {
+ $preparation_time = Request::int('preparation_time', 0);
+ $subsequent_time = Request::int('subsequent_time', 0);
+ $max_preparation_time = intval(Config::get()->RESOURCES_MAX_PREPARATION_TIME);
if ($preparation_time > $max_preparation_time) {
$error_messages[] = sprintf(
studip_interpolate(
@@ -1150,12 +1315,13 @@ class Course_TimesroomsController extends AuthenticatedController
);
continue;
}
- if (Request::option('room_id')) {
- $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance();
- if ($room instanceof Room) {
+ if (!empty($room_ids)) {
+ //Delete all existing bookings for the date:
+ ResourceBooking::deleteBySQL('`range_id` = :date_id',['date_id' => $singledate->id]);
+ foreach ($rooms as $room) {
$failure = false;
try {
- $failure = !$singledate->bookRoom($room, intval($preparation_time));
+ $failure = !$singledate->bookRoom($room, $preparation_time, $subsequent_time);
} catch (ResourceBookingException $e) {
$error_messages[] = sprintf(
_('Der angegebene Raum konnte für den Termin %1$s nicht gebucht werden: %2$s'),
@@ -1168,8 +1334,8 @@ class Course_TimesroomsController extends AuthenticatedController
$error_messages[] = studip_interpolate(
_('Der Raum %{room_name} wird an dem Termin %{date} bereits durch die Veranstaltung %{course_name} belegt.'),
[
- 'room_name' => $room->name,
- 'date' => $singledate->getFullName(),
+ 'room_name' => $room->name,
+ 'date' => $singledate->getFullName(),
'course_name' => $course->name
]
);
@@ -1177,8 +1343,8 @@ class Course_TimesroomsController extends AuthenticatedController
$error_messages[] = studip_interpolate(
_('Der Raum %{room_name} wird an dem Termin %{date} bereits anderweitig belegt.'),
[
- 'room_name' => $room->name,
- 'date' => $singledate->getFullName()
+ 'room_name' => $room->name,
+ 'date' => $singledate->getFullName()
]
);
}
@@ -1192,26 +1358,25 @@ class Course_TimesroomsController extends AuthenticatedController
$success_cases++;
}
}
- } else if (Request::get('room_id_parameter')) {
- PageLayout::postInfo(
- ('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
- );
+ } elseif (Request::get('room') === 'room') {
+ //No room has been selected despite having the room selector set to book at least one room.
+ PageLayout::postInfo(_('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!'));
}
- } elseif (Request::option('action') == 'freetext') {
- $singledate->raum = Request::get('freeRoomText');
+ } elseif (Request::get('room') === 'freetext') {
+ $singledate->raum = Request::get('room_name');
$singledate->store();
- if ($singledate->room_booking instanceof ResourceBooking) {
- $singledate->room_booking->delete();
+ if (!empty($singledate->room_bookings)) {
+ $singledate->room_bookings->each(function($b){$b->delete();});
}
$success_messages[] = sprintf(
_('Der Termin %s wurde geändert, etwaige Raumbuchungen wurden entfernt und stattdessen der angegebene Freitext eingetragen!'),
'<strong>' . htmlReady($singledate->getFullName()) . '</strong>'
);
- } elseif (Request::option('action') == 'noroom') {
+ } elseif (Request::get('room') == 'noroom') {
$singledate->raum = '';
$singledate->store();
- if ($singledate->room_booking instanceof ResourceBooking) {
- $singledate->room_booking->delete();
+ if (!empty($singledate->room_bookings)) {
+ $singledate->room_bookings->each(function($b){$b->delete();});
}
$success_messages[] = sprintf(
_('Der Termin %s wurde geändert, etwaige freie Ortsangaben und Raumbuchungen wurden entfernt.'),
@@ -1714,7 +1879,7 @@ class Course_TimesroomsController extends AuthenticatedController
private function deleteDate($termin, $cancel_comment)
{
$seminar_id = $termin->range_id;
- $termin_room = $termin->getRoomName();
+ $termin_room = $termin->getRoomNames();
$termin_date = $termin->getFullName();
$has_topics = $termin->topics->count();
@@ -1750,10 +1915,10 @@ class Course_TimesroomsController extends AuthenticatedController
if ($termin_room) {
PageLayout::postSuccess(
studip_interpolate(
- _('Der Termin %{date} wurde gelöscht! Die Buchung für den Raum %{room} wurde gelöscht.'),
+ _('Der Termin %{date} wurde gelöscht! Die Raumbuchungen für die folgenden Räume wurden gelöscht: %{room_names}'),
[
- 'date' => htmlReady($termin_date),
- 'room' => htmlReady($termin_room)
+ 'date' => htmlReady($termin_date),
+ 'room_names' => htmlReady($termin_room)
]
)
);
@@ -1822,99 +1987,6 @@ class Course_TimesroomsController extends AuthenticatedController
}
}
-
- protected function setAvailableRooms($dates, $date_booking_ids = [], $only_bookable_rooms = false)
- {
- $this->room_search = null;
- $this->selectable_rooms = [];
- if (Config::get()->RESOURCES_ENABLE) {
- //Check for how many rooms the user has booking permissions.
- //In case these permissions exist for more than 50 rooms
- //show a quick search. Otherwise show a select field
- //with the list of rooms.
-
- $current_user = User::findCurrent();
- $current_user_is_resource_admin = ResourceManager::userHasGlobalPermission(
- $current_user,
- 'admin'
- );
- $all_time_intervals = [];
- if (!empty($dates)) {
- $dates = SimpleCollection::createFromArray($dates);
- foreach ($dates as $date) {
- $begin = new DateTime();
- $begin->setTimestamp($date->date);
- $end = new DateTime();
- $end->setTimestamp($date->end_time);
- $all_time_intervals[] = [
- 'begin' => $begin,
- 'end' => $end
- ];
- }
- }
- $this->selectable_rooms = [];
- $rooms_with_booking_permissions = 0;
- if ($current_user_is_resource_admin) {
- $rooms_with_booking_permissions = Room::countAll();
- } else {
- $user_rooms = RoomManager::getUserRooms($current_user);
- foreach ($user_rooms as $room) {
- if ($room->userHasBookingRights($current_user, $begin, $end)) {
- $rooms_with_booking_permissions++;
- if ($only_bookable_rooms) {
- foreach ($all_time_intervals as $interval) {
- $available = $room->isAvailable($interval['begin'], $interval['end'], $date_booking_ids);
- if (!$available) {
- continue 2;
- }
- }
- //At this point, the room is available on all
- //time intervals.
- $this->selectable_rooms[] = $room;
- } else {
- $this->selectable_rooms[] = $room;
- }
- }
- }
- }
-
- if (($rooms_with_booking_permissions > 50) && !$only_bookable_rooms) {
- $room_search_type = new RoomSearch();
- $room_search_type->with_seats = 0;
- $room_search_type->setAcceptedPermissionLevels(
- ['autor', 'tutor', 'admin']
- );
- $room_search_type->setAdditionalDisplayProperties(
- ['seats']
- );
- $room_search_type->setAdditionalPropertyFormat(_('(%s Sitzplätze)'));
- $this->room_search = new QuickSearch(
- 'room_id',
- $room_search_type
- );
- } else {
- if (ResourceManager::userHasGlobalPermission($current_user, 'admin')) {
- if ($only_bookable_rooms) {
- $rooms = Room::findAll();
- foreach ($rooms as $room) {
- foreach ($all_time_intervals as $interval) {
- $available = $room->isAvailable($interval['begin'], $interval['end'], $date_booking_ids);
- $booking_rights = $room->userHasBookingRights($current_user, $interval['begin'], $interval['end']);
-
- if (!$available || !$booking_rights) {
- continue 2;
- }
- }
- $this->selectable_rooms[] = $room;
- }
- } else {
- $this->selectable_rooms = Room::findAll();
- }
- }
- }
- }
- }
-
private function validateDateIds(array $date_ids): array
{
if (count($date_ids) === 0) {
diff --git a/app/controllers/resources/admin.php b/app/controllers/resources/admin.php
index bea43ed..5d36b34 100644
--- a/app/controllers/resources/admin.php
+++ b/app/controllers/resources/admin.php
@@ -1034,6 +1034,45 @@ class Resources_AdminController extends AuthenticatedController
}
+ public function edit_separable_room_action($separable_room_id)
+ {
+ PageLayout::setTitle(_('Teilbaren Raum bearbeiten'));
+ $this->separable_room = SeparableRoom::find($separable_room_id);
+ if (!$this->separable_room) {
+ PageLayout::postError(_('Der teilbare Raum wurde nicht gefunden.'));
+ }
+ }
+
+
+ public function save_separable_room_action($separable_room_id)
+ {
+ CSRFProtection::verifyUnsafeRequest();
+
+ $this->separable_room = SeparableRoom::find($separable_room_id);
+ if (!$this->separable_room) {
+ PageLayout::postError(_('Der teilbare Raum wurde nicht gefunden.'));
+ $this->relocate('resources/admin/separable_rooms');
+ return;
+ }
+
+ $this->separable_room->name = Request::get('name', '');
+ $this->separable_room->description = Request::get('description', '');
+ if (!$this->separable_room->name) {
+ PageLayout::postError(_('Der Name des teilbaren Raumes darf nicht leer sein!'));
+ $this->relocate('resources/admin/separable_rooms');
+ return;
+ }
+
+ $success = $this->separable_room->store() !== false;
+ if ($success) {
+ PageLayout::postSuccess(_('Die Änderungen wurden gespeichert.'));
+ } else {
+ PageLayout::postError(_('Die Änderungen konnten nicht gespeichert werden.'));
+ }
+ $this->relocate('resources/admin/separable_rooms');
+ }
+
+
public function configuration_action()
{
if (Navigation::hasItem('/resources/admin/configuration')) {
diff --git a/app/controllers/resources/room_request.php b/app/controllers/resources/room_request.php
index ebe7baa..f7d9a66 100644
--- a/app/controllers/resources/room_request.php
+++ b/app/controllers/resources/room_request.php
@@ -1288,9 +1288,9 @@ class Resources_RoomRequestController extends AuthenticatedController
foreach ($this->request_time_intervals as $metadate_id => $data) {
if ($data['metadate'] instanceof SeminarCycleDate && !$this->expand_metadates) {
$all_dates_same_room = true;
- // check, if ALL dates are booked for the same room
+ //Check if all dates are booked for the same room:
foreach ($data['intervals'] as $interval) {
- if ($interval['booked_room'] != $selected_room->id) {
+ if (!in_array($selected_room->id, $interval['booked_rooms'])) {
$all_dates_same_room = false;
break;
}
@@ -1567,7 +1567,12 @@ class Resources_RoomRequestController extends AuthenticatedController
return;
}
- if (!$course_date->room_booking || $course_date->room_booking->resource_id !== $room_id) {
+ $room_ids = [];
+ foreach ($course_date->room_bookings as $booking) {
+ $room_ids[] = $booking->resource_id;
+ }
+
+ if (empty($room_ids) || !in_array($room_id, $room_ids)) {
try {
$booking = $room->createBooking(
$this->current_user,
@@ -1623,7 +1628,11 @@ class Resources_RoomRequestController extends AuthenticatedController
if ($metadate->dates) {
$overlap_messages = [];
foreach ($metadate->dates as $date) {
- if (!$date->room_booking || $date->room_booking->resource_id != $room_id) {
+ $room_ids = [];
+ foreach ($date->room_bookings as $booking) {
+ $room_ids[] = $booking->resource_id;
+ }
+ if (empty($room_ids) || !in_array($room_id, $room_ids)) {
try {
$booking = $room->createBooking(
$this->current_user,