<?php

/**
 * @author  David Siegfried <david.siegfried@uni-vechta.de>
 * @license GPL2 or any later version
 * @since   3.4
 */

class Course_TimesroomsController extends AuthenticatedController
{
    /**
     * Common actions before any other action
     *
     * @param String $action Action to be executed
     * @param Array  $args Arguments passed to the action
     *
     * @throws Trails\Exception when either no course was found or the user
     *                          may not access this area
     */
    public function before_filter(&$action, &$args)
    {
        parent::before_filter($action, $args);

        // Try to find a valid course
        if (!Course::findCurrent()) {
            throw new Trails\Exception(404, _('Es wurde keine Veranstaltung ausgewählt!'));
        }

        if (!$GLOBALS['perm']->have_studip_perm('tutor', Course::findCurrent()->id)) {
            throw new AccessDeniedException();
        }

        // Get seminar instance
        $this->course = new Seminar(Course::findCurrent());

        if (Navigation::hasItem('course/admin/dates')) {
            Navigation::activateItem('course/admin/dates');
        }
        $this->locked = false;

        if (LockRules::Check($this->course->id, 'room_time')) {
            $this->locked     = true;
            $this->lock_rules = LockRules::getObjectRule($this->course->id);
            PageLayout::postInfo(
                _('Diese Seite ist für die Bearbeitung gesperrt. Sie können die Daten einsehen, jedoch nicht verändern.')
                . ($this->lock_rules['description'] ? '<br>' . formatLinks($this->lock_rules['description']) : '')
            );
        }

        $this->show = [
            'regular'     => true,
            'irregular'   => true,
            'roomRequest' => !$this->locked && Config::get()->RESOURCES_ENABLE && Config::get()->RESOURCES_ALLOW_ROOM_REQUESTS,
        ];

        PageLayout::setHelpKeyword('Basis.Veranstaltungen');

        $title = _('Verwaltung von Zeiten und Räumen');
        $title = $this->course->getFullName() . ' - ' . $title;

        PageLayout::setTitle($title);


        URLHelper::bindLinkParam('semester_filter', $this->semester_filter);

        if (empty($this->semester_filter)) {
            if (!$this->course->hasDatesOutOfDuration() && count($this->course->semesters) == 1) {
                $this->semester_filter = $this->course->start_semester->id;
            } else {
                $this->semester_filter = 'all';
            }
        }
        if ($this->semester_filter == 'all') {
            $this->course->applyTimeFilter(0, 0);
        } else {
            $semester = Semester::find($this->semester_filter);
            $this->course->applyTimeFilter($semester['beginn'], $semester['ende']);
        }

        if ($this->course->isOpenEnded()) {
            $selectable_semesters = Semester::getAll();
        } else {
            $selectable_semesters = $this->course->semesters->toArray();
        }
        if (count($selectable_semesters) > 1 || (count($selectable_semesters) == 1 && $this->course->hasDatesOutOfDuration())) {
            $selectable_semesters[] = ['name' => _('Alle Semester'), 'semester_id' => 'all'];
        }
        $this->selectable_semesters = array_reverse($selectable_semesters);

        if (!Request::isXhr()) {
            $this->setSidebar();
        } elseif (Request::isXhr() && $this->flash['update-times']) {
            $semester_id = $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE ?? '';
            $this->response->add_header(
                'X-Raumzeit-Update-Times',
                json_encode([
                    'course_id' => $this->course->id,
                    'html'      => $this->course->getDatesHTML([
                        'semester_id' => $semester_id,
                        'show_room'   => true,
                    ]) ?: _('nicht angegeben'),
                ])
            );
        }
    }


    protected function bookingTooShort(int $start_time, int $end_time)
    {
        return Config::get()->RESOURCES_MIN_BOOKING_TIME &&
            (($end_time - $start_time) < Config::get()->RESOURCES_MIN_BOOKING_TIME * 60);
    }

