Skip to content
Snippets Groups Projects
timesrooms.php 68.4 KiB
Newer Older
<?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,
Michaela Brückner's avatar
Michaela Brückner committed
                '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;
Moritz Strohm's avatar
Moritz Strohm committed
                            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();

Michaela Brückner's avatar
Michaela Brückner committed
        $this->current_user = User::findCurrent();
        $this->user_has_permissions = ResourceManager::userHasGlobalPermission($this->current_user, 'admin');

        $check_room_requests = Config::get()->RESOURCES_ALLOW_ROOM_REQUESTS;
Michaela Brückner's avatar
Michaela Brückner committed
        $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;
Moritz Strohm's avatar
Moritz Strohm committed
        $this->checked_dates = [];
        if (!empty($_SESSION['_checked_dates'])) {
            $this->checked_dates = $_SESSION['_checked_dates'];
            unset($_SESSION['_checked_dates']);
        }
            $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) {
                } 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')));
            }
                $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "'.$this->course->getId().'"}');
        }
    }

    /**
     * Primary function to edit date-informations
     *
     */
    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;
David Siegfried's avatar
David Siegfried committed
            $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;
        }
Michaela Brückner's avatar
Michaela Brückner committed
            'course/room_requests/new_request',
            [
                'range' => 'date-multiple',
Michaela Brückner's avatar
Michaela Brückner committed
                '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();
            }
        }