    /**
     * Displays the times and rooms of a course
     *
     * @param mixed $course_id Id of the course (optional, defaults to
     *                         globally selected)
     */
    public function index_action()
    {
        Helpbar::get()->addPlainText(_('Rot'), _('Kein Termin hat eine Raumbuchung.'));
        Helpbar::get()->addPlainText(_('Gelb'), _('Mindestens ein Termin hat keine Raumbuchung.'));
        Helpbar::get()->addPlainText(_('Grün'), _('Alle Termine haben eine Raumbuchung.'));

        if (Request::isXhr()) {
            $this->show = [
                'regular'     => true,
                'irregular'   => true,
                'roomRequest' => true,
            ];
        }
        $this->linkAttributes   = ['fromDialog' => Request::isXhr() ? 1 : 0];
        $this->semester         = array_reverse(Semester::getAll());
        $this->current_semester = Semester::findCurrent();
        $this->cycle_dates      = [];
        $matched                = [];

        $this->cycle_room_names = [];

        foreach ($this->course->cycles as $cycle) {
            $cycle_has_multiple_rooms = false;
            foreach ($cycle->getAllDates() as $val) {
                foreach ($this->semester as $sem) {
                    if ($this->semester_filter !== 'all' && $this->semester_filter !== $sem->id) {
                        continue;
                    }

                    if ($sem->beginn <= $val->date && $sem->ende >= $val->date) {
                        if (!isset($this->cycle_dates[$cycle->metadate_id])) {
                            $this->cycle_dates[$cycle->metadate_id] = [
                                'cycle'        => $cycle,
                                'dates'        => [],
                                'room_request' => [],
                            ];
                        }
                        if (!isset($this->cycle_dates[$cycle->metadate_id]['dates'][$sem->id])) {
                            $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();
                        }
                        $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;
                                }
                            } elseif ($date_room) {
                                $this->cycle_room_names[$cycle->id] = $date_room;
                            }
                        }
                    }
                }
            }
            if ($cycle_has_multiple_rooms) {
                $this->cycle_room_names[$cycle->id] = _('mehrere gebuchte Räume');
            }
        }

        $dates = $this->course->getDatesWithExdates();

        $this->current_user = User::findCurrent();
        $this->user_has_permissions = ResourceManager::userHasGlobalPermission($this->current_user, 'admin');

        $check_room_requests = Config::get()->RESOURCES_ALLOW_ROOM_REQUESTS;
        $this->room_requests = RoomRequest::findBySQL(
            'course_id = :course_id
            ORDER BY course_id, metadate_id, termin_id',
            [
                'course_id' => $this->course->id
            ]
        );

        $this->global_requests = $this->course->room_requests->filter(function (RoomRequest $request) {
            return $request->closed < 2 && !$request->termin_id;
        });

        $single_dates  = [];
        $this->single_date_room_request_c = 0;
        foreach ($dates as $val) {
            foreach ($this->semester as $sem) {
                if ($this->semester_filter !== 'all' && $this->semester_filter !== $sem->id) {
                    continue;
                }

                if ($sem->beginn > $val->date || $sem->ende < $val->date || $val->metadate_id != '') {
                    continue;
                }

                if (!isset($single_dates[$sem->id])) {
                    $single_dates[$sem->id] = new SimpleCollection();
                }
                $single_dates[$sem->id]->append($val);

                $matched[] = $val->id;
                if ($check_room_requests) {
                    $this->single_date_room_request_c += ResourceRequest::countBySql(
                        "resource_requests.closed < '2' AND termin_id = :termin_id",
                        [
                            'termin_id' => $val->id
                        ]
                    );
                }
            }
        }

        if ($this->semester_filter === 'all') {
            $out_of_bounds = $dates->findBy('id', $matched, '!=');
            if (count($out_of_bounds)) {
                $single_dates['none'] = $out_of_bounds;
            }
        }

        $this->single_dates  = $single_dates;
        $this->checked_dates = [];
        if (!empty($_SESSION['_checked_dates'])) {
            $this->checked_dates = $_SESSION['_checked_dates'];
            unset($_SESSION['_checked_dates']);
        }
        if (Request::isDialog()) {
            $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "'.$this->course->getId().'"}');
        }
    }

    /**
     * Edit the start-semester of a course
     *
     * @throws Trails\Exceptions\DoubleRenderError
     */
    public function editSemester_action()
    {
        URLHelper::addLinkParam('origin', Request::option('origin', 'course_timesrooms'));
        $this->semester = array_reverse(Semester::getAll());
        $this->current_semester = Semester::findCurrent();
        if (Request::submitted('save')) {
            CSRFProtection::verifyUnsafeRequest();
            $start_semester = Semester::find(Request::get('startSemester'));
            if (Request::get('endSemester') != '-1' && Request::get('endSemester') != '0') {
                $end_semester = Semester::find(Request::get('endSemester'));
            } else {
                $end_semester = Request::int('endSemester');
            }

            $course = Course::findCurrent();

            if ($start_semester == $end_semester) {
                $end_semester = 0;
            }

            if ($end_semester != 0 && $end_semester != -1 && $start_semester->beginn >= $end_semester->beginn) {
                PageLayout::postError(_('Das Startsemester liegt nach dem Endsemester!'));
            } else {
                $old_start_weeks = !$course->isOpenEnded() ? $course->start_semester->getStartWeeks($course->end_semester) : [];
                //set the new semester array:
                if ($end_semester == -1) {
                    $course->semesters = [];
                } elseif($end_semester == 0)  {
                    $course->semesters = [$start_semester];
                } else {
                    $selected_semesters = [];
                    foreach (Semester::getAll() as $sem) {
                        if ($sem['beginn'] >= $start_semester['beginn'] && $sem['ende'] <= $end_semester['ende']) {
                            $selected_semesters[] = $sem;
                        }
                    }
                    $course->semesters = $selected_semesters;
                }

                // set the semester-chooser to the first semester
                $this->course->setFilter($course->getStartSemester());
                $this->semester_filter = $start_semester->semester_id;

                $course->store();

                if (!$course->isOpenEnded()) {
                    $new_start_weeks = $course->start_semester->getStartWeeks($course->end_semester);
                    SeminarCycleDate::removeOutRangedSingleDates($this->course->getStartSemester(), $this->course->getEndSemesterVorlesEnde(), $course->id);
                    $cycles = SeminarCycleDate::findBySeminar_id($course->id);
                    foreach ($cycles as $cycle) {
                        $cycle->end_offset = $this->getNewEndOffset($cycle, $old_start_weeks, $new_start_weeks);
                        $cycle->generateNewDates();
                        $cycle->store();
                    }
                }

                $messages = $this->course->getStackedMessages();
                foreach ($messages as $type => $msg) {
                    PageLayout::postMessage(MessageBox::$type($msg['title'], $msg['details']));
                }
                $this->relocate(str_replace('_', '/', Request::option('origin')));
            }
            if (Request::isDialog()) {
                $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "'.$this->course->getId().'"}');
            }
        }
    }

    /**
     * Primary function to edit date-informations
     *
     * @param string $termin_id
     */
    public function editDate_action($termin_id)
    {
        PageLayout::setTitle(_('Einzeltermin bearbeiten'));
        $this->date       = CourseDate::find($termin_id) ?: CourseExDate::find($termin_id);
        $this->attributes = [];

        $this->only_bookable_rooms = Request::submitted('only_bookable_rooms');

        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!')
                        );
                    }
                }
            }
            $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->preparation_time = $this->date->room_booking instanceof ResourceBooking
                                ? $this->date->room_booking->preparation_time / 60
                                : '0';
        $this->max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
    }


    /**
     * Clone an existing date
     *
     * @param $termin_id
     */
    public function cloneDate_action($termin_id)
    {
        $date = CourseDate::find($termin_id);

        if ($date) {
            $termin = CourseDate::build($date);
            $termin->setId($termin->getNewId());

            $termin->dozenten = $date->dozenten;
            $termin->statusgruppen = $date->statusgruppen;
            $termin->store();

            PageLayout::postSuccess(sprintf(
                _('Der Termin "%s" wurde dupliziert.'),
                htmlReady($termin->getFullName())
            ));
        }

        $this->redirect('course/timesrooms/index');
    }


    /**
     * Save date-information
     *
     * @param $termin_id
     *
     * @throws Trails\Exceptions\DoubleRenderError
     */
    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')));
        $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;
            PageLayout::postError(_('Die Zeitangaben sind nicht korrekt. Bitte überprüfen Sie diese!'));
        }
        if ($this->bookingTooShort($date, $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/editDate/' . $termin_id, ['contentbox_open' => $termin->metadate_id]);
            return;
        }

        $time_changed = ($date != $termin->date || $end_time != $termin->end_time);
        //time changed for regular date. create normal singledate and cancel the regular date
        if ($termin->metadate_id != '' && $time_changed) {
            $termin_values = $termin->toArray();
            $termin_info   = $termin->getFullName();

            $termin->cancelDate();
            PageLayout::postInfo(sprintf(
                _('Der Termin %s wurde aus der Liste der regelmäßigen Termine'
               . ' gelöscht und als unregelmäßiger Termin eingetragen, da Sie die Zeiten des Termins verändert haben,'
               . ' so dass dieser Termin nun nicht mehr regelmäßig ist.'),
                htmlReady($termin_info)
            ));

            $termin = new CourseDate();
            unset($termin_values['metadate_id']);
            $termin->setData($termin_values);
            $termin->setId($termin->getNewId());
        }
        $termin->date_typ = Request::get('course_type');

        // Set assigned teachers
        $assigned_teachers = Request::optionArray('assigned_teachers');
        $dozenten          = $this->course->getMembers();
        if (count($assigned_teachers) === count($dozenten) || empty($assigned_teachers)) {
            //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);
        }

        // Set assigned groups
        $assigned_groups       = Request::optionArray('assigned-groups');
        $termin->statusgruppen = Statusgruppen::findMany($assigned_groups);

        $termin->store();

        if ($time_changed) {
            NotificationCenter::postNotification('CourseDidChangeSchedule', $this->course);
        }

        // Set Room
        if ($termin->room_booking) {
            $old_room_id = $termin->room_booking->resource_id;
        } else {
            $old_room_id = null;
        }
        $singledate = new SingleDate($termin);
        if ($singledate->setTime($date, $end_time)) {
            $singledate->store();
        }

        if ((Request::option('room') == 'room') || Request::option('room') == 'nochange') {
            //Storing the SingleDate above has deleted the room booking
            //(see SingleDateDB::storeSingleDate). Therefore, the booking
            //has to be recereated, even if the room option
            //is set to "nochange".
            $room_id = null;
            $preparation_time = Request::int('preparation_time', 0);
            if (Request::option('room') == 'room') {
                $room_id = Request::get('room_id');
                if ($preparation_time > $max_preparation_time) {
                    PageLayout::postError(
                        sprintf(
                            _('Die eingegebene Rüstzeit überschreitet das erlaubte Maximum von %d Minuten!'),
                            $max_preparation_time
                        )
                    );
                }
            }
            if ($room_id) {
                if ($room_id != $singledate->resource_id) {
                    if ($singledate->bookRoom($room_id, $preparation_time ?: 0)) {
                        $messages = $singledate->getMessages();
                        $this->course->appendMessages($messages);
                    } else if (!$singledate->ex_termin) {
                        $this->course->createError(
                            sprintf(
                                _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'),
                                '<strong>' . htmlReady($singledate->toString()) . '</strong>'
                            )
                        );
                    }
                } elseif ($termin->room_booking->preparation_time != ($preparation_time * 60)) {
                    $singledate->bookRoom($room_id, $preparation_time ?: 0);
                }
            } else if ($old_room_id && !$singledate->resource_id) {
                $this->course->createInfo(
                    sprintf(
                        _('Die Raumbuchung für den Termin %s wurde aufgehoben, da die neuen Zeiten außerhalb der alten liegen!'),
                        '<strong>'.htmlReady( $singledate->toString()) .'</strong>'
                    ));
            } else if (Request::get('room_id_parameter')) {
                $this->course->createInfo(
                    _('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
                );
            }
        } elseif (Request::option('room') == 'freetext') {
            $singledate->setFreeRoomText(Request::get('freeRoomText_sd'));
            $singledate->killAssign();
            $singledate->store();
            $this->course->createMessage(sprintf(
                _('Der Termin %s wurde geändert, etwaige Raumbuchungen wurden entfernt und stattdessen der angegebene Freitext eingetragen!'),
                '<strong>' . htmlReady($singledate->toString()) . '</strong>'
            ));
        } elseif (Request::option('room') == 'noroom') {
            $singledate->setFreeRoomText('');
            $singledate->killAssign();
            $singledate->store();
            $this->course->createMessage(sprintf(
                _('Der Termin %s wurde geändert, etwaige freie Ortsangaben und Raumbuchungen wurden entfernt.'),
                '<strong>' . htmlReady($singledate->toString()) . '</strong>'
            ));
        }
        if (!empty($singledate->messages['error'])) {
            PageLayout::postError(
                _('Die folgenden Fehler traten beim Bearbeiten des Termins auf:'),
                htmlReady($singledate->messages['error'])
            );
        }

        $this->displayMessages();
        $this->redirect('course/timesrooms/index', ['contentbox_open' => $termin->metadate_id]);
    }


    /**
     * Create Single Date
     */
    public function createSingleDate_action()
    {
        PageLayout::setTitle(Course::findCurrent()->getFullName() . " - " . _('Einzeltermin anlegen'));
        $this->restoreRequest(
            words('date start_time end_time room related_teachers related_statusgruppen freeRoomText dateType fromDialog course_type'),
            $_SESSION['last_single_date'] ?? null
        );

        if (Config::get()->RESOURCES_ENABLE) {
            $this->setAvailableRooms(null);
        }
        $this->teachers = $this->course->getMembers('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);
        }

        if (!Request::get('room_id')) {
            $termin->raum = Request::get('freeRoomText');
            $termin->store();
        } else {
            $termin->store();
            $singledate = new SingleDate($termin);
            $singledate->bookRoom(Request::option('room_id'));
            $this->course->appendMessages($singledate->getMessages());
        }
        if (Request::get('room_id_parameter')) {
            $this->course->createInfo(
                _('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
            );
        }

        // store last created date in session
        $_SESSION['last_single_date'] = Request::getInstance();

        if ($start_time < $this->course->filterStart || $end_time > $this->course->filterEnd) {
            $this->course->setFilter('all');
        }

        $this->course->createMessage(sprintf(_('Der Termin %s wurde hinzugefügt!'), htmlReady($termin->getFullName())));
        $this->course->store();
        $this->displayMessages();

        $this->relocate('course/timesrooms/index');
    }

    /**
     * Restores a previously removed date.
     *
     * @param String $termin_id Id of the previously removed date
     */
    public function undeleteSingle_action($termin_id, $from_dates = false)
    {
        $ex_termin = CourseExDate::find($termin_id);
        $termin    = $ex_termin->unCancelDate();
        if ($termin) {
            $this->course->createMessage(sprintf(
                _('Der Termin %s wurde wiederhergestellt!'),
                htmlReady($termin->getFullName())
            ));
            $this->displayMessages();
        }

        if ($from_dates) {
            $this->redirect("course/dates#date_{$termin_id}");
        } else {
            $params = [];
            if ($termin->metadate_id != '') {
                $params['contentbox_open'] = $termin->metadate_id;
            }
            $this->redirect('course/timesrooms/index', $params);
        }
    }

    /**
     * Performs a stack action defined by url parameter method.
     *
     * @param String $cycle_id Id of the cycle the action should be performed
     *                         upon
     */
    public function stack_action($cycle_id = '')
    {
        $_SESSION['_checked_dates'] = Request::optionArray('single_dates');
        if (empty($_SESSION['_checked_dates']) && isset($_SESSION['_checked_dates'])) {
            PageLayout::postError(_('Sie haben keine Termine ausgewählt!'));
            $this->redirect('course/timesrooms/index', ['contentbox_open' => $cycle_id]);

            return;
        }

        $this->linkAttributes = ['fromDialog' => Request::int('fromDialog') ? 1 : 0];

        switch (Request::get('method')) {
            case 'edit':
                PageLayout::setTitle(_('Termine bearbeiten'));
                $this->editStack($cycle_id);
                break;
            case 'preparecancel':
                PageLayout::setTitle(_('Termine ausfallen lassen'));
                $this->prepareCancel($cycle_id);
                break;
            case 'undelete':
                PageLayout::setTitle(_('Termine stattfinden lassen'));
                $this->unDeleteStack($cycle_id);
                break;
            case 'request':
                PageLayout::setTitle(
                    _('Anfrage auf ausgewählte Termine stellen')
                );
                $this->requestStack($cycle_id);
        }
    }


    /**
     * Edits a stack/cycle.
     *
     * @param String $cycle_id Id of the cycle to be edited.
     */
    private function editStack($cycle_id)
    {
        $this->cycle_id = $cycle_id;
        $this->teachers = $this->course->getMembers();
        $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_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;
        });
        $this->preparation_time = floor($preparation_time / 60);

        $this->max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
        $this->render_template('course/timesrooms/editStack');
    }

    /**
     * Checks a specific field value of the specified course dates for equality.
     * A closure defines which field of the course dates to check.
     *
     * @param CourseDate[] $dates The dates from which to extract values.
     * @param Closure $callback The closure that extracts values from a CourseDate object that is passed to it.
     * @return mixed The identical value that has been retrieved from all course dates or a value that is equal to
     *     false in case the value differs. The returned result might be a string or an array or it may be empty.
     */
    protected function getSameFieldValue(array $dates, Closure $callback)
    {
        $data = array_map($callback, $dates);

        $initial = null;
        foreach ($data as $item) {
            if ($initial === null) {
                $initial = $item;
                continue;
            }

            // Compare array by checking their sizes and different items
            if (
                is_array($initial)
                && (
                    count($initial) !== count($item)
                    || count(array_diff($initial, $item)) > 0
                )
            ) {
                return [];
            }

            // Otherwise compare items themselves
            if (!is_array($initial) && $initial != $item) {
                return '';
            }
        }

        return $initial;
    }

    /**
     * Creates a new room request for the selected dates.
     */
    protected function requestStack($cycle_id)
    {
        $appointment_ids = [];

        foreach ($_SESSION['_checked_dates'] as $appointment_id) {
            if (CourseDate::exists($appointment_id)) {
                $appointment_ids[] = $appointment_id;
            }
        }

        if (!$appointment_ids) {
            PageLayout::postError(_('Es wurden keine gültigen Termin-IDs übergeben!'));
            $this->relocate('course/timesrooms/index', ['contentbox_open' => $cycle_id]);
            return;
        }

        $this->redirect(
            'course/room_requests/new_request',
            [
                'range' => 'date-multiple',
                'range_str' => 'date-multiple',
                'range_ids' => $appointment_ids
            ]
        );
    }


    /**
     * Prepares a stack/cycle to be canceled.
     *
     * @param String $cycle_id Id of the cycle to be canceled.
     */
    private function prepareCancel($cycle_id)
    {
        $this->cycle_id = $cycle_id;
        $this->render_template('course/timesrooms/cancelStack');
    }

    /**
     * Restores a previously deleted stack/cycle.
     *
     * @param String $cycle_id Id of the cycle to be restored.
     */
    private function unDeleteStack($cycle_id = '')
    {
        foreach ($_SESSION['_checked_dates'] as $id) {
            $ex_termin = CourseExDate::find($id);
            if ($ex_termin === null) {
                continue;
            }
            $ex_termin->content = '';
            $termin             = $ex_termin->unCancelDate();
            if ($termin !== null) {
                $this->course->createMessage(sprintf(
                    _('Der Termin %s wurde wiederhergestellt!'),
                    htmlReady($termin->getFullName())
                ));
            }
        }
        $this->displayMessages();

        $this->relocate('course/timesrooms/index', ['contentbox_open' => $cycle_id]);
    }

    /**
     * Saves a stack/cycle.
     *
     * @param String $cycle_id Id of the cycle to be saved.
     */
    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();
                break;
            case 'preparecancel':
                $this->saveCanceledStack();
                break;
            case 'request':
                $this->saveRequestStack();
        }

        $this->displayMessages();

        $this->relocate('course/timesrooms/index', ['contentbox_open' => $cycle_id]);
    }

    /**
     * Saves a canceled stack/cycle.
     *
     * @param String $cycle_id Id of the canceled cycle to be saved.
     */
    private function saveCanceledStack()
    {
        $deleted_dates = [];
        $cancel_comment = trim(Request::get('cancel_comment'));
        $cancel_send_message = Request::int('cancel_send_message');

        foreach ($_SESSION['_checked_dates'] as $id) {
            $termin = CourseDate::find($id);
            if ($termin) {
                $deleted_dates[] = $this->deleteDate($termin, $cancel_comment);
            }
        }

        if ($cancel_send_message && $cancel_comment != '' && count($deleted_dates) > 0) {
            $snd_messages = raumzeit_send_cancel_message($cancel_comment, $deleted_dates);
            if ($snd_messages > 0) {
                $this->course->createMessage(_('Alle Teilnehmenden wurden benachrichtigt.'));
            }
        }
    }

    /**
     * Saves an edited stack/cycle.
     *
     * @param String $cycle_id Id of the edited cycle to be saved.
     */
    private function saveEditedStack()
    {
        $persons         = Request::getArray('related_persons');
        $action          = Request::get('related_persons_action');
        $groups          = Request::getArray('related_groups');
        $group_action    = Request::get('related_groups_action');
        $lecture_changed = false;
        $groups_changed  = false;
        $singledates     = [];

        if (is_array($_SESSION['_checked_dates'])) {
            foreach ($_SESSION['_checked_dates'] as $singledate_id) {
                $singledate = CourseDate::find($singledate_id);
                if (!isset($singledate)) {
                    $singledate = CourseExDate::find($singledate_id);
                }
                $singledates[] = $singledate;
            }
        }

        // Update related persons
        if (in_array($action, words('add delete'))) {
            $course_lectures = $this->course->getMembers();
            $persons         = User::findMany($persons);
            foreach ($singledates as $singledate) {
                if ($action === 'add') {
                    if (count($course_lectures) === count($persons)) {
                        $singledate->dozenten = [];
                    } else {
                        foreach ($persons as $person) {
                            if (!count($singledate->dozenten->findBy('id', $person->id))) {
                                $singledate->dozenten[] = $person;
                            }
                        }
                        if (count($singledate->dozenten) === count($course_lectures)) {
                            $singledate->dozenten = [];
                        }
                    }

                    $lecture_changed = true;
                }

                if ($action === 'delete') {
                    foreach ($persons as $person) {
                        $singledate->dozenten->unsetBy('id', $person->id);
                    }
                    $lecture_changed = true;
                }
                $singledate->store();
            }
        }

        if ($lecture_changed) {
            $this->course->createMessage(_('Zuständige Personen für die Termine wurden geändert.'));
        }

        if (in_array($group_action, words('add delete'))) {
            $course_groups = Statusgruppen::findBySeminar_id($this->course->id);
            $groups        = Statusgruppen::findMany($groups);
            foreach ($singledates as $singledate) {
                if ($group_action === 'add') {
                    if (count($course_groups) === count($groups)) {
                        $singledate->statusgruppen = [];
                    } else {

                        foreach ($groups as $group) {
                            if (!count($singledate->statusgruppen->findBy('id', $group->id))) {
                                $singledate->statusgruppen[] = $group;
                            }
                        }
                        if (count($singledate->statusgruppen) === count($course_groups)) {
                            $singledate->statusgruppen = [];
                        }
                    }
                    $groups_changed = true;
                }
                if ($group_action === 'delete') {
                    foreach ($groups as $group) {
                        $singledate->statusgruppen->unsetByPk($group->id);
                    }
                    $groups_changed = true;
                }
                $singledate->store();
            }
        }

        if ($groups_changed) {
            $this->course->createMessage(_('Zugewiesene Gruppen für die Termine wurden geändert.'));
        }

        if (in_array(Request::get('action'), ['room', 'freetext', 'noroom']) || Request::get('course_type')) {
            foreach ($singledates as $key => $singledate) {
                $date = new SingleDate($singledate);
                if (Request::option('action') == 'room') {
                    $preparation_time = Request::get('preparation_time');
                    $max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME;
                    if ($preparation_time > $max_preparation_time) {
                        $this->course->createError(
                            sprintf(
                                _('Die eingegebene Rüstzeit überschreitet das erlaubte Maximum von %d Minuten!'),
                                $max_preparation_time
                            )
                        );
                        continue;
                    }
                    if (Request::option('room_id')) {
                        if (Request::option('room_id') != $singledate->room_booking->resource_id) {
                            if ($date->bookRoom(Request::option('room_id'), $preparation_time)) {
                                $messages = $date->getMessages();
                                $this->course->appendMessages($messages);
                            } else if (!$date->ex_termin) {
                                $this->course->createError(sprintf(
                                    _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'),
                                    '<strong>' . htmlReady($date->toString() ) . '</strong>')
                                );
                            }
                        } elseif (($preparation_time * 60) != $singledate->room_booking->preparation_time) {
                            $date->bookRoom(Request::option('room_id'), $preparation_time);
                            $messages = $date->getMessages();
                            $this->course->appendMessages($messages);
                        }
                    } else if (Request::get('room_id_parameter')) {
                        $this->course->createInfo(
                            ('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!')
                        );
                    }
                } elseif (Request::option('action') == 'freetext') {
                    $date->setFreeRoomText(Request::get('freeRoomText'));
                    $date->store();
                    $date->killAssign();
                    $this->course->createMessage(sprintf(
                        _('Der Termin %s wurde geändert, etwaige Raumbuchungen wurden entfernt und stattdessen der angegebene Freitext eingetragen!'),
                        '<strong>' . htmlReady($date->toString()) . '</strong>'
                    ));
                } elseif (Request::option('action') == 'noroom') {
                    $date->setFreeRoomText('');
                    $date->store();
                    $date->killAssign();
                    $this->course->createMessage(sprintf(
                        _('Der Termin %s wurde geändert, etwaige freie Ortsangaben und Raumbuchungen wurden entfernt.'),
                        '<strong>' . htmlReady($date->toString()) . '</strong>'
                    ));
                }

                if (Request::get('course_type') != '') {
                    $date->setDateType(Request::get('course_type'));
                    $date->store();
                    $this->course->createMessage(sprintf(
                        _('Die Art des Termins %s wurde geändert.'),
                        '<strong>' . htmlReady($date->toString()) . '</strong>'
                    ));
                }
            }
        }
    }


    /**
     * The data saving part of the action to create one request
     * for multiple appointments.
     */
    public function saveRequestStack()
    {
        //The properties[] array is set by $rp->definition->toHtmlInput.
        //The default name for toHtmlInput input elements is:
        //properties[$property->id].
        $set_property_values = Request::getArray('properties');
        $set_properties = [];
        foreach ($set_property_values as $id => $value) {
            if (!ResourcePropertyDefinition::exists($id)) {
                continue;
            }
            $property = new ResourceRequestProperty();
            $property->property_id = $id;
            $property->state = $value;
            $set_properties[] = $property;
        }

        $appointments = [];
        foreach ($_SESSION['_checked_dates'] as $appointment_id) {
            $appointment = CourseDate::find($appointment_id);
            if ($appointment) {
                $appointments[] = $appointment;
            }
        }

        if (!$appointments) {
            $this->course->createError(
                _('Es wurden keine gültigen Termin-IDs übergeben!')
            );
            return;
        }

        $request = new RoomRequest();
        $request->course_id = $this->course->getId();
        $request->user_id = $GLOBALS['user']->id;
        $request->comment = Request::get('comment');
        $request->closed = '0';
        if (!$request->store()) {
            $this->course->createError(
                _('Fehler beim Speichern der Anfrage!')
            );
            return;
        }

        //Now we store the requested properties:
        $successfully_stored = 0;
        foreach ($set_properties as $property) {
            $property->request_id = $request->id;
            if ($property->store()) {
                $successfully_stored++;
            }
        }

        if ($set_properties && $successfully_stored < count($set_properties)) {
            $this->course->createError(
                _('Es wurden nicht alle zur Anfrage gehörenden Eigenschaften gespeichert!')
            );
        }

        //Finally we can create ResourceRequestAppointment
        //objects for each appointment:

        $successfully_stored = 0;
        foreach ($appointments as $appointment) {
            $rra = new ResourceRequestAppointment();
            $rra->request_id = $request->id;
            $rra->appointment_id = $appointment->id;
            if ($rra->store()) {
                $successfully_stored++;
            }
        }

        if (($successfully_stored < count($appointments)) && count($appointments)) {
            $this->course->createError(
                _('Es wurden nicht alle zur Anfrage gehörenden Terminzuordnungen gespeichert!')
            );
            return;
        }

        $this->course->createMessage(
            _('Die Raumanfrage wurde gespeichert!')
        );
    }


    /**
     * Creates a cycle.
     *
     * @param String $cycle_id Id of the cycle to be created (optional)
     */
    public function createCycle_action($cycle_id = null)
    {
        PageLayout::setTitle(Course::findCurrent()->getFullName() . " - " . _('Regelmäßige Termine anlegen'));
        $this->restoreRequest(
            words('day start_time end_time description cycle startWeek teacher_sws fromDialog course_type')
        );

        $this->linkAttributes = ['fromDialog' => Request::bool('fromDialog')];

        $this->cycle = new SeminarCycleDate($cycle_id);

        if ($this->cycle->isNew()) {
            $this->has_bookings = false;
        } else {
            $ids = $this->cycle->dates->pluck('termin_id');

            $count = ResourceBooking::countBySQL(
                'range_id IN ( :range_ids )',
                [
                    'range_ids' => ($ids ?: '')
                ]
            );
            $this->has_bookings = $count > 0;
        }

        if ($this->course->isOpenEnded()) { // course with endless lifespan
            $end_semester = array_values(array_filter(Semester::getAll(), function ($s) {return $s->past === false;}));
        } else { // course over one or more semester
            $end_semester = $this->course->semesters->getArrayCopy();
        }
        $this->start_weeks = [];
        $this->end_semester_weeks = [];
        if (!empty($end_semester)) {
            $this->start_weeks = $end_semester[0]->getStartWeeks($end_semester[count($end_semester) - 1]);

            foreach ($end_semester as $sem) {

                $weeks = $sem->getStartWeeks();

                foreach ($this->start_weeks as $key => $week) {
                    if (mb_strpos($week, mb_substr($weeks[0], -15)) !== false) {
                        $this->end_semester_weeks['start'][] = [
                            'value' => $key,
                            'label' => sprintf(_('Anfang %s'), $sem->name)
                        ];
                    }
                    if (mb_strpos($week, mb_substr($weeks[count($weeks) - 1], -15)) !== false) {
                        $this->end_semester_weeks['ende'][] = [
                            'value' => $key,
                            'label' => sprintf(_('Ende %s'), $sem->name)
                        ];
                    }
                    foreach ($weeks as $val) {
                        if (mb_strpos($week, mb_substr($val, -15)) !== false) {
                            $this->clean_weeks[(string) $sem->name][$key] = $val;
                        }
                    }
                }
            }

            if (count($end_semester) > 1) {
                $this->end_semester_weeks['ende'][] = ['value' => -1, 'label' => _('Alle Semester')];
            }
        }
    }

    /**
     * Saves a cycle
     */
    public function saveCycle_action()
    {
        CSRFProtection::verifyRequest();
        $start = strtotime(Request::get('start_time'));
        $end   = strtotime(Request::get('end_time'));

        if ($start === false || $end === false || $start > $end
        || !$this->validate_datetime(Request::get('start_time'))
        || !$this->validate_datetime(Request::get('end_time'))) {
            $this->storeRequest();
            PageLayout::postError(_('Die Zeitangaben sind nicht korrekt. Bitte überprüfen Sie diese!'));
            $this->redirect('course/timesrooms/createCycle');

            return;
        } elseif (Request::int('startWeek') > Request::int('endWeek') && Request::int('endWeek') != -1) {
            $this->storeRequest();
            PageLayout::postError(_('Die Endwoche liegt vor der Startwoche. Bitte überprüfen Sie diese Angabe!'));
            $this->redirect('course/timesrooms/createCycle');

            return;
        }
        if ($this->bookingTooShort($start, $end)) {
            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/createCycle');
            return;
        }

        $cycle              = new SeminarCycleDate();
        $cycle->seminar_id  = $this->course->id;
        $cycle->weekday     = Request::int('day');
        $cycle->description = Request::get('description');
        $cycle->sws         = round(Request::float('teacher_sws'), 1);
        $cycle->cycle       = Request::int('cycle');
        $cycle->week_offset = Request::int('startWeek');
        $cycle->end_offset  = Request::int('endWeek');
        $cycle->start_time  = date('H:i:00', $start);
        $cycle->end_time    = date('H:i:00', $end);

        if ($cycle->end_offset == -1) {
            $cycle->end_offset = NULL;
        }

        if ($cycle->store()) {
            if(Request::int('course_type')) {
                $cycle->setSingleDateType(Request::int('course_type'));
            }

            $cycle_info = $cycle->toString();
            NotificationCenter::postNotification('CourseDidChangeSchedule', $this->course);

            $this->course->createMessage(sprintf(
                _('Die regelmäßige Veranstaltungszeit %s wurde hinzugefügt!'),
                htmlReady($cycle_info))
            );
            $this->displayMessages();
            $this->relocate('course/timesrooms/index');
        } else {
            $this->storeRequest();
            $this->course->createError(
                _('Die regelmäßige Veranstaltungszeit konnte nicht hinzugefügt werden! Bitte überprüfen Sie Ihre Eingabe.')
            );
            $this->displayMessages();
            $this->redirect('course/timesrooms/createCycle');
        }
    }

    /**
     * Edits a cycle
     *
     * @param String $cycle_id Id of the cycle to be edited
     */
    public function editCycle_action($cycle_id)
    {
        $cycle = SeminarCycleDate::find($cycle_id);
        $start = strtotime(Request::get('start_time'));
        $end   = strtotime(Request::get('end_time'));

        // Prepare Request for saving Request
        if ($start === false || $end === false || $start > $end) {
            PageLayout::postError(_('Die Zeitangaben sind nicht korrekt. Bitte überprüfen Sie diese!'));
        } else {
            $cycle->start_time  = date('H:i:00', $start);
            $cycle->end_time    = date('H:i:00', $end);
        }

        //Check the duration:
        if ($this->bookingTooShort($start, $end)) {
            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/createCycle/' . $cycle_id);
            return;
        }

        $cycle->weekday     = Request::int('day');
        $cycle->description = Request::get('description');
        $cycle->sws         = Request::get('teacher_sws');
        $cycle->cycle       = Request::get('cycle');
        $cycle->week_offset = Request::int('startWeek');
        $cycle->end_offset  = Request::int('endWeek');

        if ($cycle->end_offset == -1) {
            $cycle->end_offset = NULL;
        }

        $changed_dates = 0;
        if (Request::int('course_type')) {
            $changed_dates = $cycle->setSingleDateType(Request::int('course_type'));
        }

        if ($changed_dates > 0 || $cycle->isDirty()) {
            $cycle->chdate = time();
            $cycle->store();

            if ($changed_dates > 0) {
                PageLayout::postSuccess(sprintf(ngettext(
                    _('Die Art des Termins wurde bei 1 Termin geändert'),
                    _('Die Art des Termins wurde bei %u Terminen geändert'),
                    $changed_dates
                ), $changed_dates));
            } else {
                PageLayout::postSuccess(_('Änderungen gespeichert!'));
            }
        } else {
            PageLayout::postInfo(_('Es wurden keine Änderungen vorgenommen'));
        }

        $this->relocate('course/timesrooms/index');
    }

    /**
     * Deletes a cycle
     *
     * @param String $cycle_id Id of the cycle to be deleted
     */
    public function deleteCycle_action($cycle_id)
    {
        CSRFProtection::verifyRequest();
        $cycle = SeminarCycleDate::find($cycle_id);
        if ($cycle === null) {
            $message = sprintf(_('Es gibt keinen regelmäßigen Eintrag "%s".'), $cycle_id);
            PageLayout::postError($message);
        } else {
            $cycle_string = $cycle->toString();
            if ($cycle->delete()) {
                PageLayout::postSuccess(sprintf(
                    _('Der regelmäßige Eintrag "%s" wurde gelöscht.'),
                    '<strong>' . htmlReady($cycle_string) . '</strong>'
                ));
            }
        }

        $this->redirect('course/timesrooms/index');
    }

    /**
     * Add information to canceled / holiday date
     *
     * @param String $termin_id Id of the date
     */
    public function cancel_action($termin_id)
    {
        PageLayout::setTitle(_('Kommentar hinzufügen'));
        $this->termin = CourseDate::find($termin_id) ?: CourseExDate::find($termin_id);
    }

    /**
     * Saves a comment for a given date.
     *
     * @param String $termin_id Id of the date
     */
    public function saveComment_action($termin_id)
    {
        $termin = CourseExDate::find($termin_id);

        if (is_null($termin)) {
            $termin = CourseDate::find($termin_id);
        }
        if (Request::get('cancel_comment') != $termin->content) {
            $termin->content = Request::get('cancel_comment');
            if ($termin->store()) {
                $this->course->createMessage(sprintf(
                    _('Der Kommtentar des gelöschten Termins %s wurde geändert.'),
                    htmlReady($termin->getFullName())
                ));
            } else {
                $this->course->createInfo(sprintf(
                    _('Der gelöschte Termin %s wurde nicht verändert.'),
                    htmlReady($termin->getFullName())
                ));
            }
        } else {
            $this->course->createInfo(sprintf(
                _('Der gelöschte Termin %s wurde nicht verändert.'),
                htmlReady($termin->getFullName())
            ));
        }
        if (Request::int('cancel_send_message')) {
            $snd_messages = raumzeit_send_cancel_message(Request::get('cancel_comment'), $termin);
            if ($snd_messages > 0) {
                $this->course->createInfo(_('Alle Teilnehmenden wurden benachrichtigt.'));
            }
        }
        $this->displayMessages();
        $this->redirect('course/timesrooms/index', ['contentbox_open' => $termin->metadate_id]);
    }

    /**
     * Creates the sidebar
     */
    private function setSidebar()
    {
        if (!$this->locked) {
            $actions = new ActionsWidget();
            $actions->addLink(
                sprintf(
                    _('Semester ändern (%s)'),
                    $this->course->getFullName('sem-duration-name')
                ),
                $this->url_for('course/timesrooms/editSemester'),
                Icon::create('date')
            )->asDialog('size=400');
            Sidebar::Get()->addWidget($actions);
        }

        $widget = new SelectWidget(_('Semesterfilter'), $this->url_for('course/timesrooms/index'), 'semester_filter');
        foreach ($this->selectable_semesters as $item) {
            $element = new SelectElement(
                $item['semester_id'],
                $item['name'],
                $item['semester_id'] == $this->semester_filter
            );
            $widget->addElement($element);
        }
        Sidebar::Get()->addWidget($widget);


        if ($GLOBALS['perm']->have_studip_perm('admin', $this->course->id)) {
            $widget = new CourseManagementSelectWidget();
            Sidebar::get()->addWidget($widget);
        }
    }

    /**
     * Calculates new end_offset value for given SeminarCycleDate Object
     *
     * @param object of SeminarCycleDate
     * @param array
     * @param array
     *
     * @return int
     */
    public function getNewEndOffset($cycle, $old_start_weeks, $new_start_weeks)
    {
        // if end_offset is null (endless lifespan) it should stay null
        if (is_null($cycle->end_offset)) {
            return null;
        }
        $old_offset_string = $old_start_weeks[$cycle->end_offset];
        $new_offset_value  = 0;

        foreach ($new_start_weeks as $value => $label) {
            if (mb_strpos($label, mb_substr($old_offset_string, -15)) !== false) {
                $new_offset_value = $value;
            }
        }
        if ($new_offset_value == 0) {
            return count($new_start_weeks) - 1;
        }

        return $new_offset_value;
    }

    /**
     * Displays messages.
     *
     * @param Array $messages Messages to display (optional, defaults to
     *                        potential stored messages on course object)
     */
    private function displayMessages(array $messages = [])
    {
        $messages = $messages ?: $this->course->getStackedMessages();
        foreach ((array)$messages as $type => $msg) {
            PageLayout::postMessage(MessageBox::$type($msg['title'], $msg['details']));
        }
    }

    /**
     * Deletes a date.
     *
     * @param CourseDate $termin CourseDate of the date
     * @param String $cancel_comment cancel mesessage (if non empty)
     *
     * @return CourseDate|CourseExDate deleted date
     */
    private function deleteDate($termin, $cancel_comment)
    {
        $seminar_id = $termin->range_id;
        $termin_room = $termin->getRoomName();
        $termin_date = $termin->getFullName();
        $has_topics  = $termin->topics->count();

        if ($cancel_comment != '') {
            $termin->content = $cancel_comment;
        }

        //cancel cycledate entry
        if ($termin->metadate_id || $cancel_comment != '') {
            $termin = $termin->cancelDate();
            StudipLog::log('SEM_DELETE_SINGLEDATE', $termin->id, $seminar_id, 'Cycle_id: ' . $termin->metadate_id);
        } else {
            if ($termin->delete()) {
                StudipLog::log("SEM_DELETE_SINGLEDATE", $termin->id, $seminar_id, 'appointment cancelled');
            }
        }

        if ($has_topics) {
            $this->course->createMessage(sprintf(
                _('Dem Termin %s war ein Thema zugeordnet. Sie können das Thema im %sAblaufplan%s einem anderen Termin (z.B. einem Ausweichtermin) zuordnen.'),
                htmlReady($termin_date),
                '<a href="' . URLHelper::getLink('dispatch.php/course/topics') . '">', '</a>'
            ));
        }
        if ($termin_room) {
            $this->course->createMessage(sprintf(
                _('Der Termin %s wurde gelöscht! Die Buchung für den Raum %s wurde gelöscht.'),
                htmlReady($termin_date),
                htmlReady($termin_room)
            ));
        } else {
            $this->course->createMessage(sprintf(
                _('Der Termin %s wurde gelöscht!'),
                htmlReady($termin_date)
            ));
        }

        return $termin;
    }


    /**
     * Redirects to another location.
     *
     * @param String $to New location
     */
    public function redirect($to)
    {
        $arguments = func_get_args();
        $url       = call_user_func_array('parent::url_for', $arguments);

        if (Request::isXhr()) {
            $index_url = $this->action_url('index');

            if (mb_strpos($url, $index_url) !== false) {
                $this->flash['update-times'] = $this->course->id;
            }
        }

        parent::redirect($url);
    }

    /**
     * Stores a request into trails' flash object
     */
    private function storeRequest()
    {
        $this->flash['request'] = Request::getInstance();
    }

    /**
     * Restores a previously stored request from trails' flash object
     */
    private function restoreRequest(array $fields, $request = null)
    {
        $request = $this->flash['request'] ?? $request;

        if ($request) {
            foreach ($fields as $field) {
                Request::set($field, $request[$field]);
            }
        }
    }

    /**
     * Relocates to another location if not from dialog
     *
     * @param String $to New location
     */
    public function relocate($to)
    {
        if (Request::int('fromDialog')) {
            $this->redirect(...func_get_args());
        } else {
            call_user_func_array('parent::relocate', func_get_args());
        }
    }


    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 ?? null, $end ?? null)) {
                        $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();
                    }
                }
            }
        }
    }
}