diff --git a/app/controllers/admin/autoinsert.php b/app/controllers/admin/autoinsert.php index f89979555ad72b7f0b0745b238a4c64043d8286f..003586e2ad01a308f124f3c69aeea76c388507c2 100644 --- a/app/controllers/admin/autoinsert.php +++ b/app/controllers/admin/autoinsert.php @@ -159,7 +159,7 @@ class Admin_AutoinsertController extends AuthenticatedController } elseif (!count($filters)) { PageLayout::postError(_('Keine Filterkriterien gewählt')); } else { - $seminar = Seminar::GetInstance($seminar_id); + $course = Course::find($seminar_id); $userlookup = new UserLookup(); foreach ($filters as $type => $values) { @@ -170,8 +170,11 @@ class Admin_AutoinsertController extends AuthenticatedController foreach ($user_ids as $user_id) { if ($force || !AutoInsert::checkAutoInsertUser($seminar_id, $user_id)) { - $real_users += $seminar->addMember($user_id) ? 1 : 0; - AutoInsert::saveAutoInsertUser($seminar_id, $user_id); + $user = User::find($user_id); + if ($user) { + $real_users += $course->addMember($user) ? 1 : 0; + AutoInsert::saveAutoInsertUser($seminar_id, $user_id); + } } } @@ -182,9 +185,8 @@ class Admin_AutoinsertController extends AuthenticatedController count($user_ids), sprintf( '<a href="%s">%s</a>', - URLHelper::getLink('dispatch.php/course/details/', ['cid' => $seminar->getId()]), - htmlReady($seminar->getName() - ) + URLHelper::getLink('dispatch.php/course/details/', ['cid' => $course->id]), + htmlReady($course->name) ) ); $details = [_('Etwaige Abweichungen der Personenzahlen enstehen durch bereits vorhandene bzw. wieder ausgetragene Personen.')]; diff --git a/app/controllers/admin/banner.php b/app/controllers/admin/banner.php index 5199bbc26fe04e98d82fdad35ad6e0450eeecf40..abc88ba4cf455266e6bf0bc87009a5fe12431ac8 100644 --- a/app/controllers/admin/banner.php +++ b/app/controllers/admin/banner.php @@ -156,9 +156,7 @@ class Admin_BannerController extends AuthenticatedController } break; case 'seminar': - try { - Seminar::getInstance($target); - } catch (Exception $e) { + if (!Course::exists($target)) { $errors[] = _('Die angegebene Veranstaltung existiert nicht. ' .'Bitte geben Sie eine gültige Veranstaltungs-ID ein.'); } @@ -199,14 +197,14 @@ class Admin_BannerController extends AuthenticatedController ->defaultValue($banner->target,$seminar_name['name']) ->render(); } - + if ($banner->target_type == 'user') { $this->user = QuickSearch::get('user', new StandardSearch('username')) ->setInputStyle('width: 240px') ->defaultValue($banner->target, $banner->target) ->render(); } - + if ($banner->target_type == 'inst') { $institut_name = get_object_name($banner->target, 'inst'); $this->institut = QuickSearch::get('institut', new StandardSearch('Institut_id')) diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php index 5f16d21ef54d1f6c70d36878cf45e8c3aa6d06b8..562a14bcb17bbd648eae87b8caddae16b9b66b98 100644 --- a/app/controllers/admin/courses.php +++ b/app/controllers/admin/courses.php @@ -641,10 +641,8 @@ class Admin_CoursesController extends AuthenticatedController $d['type'] = $semtype['name']; } if (in_array('room_time', $activated_fields)) { - $seminar = new Seminar($course); - $d['room_time'] = $seminar->getDatesHTML([ - 'show_room' => true, - ]) ?: _('nicht angegeben'); + $strings = $course->getAllDatesInSemester()->toStringArray(); + $d['room_time'] = implode('<br>', $strings) ?: _('nicht angegeben'); } if (in_array('semester', $activated_fields)) { $d['semester'] = $course->semester_text; @@ -968,7 +966,6 @@ class Admin_CoursesController extends AuthenticatedController $data = []; foreach ($courses as $course) { - $sem = new Seminar($course); $row = []; if (in_array('number', $filter_config)) { @@ -988,11 +985,9 @@ class Admin_CoursesController extends AuthenticatedController } if (in_array('room_time', $filter_config)) { - $_room = $sem->getDatesExport([ - 'semester_id' => $this->semester->id, - 'show_room' => true - ]); - $row['room_time'] = $_room ?: _('nicht angegeben'); + $dates = $course->getAllDatesInSemester($this->semester); + $date_strings = $dates->toStringArray(true); + $row['room_time'] = implode("\n", $date_strings) ?: _('nicht angegeben'); } if (in_array('requests', $filter_config)) { diff --git a/app/controllers/admin/user.php b/app/controllers/admin/user.php index d1dcf1e27481fe958e74552e4071efb69b053b98..45802bc98e5f873067f358191151414bd3672452 100644 --- a/app/controllers/admin/user.php +++ b/app/controllers/admin/user.php @@ -1569,21 +1569,22 @@ class Admin_UserController extends AuthenticatedController { CSRFProtection::verifyUnsafeRequest(); + $course_ids = []; if (Request::get('course_id')) { - $courses = [Request::get('course_id')]; + $course_ids = [Request::option('course_id')]; } else { - $courses = Request::getArray('courses'); + $course_ids = Request::optionArray('courses'); } - if (empty($courses)) { + if (empty($course_ids)) { PageLayout::postError(_('Sie haben keine Veranstaltungen ausgewählt.')); } else { - $courses = array_map('Seminar::GetInstance', $courses); + $courses = Course::findMany($course_ids); $successes = 0; $fails = 0; foreach ($courses as $course) { - if ($course->deleteMember($user->id)) { + if ($course->deleteMember($user)) { $successes++; } else { $fails++; diff --git a/app/controllers/avatar.php b/app/controllers/avatar.php index aafbb986cd1d101cce21f94ea1935a2a386fdcd2..5111078df442358e768742f7bd10e5da9b52ef82 100644 --- a/app/controllers/avatar.php +++ b/app/controllers/avatar.php @@ -41,10 +41,9 @@ class AvatarController extends AuthenticatedController PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Veranstaltungsbild ändern')); $has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id); - $sem = Seminar::getInstance($id); - $studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode'); - if ($studygroup_mode) { - $class = StudygroupAvatar::class; + $course = Course::find($id); + if ($course->isStudygroup()) { + $class = 'StudygroupAvatar'; $this->cancel_link = $this->url_for('course/studygroup/edit?cid=' . $id); } else { $class = CourseAvatar::class; @@ -99,10 +98,9 @@ class AvatarController extends AuthenticatedController $redirect = 'institute/basicdata/index'; } else { $has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id); - $sem = Seminar::getInstance($id); - $studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode'); - if ($studygroup_mode) { - $class = StudygroupAvatar::class; + $course = Course::find($id); + if ($course->isStudygroup()) { + $class = 'StudygroupAvatar'; $redirect = 'course/studygroup/edit/?cid=' . $id; } else { $class = CourseAvatar::class; @@ -180,9 +178,8 @@ class AvatarController extends AuthenticatedController $redirect = 'institute/basicdata/index'; } else { $has_perm = $GLOBALS['perm']->have_studip_perm('tutor', $id); - $sem = Seminar::getInstance($id); - $studygroup_mode = $sem->getSemClass()->offsetget('studygroup_mode'); - if ($studygroup_mode) { + $course = Course::find($id); + if ($course->isStudygroup()) { $class = 'StudygroupAvatar'; $redirect = 'course/studygroup/edit/?cid=' . $id; } else { diff --git a/app/controllers/calendar/calendar.php b/app/controllers/calendar/calendar.php index 6923f8f8cd11d7af1be216ecec5b2d556272c126..dfc6706071636873b9c7c62a979a75ac379b2195 100644 --- a/app/controllers/calendar/calendar.php +++ b/app/controllers/calendar/calendar.php @@ -1,4 +1,17 @@ <?php +/* + * The controller for the personal calendar. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Peter Thienel <thienel@data-quest.de> + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since + */ class Calendar_CalendarController extends AuthenticatedController { diff --git a/app/controllers/calendar/schedule.php b/app/controllers/calendar/schedule.php index cf70dd3184cc6a79fd5f877f4641543a846d9656..668ed7739562146582c4d1f75c9c9ac37afc8579 100644 --- a/app/controllers/calendar/schedule.php +++ b/app/controllers/calendar/schedule.php @@ -292,18 +292,18 @@ class Calendar_ScheduleController extends AuthenticatedController * * @param string $start the start time of the group, e.g. "1000" * @param string $end the end time of the group, e.g. "1200" - * @param string $seminars the IDs of the courses + * @param string $course_ids the IDs of the courses * @param string $day numeric day to show * * @return void */ - public function groupedentry_action($start, $end, $seminars, $day) + public function groupedentry_action($start, $end, $course_ids, $day) { $this->response->add_header('Content-Type', 'text/html; charset=utf-8'); - $seminars = explode(',', $seminars); - foreach ($seminars as $seminar) { - $zw = explode('-', $seminar); - $this->seminars[$zw[0]] = Seminar::getInstance($zw[0]); + $course_ids = explode(',', $course_ids); + foreach ($course_ids as $course_id) { + $zw = explode('-', $course_id); + $this->courses[$zw[0]] = Course::find($zw[0]); } $this->timespan = mb_substr($start, 0, 2) . ':' . mb_substr($start, 2, 2) @@ -369,11 +369,11 @@ class Calendar_ScheduleController extends AuthenticatedController */ public function addvirtual_action($seminar_id) { - $sem = Seminar::getInstance($seminar_id); - foreach ($sem->getCycles() as $cycle) { + $regular_dates = SeminarCycleDate::findBySeminar($seminar_id); + foreach ($regular_dates as $cycle) { $data = [ 'id' => $seminar_id, - 'cycle_id' => $cycle->getMetaDateId(), + 'cycle_id' => $cycle->id, 'color' => false ]; diff --git a/app/controllers/course/admission.php b/app/controllers/course/admission.php index 14a9b200b831c2c4fdf27f4e0ccbf1010af7d87f..0b3755a1ea095078e9a9ab231f62e13cbfd475ed 100644 --- a/app/controllers/course/admission.php +++ b/app/controllers/course/admission.php @@ -138,14 +138,18 @@ class Course_AdmissionController extends AuthenticatedController if (Request::submittedSome('change_admission_prelim_no', 'change_admission_prelim_yes') || !$question) { if ($this->course->admission_prelim == 1 && $this->course->getNumParticipants() && Request::submitted('change_admission_prelim_yes')) { $num_moved = 0; - $seminar = new Seminar($this->course_id); - foreach ($this->course->members->findBy('status', ['user','autor'])->pluck('user_id') as $user_id) { - $seminar->addPreliminaryMember($user_id); - $num_moved += ($seminar->deleteMember($user_id) !== false); - setTempLanguage($user_id); + foreach ($this->course->members->findBy('status', ['user','autor'])->pluck('user') as $user) { + $this->course->addPreliminaryMember($user); + try { + $this->course->deleteMember($user); + } catch (\Studip\Exception $e) { + continue; + } + $num_moved++; + setTempLanguage($user->id); $message_body = sprintf(_('Sie wurden in der Veranstaltung **%s** in den Status **vorläufig akzeptiert** befördert, da das Anmeldeverfahren geändert wurde.'), $this->course->name); $message_title = sprintf(_("Statusänderung %s"), $this->course->name); - messaging::sendSystemMessage($user_id, $message_title, $message_body); + messaging::sendSystemMessage($user->id, $message_title, $message_body); restoreLanguage(); } if ($num_moved) { @@ -158,9 +162,13 @@ class Course_AdmissionController extends AuthenticatedController if ($this->course->admission_prelim == 0 && $this->course->getNumPrelimParticipants()) { if (Request::submitted('change_admission_prelim_yes')) { $num_moved = 0; - $seminar = new Seminar($this->course_id); - foreach ($this->course->admission_applicants->findBy('status', 'accepted')->pluck('user_id') as $user_id) { - $num_moved += ($seminar->addMember($user_id, 'autor') !== false); + foreach ($this->course->admission_applicants->findBy('status', 'accepted')->pluck('user') as $user) { + try { + $this->course->addMember($user, 'autor'); + } catch (\Studip\Exception $e) { + continue; + } + $num_moved++; setTempLanguage($user_id); $message_body = sprintf(_('Sie wurden in der Veranstaltung **%s** in den Status **Autor** versetzt, da das Anmeldeverfahren geändert wurde.'), $this->course->name); $message_title = sprintf(_("Statusänderung %s"), $this->course->name); diff --git a/app/controllers/course/archive.php b/app/controllers/course/archive.php index f181cca9e72dbf1330d594da9bfbfcbfddc3912b..1c6fc25e2333b1060f43261a4a39d0dd6d0212bd 100644 --- a/app/controllers/course/archive.php +++ b/app/controllers/course/archive.php @@ -165,9 +165,8 @@ class Course_ArchiveController extends AuthenticatedController $course = Course::find($courseId); if ($course) { - $seminar = new Seminar($course); - $coursename = $course->getFullName(); - if ($seminar->delete()) { + $coursename = $course->getFullname(); + if ($course->delete()) { $this->deletedCourses[] = $courseId; PageLayout::postSuccess(sprintf( _('Die Veranstaltung %s wurde erfolgreich gelöscht.'), diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php index 59130c4a358702fa697a13fd5d5de85347cf2fd7..eab5f3640bffc0ad9c5fa179b8ae3cbcceb5865e 100644 --- a/app/controllers/course/basicdata.php +++ b/app/controllers/course/basicdata.php @@ -21,17 +21,14 @@ class Course_BasicdataController extends AuthenticatedController /** * Set up the list of input fields. Some fields may be locked for - * some reasons (lock rules, insufficient permissions etc.). This + * some reason (lock rules, insufficient permissions etc.). This * method does not return anything, it just sets up $this->attributes * and $this->descriptions. * - * @param Seminar $sem + * @param Course $course */ - private function setupInputFields($sem) + protected function setupInputFields(Course $course) { - $course_id = $sem->getId(); - $data = $sem->getData(); - $this->attributes = []; $this->attributes[] = [ 'title' => _("Name der Veranstaltung"), @@ -39,16 +36,16 @@ class Course_BasicdataController extends AuthenticatedController 'must' => true, 'type' => 'text', 'i18n' => true, - 'value' => $data['name'], - 'locked' => LockRules::Check($course_id, 'Name') + 'value' => $course->name, + 'locked' => LockRules::Check($course->id, 'Name') ]; $this->attributes[] = [ - 'title' => _("Untertitel der Veranstaltung"), - 'name' => "course_subtitle", + 'title' => _('Untertitel der Veranstaltung'), + 'name' => 'course_untertitel', 'type' => 'text', 'i18n' => true, - 'value' => $data['subtitle'], - 'locked' => LockRules::Check($course_id, 'Untertitel') + 'value' => $course->untertitel, + 'locked' => LockRules::Check($course->id, 'Untertitel') ]; $changable = true; $this->attributes[] = [ @@ -56,60 +53,60 @@ class Course_BasicdataController extends AuthenticatedController 'name' => 'course_status', 'must' => true, 'type' => 'select', - 'value' => $data['status'], - 'locked' => LockRules::Check($course_id, 'status'), - 'choices' => $this->_getTypes($sem, $data, $changable), + 'value' => $course->status, + 'locked' => LockRules::Check($course->id, 'status'), + 'choices' => $this->_getTypes($course, $changable), 'changable' => $changable, ]; $this->attributes[] = [ 'title' => _("Art der Veranstaltung"), - 'name' => "course_form", + 'name' => 'course_art', 'type' => 'text', 'i18n' => true, - 'value' => $data['form'], - 'locked' => LockRules::Check($course_id, 'art') + 'value' => $course->art, + 'locked' => LockRules::Check($course->id, 'art') ]; $course_number_format_config = Config::get()->getMetadata('COURSE_NUMBER_FORMAT'); $this->attributes[] = [ - 'title' => _("Veranstaltungsnummer"), - 'name' => "course_seminar_number", + 'title' => _('Veranstaltungsnummer'), + 'name' => 'course_veranstaltungsnummer', 'type' => 'text', - 'value' => $data['seminar_number'], - 'locked' => LockRules::Check($course_id, 'VeranstaltungsNummer'), + 'value' => $course->veranstaltungsnummer, + 'locked' => LockRules::Check($course->id, 'VeranstaltungsNummer'), 'description' => $course_number_format_config['comment'], 'pattern' => Config::get()->COURSE_NUMBER_FORMAT ]; $this->attributes[] = [ - 'title' => _("ECTS-Punkte"), - 'name' => "course_ects", + 'title' => _('ECTS-Punkte'), + 'name' => 'course_ects', 'type' => 'text', - 'value' => $data['ects'], - 'locked' => LockRules::Check($course_id, 'ects') + 'value' => $course->ects, + 'locked' => LockRules::Check($course->id, 'ects') ]; $this->attributes[] = [ - 'title' => _("max. Teilnehmendenzahl"), - 'name' => "course_admission_turnout", + 'title' => _('max. Teilnehmendenzahl'), + 'name' => 'course_admission_turnout', 'must' => false, 'type' => 'number', - 'value' => $data['admission_turnout'], - 'locked' => LockRules::Check($course_id, 'admission_turnout'), + 'value' => $course->admission_turnout, + 'locked' => LockRules::Check($course->id, 'admission_turnout'), 'min' => '0' ]; $this->attributes[] = [ - 'title' => _("Beschreibung"), - 'name' => "course_description", + 'title' => _('Beschreibung'), + 'name' => 'course_beschreibung', 'type' => 'textarea', 'i18n' => true, - 'value' => $data['description'], - 'locked' => LockRules::Check($course_id, 'Beschreibung') + 'value' => $course->beschreibung, + 'locked' => LockRules::Check($course->id, 'Beschreibung') ]; $this->institutional = []; $my_institutes = Institute::getMyInstitutes(); $institutes = Institute::getInstitutes(); foreach ($institutes as $institute) { - if ($institute['Institut_id'] === $data['institut_id']) { + if ($institute['Institut_id'] === $course->institut_id) { $found = false; foreach ($my_institutes as $inst) { if ($inst['Institut_id'] === $institute['Institut_id']) { @@ -128,54 +125,54 @@ class Course_BasicdataController extends AuthenticatedController 'name' => 'course_institut_id', 'must' => true, 'type' => 'nested-select', - 'value' => $data['institut_id'], + 'value' => $course->institut_id, 'choices' => $this->instituteChoices($my_institutes), - 'locked' => LockRules::Check($course_id, 'Institut_id') + 'locked' => LockRules::Check($course->id, 'Institut_id') ]; - $sem_institutes = $sem->getInstitutes(); + $institute_ids = $course->institutes->pluck('id'); $this->institutional[] = [ 'title' => _('beteiligte Einrichtungen'), 'name' => 'related_institutes[]', 'type' => 'nested-select', - 'value' => array_diff($sem_institutes, [$sem->institut_id]), + 'value' => $institute_ids, 'choices' => $this->instituteChoices($institutes), - 'locked' => LockRules::Check($course_id, 'seminar_inst'), + 'locked' => LockRules::Check($course->id, 'seminar_inst'), 'multiple' => true, ]; $this->descriptions = []; $this->descriptions[] = [ - 'title' => _("Teilnehmende"), - 'name' => "course_participants", + 'title' => _('Teilnehmende'), + 'name' => 'course_teilnehmer', 'type' => 'textarea', 'i18n' => true, - 'value' => $data['participants'], - 'locked' => LockRules::Check($course_id, 'teilnehmer') + 'value' => $course->teilnehmer, + 'locked' => LockRules::Check($course->id, 'teilnehmer') ]; $this->descriptions[] = [ - 'title' => _("Voraussetzungen"), - 'name' => "course_requirements", + 'title' => _('Voraussetzungen'), + 'name' => 'course_vorrausetzungen', 'type' => 'textarea', 'i18n' => true, - 'value' => $data['vorrausetzungen'], - 'locked' => LockRules::Check($course_id, 'voraussetzungen') + 'value' => $course->vorrausetzungen, + 'locked' => LockRules::Check($course->id, 'voraussetzungen') ]; $this->descriptions[] = [ - 'title' => _("Lernorganisation"), - 'name' => "course_orga", + 'title' => _('Lernorganisation'), + 'name' => 'course_lernorga', 'type' => 'textarea', 'i18n' => true, - 'value' => $data['orga'], - 'locked' => LockRules::Check($course_id, 'lernorga') + 'value' => $course->lernorga, + 'locked' => LockRules::Check($course->id, 'lernorga') ]; $this->descriptions[] = [ - 'title' => _("Leistungsnachweis"), - 'name' => "course_leistungsnachweis", + 'title' => _('Leistungsnachweis'), + 'name' => 'course_leistungsnachweis', 'type' => 'textarea', 'i18n' => true, - 'value' => $data['leistungsnachweis'], - 'locked' => LockRules::Check($course_id, 'leistungsnachweis') + 'value' => $course->leistungsnachweis, + 'locked' => LockRules::Check($course->id, 'leistungsnachweis') ]; $this->descriptions[] = [ 'title' => _("Ort") . @@ -186,18 +183,18 @@ class Course_BasicdataController extends AuthenticatedController "Angaben aus Zeiten oder Sitzungsterminen gemacht werden können.") . "</span>", 'i18n' => true, - 'name' => "course_location", + 'name' => 'course_ort', 'type' => 'textarea', - 'value' => $data['ort'], - 'locked' => LockRules::Check($course_id, 'Ort') + 'value' => $course->ort, + 'locked' => LockRules::Check($course->id, 'Ort') ]; - $datenfelder = DataFieldEntry::getDataFieldEntries($course_id, 'sem', $data["status"]); + $datenfelder = DataFieldEntry::getDataFieldEntries($course->id, 'sem', $course->status); if ($datenfelder) { foreach($datenfelder as $datenfeld) { if ($datenfeld->isVisible()) { $locked = !$datenfeld->isEditable() - || LockRules::Check($course_id, $datenfeld->getID()); + || LockRules::Check($course->id, $datenfeld->getID()); $desc = $locked ? _('Diese Felder werden zentral durch die zuständigen Administratoren erfasst.') : $datenfeld->getDescription(); $this->descriptions[] = [ 'title' => $datenfeld->getName(), @@ -215,11 +212,11 @@ class Course_BasicdataController extends AuthenticatedController } } $this->descriptions[] = [ - 'title' => _("Sonstiges"), - 'name' => "course_misc", + 'title' => _('Sonstiges'), + 'name' => 'course_sonstiges', 'type' => 'textarea', - 'value' => $data['misc'], - 'locked' => LockRules::Check($course_id, 'Sonstiges') + 'value' => $course->sonstiges, + 'locked' => LockRules::Check($course->id, 'Sonstiges') ]; } @@ -291,22 +288,21 @@ class Course_BasicdataController extends AuthenticatedController //Daten sammeln: $course = Course::find($this->course_id); - $sem = new Seminar($course); - $data = $sem->getData(); + $data = $course->toRawArray(); //Erster, zweiter und vierter Reiter des Akkordions: Grundeinstellungen - $this->setupInputFields($sem); + $this->setupInputFields($course); - $sem_institutes = $sem->getInstitutes(); + $sem_institutes = $course->institutes->pluck('id'); $this->dozent_is_locked = LockRules::Check($this->course_id, 'dozent'); $this->tutor_is_locked = LockRules::Check($this->course_id, 'tutor'); //Dritter Reiter: Personal - $this->dozenten = $sem->getMembers('dozent'); - $instUsers = new SimpleCollection(InstituteMember::findByInstituteAndStatus($sem->getInstitutId(), 'dozent')); + $this->dozenten = $course->getMembersWithStatus('dozent'); + $instUsers = new SimpleCollection(InstituteMember::findByInstituteAndStatus($course->institut_id, 'dozent')); $this->lecturersOfInstitute = $instUsers->pluck('user_id'); - if (SeminarCategories::getByTypeId($sem->status)->only_inst_user) { + if (SeminarCategories::getByTypeId($course->status)->only_inst_user) { $search_template = "user_inst_not_already_in_sem"; } else { $search_template = "user_not_already_in_sem"; @@ -314,7 +310,7 @@ class Course_BasicdataController extends AuthenticatedController $this->dozentUserSearch = new PermissionSearch( $search_template, - sprintf(_("%s suchen"), get_title_for_status('dozent', 1, $sem->status)), + sprintf(_('%s suchen'), get_title_for_status('dozent', 1, $course->status)), "user_id", [ 'permission' => 'dozent', @@ -323,25 +319,25 @@ class Course_BasicdataController extends AuthenticatedController 'institute' => $sem_institutes ] ); - $this->dozenten_title = get_title_for_status('dozent', 1, $sem->status); + $this->dozenten_title = get_title_for_status('dozent', 1, $course->status); $this->deputies_enabled = $deputies_enabled; if ($this->deputies_enabled) { $this->deputies = Deputy::findDeputies($this->course_id); $this->deputySearch = new PermissionSearch( "user_not_already_in_sem_or_deputy", - sprintf(_("%s suchen"), get_title_for_status('deputy', 1, $sem->status)), + sprintf(_("%s suchen"), get_title_for_status('deputy', 1, $course->status)), "user_id", ['permission' => Deputy::getValidPerms(), 'seminar_id' => $this->course_id] ); - $this->deputy_title = get_title_for_status('deputy', 1, $sem->status); + $this->deputy_title = get_title_for_status('deputy', 1, $course->status); } - $this->tutoren = $sem->getMembers('tutor'); + $this->tutoren = $course->getMembersWithStatus('tutor'); $this->tutorUserSearch = new PermissionSearch( $search_template, - sprintf(_("%s suchen"), get_title_for_status('tutor', 1, $sem->status)), + sprintf(_('%s suchen'), get_title_for_status('tutor', 1, $course->status)), "user_id", ['permission' => ['dozent','tutor'], 'seminar_id' => $this->course_id, @@ -349,8 +345,8 @@ class Course_BasicdataController extends AuthenticatedController 'institute' => $sem_institutes ] ); - $this->tutor_title = get_title_for_status('tutor', 1, $sem->status); - $instUsers = new SimpleCollection(InstituteMember::findByInstituteAndStatus($sem->getInstitutId(), 'tutor')); + $this->tutor_title = get_title_for_status('tutor', 1, $course->status); + $instUsers = new SimpleCollection(InstituteMember::findByInstituteAndStatus($course->institut_id, 'tutor')); $this->tutorsOfInstitute = $instUsers->pluck('user_id'); unset($instUsers); @@ -464,15 +460,19 @@ class Course_BasicdataController extends AuthenticatedController CSRFProtection::verifyUnsafeRequest(); $course_number_format = Config::get()->COURSE_NUMBER_FORMAT; - $sem = Seminar::getInstance($course_id); - $this->msg = []; - $old_settings = $sem->getSettings(); + $course = Course::find($course_id); + $this->msg = [ + 'success' => '', + 'errors' => [] + ]; + $old_settings = $course->toRawArray(); + unset($old_settings['config']); //Seminar-Daten: - if ($perm->have_studip_perm("tutor", $sem->getId())) { - $this->setupInputFields($sem); + if ($perm->have_studip_perm('tutor', $course_id)) { + $this->setupInputFields($course); $changemade = false; $invalid_datafields = []; - $all_fields_types = DataFieldEntry::getDataFieldEntries($sem->id, 'sem', $sem->status); + $all_fields_types = DataFieldEntry::getDataFieldEntries($course->id, 'sem', $course->status); $datafield_values = Request::getArray('datafields'); foreach (array_merge($this->attributes, $this->institutional, $this->descriptions) as $field) { @@ -490,8 +490,17 @@ class Course_BasicdataController extends AuthenticatedController } } else if ($field['name'] == 'related_institutes[]') { // only related_institutes supported for now - if ($sem->setInstitutes(Request::optionArray('related_institutes'))) { - $changemade = true; + $related_institute_ids = Request::optionArray('related_institutes'); + if (is_array($related_institute_ids)) { + $institutes = Institute::findMany($related_institute_ids); + if ($institutes) { + $course->institutes = $institutes; + $changemade = $course->store(); + } else { + $this->msg['error'][] = _('Es muss mindestens ein Institut angegeben werden.'); + } + } else { + $this->msg['error'][] = _('Es muss mindestens ein Institut angegeben werden.'); } } else { // format of input element name is "course_xxx" @@ -503,14 +512,14 @@ class Course_BasicdataController extends AuthenticatedController } if ($varname === "name" && !$req_value) { - $this->msg[] = ["error", _("Name der Veranstaltung darf nicht leer sein.")]; + $this->msg['error'][] = _('Name der Veranstaltung darf nicht leer sein.'); } elseif ($varname === "seminar_number" && $req_value && $course_number_format && !preg_match('/^' . $course_number_format . '$/', $req_value)) { - $this->msg[] = ['error', _('Die Veranstaltungsnummer hat ein ungültiges Format.')]; + $this->msg['error'][] = _('Die Veranstaltungsnummer hat ein ungültiges Format.'); } else if ($field['type'] == 'select' && !in_array($req_value, array_flatten(array_values(array_map('array_keys', $field['choices']))))) { // illegal value - just ignore this - } else if ($sem->{$varname} != $req_value) { - $sem->{$varname} = $req_value; + } else if ($course->getValue($varname) != $req_value) { + $course->setValue($varname, $req_value); $changemade = true; } } @@ -524,23 +533,25 @@ class Course_BasicdataController extends AuthenticatedController count($invalid_datafields) ); $message = sprintf($message, join(', ', array_map('htmlReady', $invalid_datafields))); - $this->msg[] = ['error', $message]; + $this->msg['error'][] = $message; } - $sem->store(); + $course->store(); // Logging - $before = array_diff_assoc($old_settings, $sem->getSettings()); - $after = array_diff_assoc($sem->getSettings(), $old_settings); + $current_settings = $course->toRawArray(); + unset($current_settings['config']); + $before = array_diff_assoc($old_settings, $current_settings); + $after = array_diff_assoc($current_settings, $old_settings); //update admission, if turnout was raised if ( !empty($after['admission_turnout']) && !empty($before['admission_turnout']) && $after['admission_turnout'] > $before['admission_turnout'] - && $sem->isAdmissionEnabled() + && $course->isAdmissionEnabled() ) { - AdmissionApplication::addMembers($sem->getId()); + AdmissionApplication::addMembers($course_id); } if (sizeof($before) && sizeof($after)) { @@ -548,30 +559,30 @@ class Course_BasicdataController extends AuthenticatedController foreach ($before as $k => $v) { $log_message .= "$k: $v => " . $after[$k] . " \n"; } - StudipLog::log('CHANGE_BASIC_DATA', $sem->getId(), " ", $log_message); - NotificationCenter::postNotification('SeminarBasicDataDidUpdate', $sem->id , $GLOBALS['user']->id); + StudipLog::log('CHANGE_BASIC_DATA', $course_id, ' ', $log_message); + NotificationCenter::postNotification('SeminarBasicDataDidUpdate', $course->id , $GLOBALS['user']->id); } // end of logging if ($changemade) { - $this->msg[] = ["msg", _("Die Grunddaten der Veranstaltung wurden verändert.")]; + $this->msg['success'] = _('Die Grunddaten der Veranstaltung wurden verändert.'); } } else { - $this->msg[] = ["error", _("Sie haben keine Berechtigung diese Veranstaltung zu verändern.")]; + $this->msg['error'][] = _('Sie haben keine Berechtigung diese Veranstaltung zu verändern.'); } //Labels/Funktionen für Dozenten und Tutoren - if ($perm->have_studip_perm('dozent', $sem->getId())) { + if ($perm->have_studip_perm('dozent', $course_id)) { foreach (Request::getArray('label') as $user_id => $label) { - if ($GLOBALS['perm']->have_studip_perm('tutor', $sem->getId(), $user_id)) { - $mb = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $sem->getId()]); + if ($GLOBALS['perm']->have_studip_perm('tutor', $course_id, $user_id)) { + $mb = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $course_id]); if ($mb) { $mb->label = $label; if ($mb->store()) { NotificationCenter::postNotification( 'CourseDidChangeMemberLabel', - $sem, + $course, $mb ); } @@ -580,21 +591,23 @@ class Course_BasicdataController extends AuthenticatedController } } - foreach($sem->getStackedMessages() as $key => $messages) { - foreach($messages['details'] as $message) { - $this->msg[] = [($key !== "success" ? $key : "msg"), $message]; - } + if (!empty($this->msg['error'])) { + PageLayout::postError( + _('Die folgenden Fehler traten auf:'), + $this->msg['error'] + ); + } elseif ($this->msg['success']) { + PageLayout::postSuccess($this->msg['success']); } - $this->flash['msg'] = $this->msg; + $this->flash['open'] = Request::get("open"); if (Request::isDialog()) { $this->response->add_header('X-Dialog-Close', 1); $this->response->add_header('X-Dialog-Execute', 'STUDIP.AdminCourses.App.loadCourse'); $this->render_text($course_id); } else { - $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId())); + $this->redirect($this->url_for('course/basicdata/view/' . $course_id)); } - } public function add_member_action($course_id, $status = 'dozent') @@ -627,8 +640,8 @@ class Course_BasicdataController extends AuthenticatedController } // Only show the success messagebox once if ($succeeded) { - $sem = Seminar::GetInstance($course_id); - $status_title = get_title_for_status($status, count($succeeded), $sem->status); + $course = Course::find($course_id); + $status_title = get_title_for_status($status, count($succeeded), $course->status); if (count($succeeded) > 1) { $messagetext = sprintf( _("%u %s wurden hinzugefügt."), @@ -661,72 +674,28 @@ class Course_BasicdataController extends AuthenticatedController $this->redirect($this->url_for($redirect)); } - private function addTutor($tutor, $course_id) - { - //Tutoren hinzufügen: - if ($GLOBALS['perm']->have_studip_perm('tutor', $course_id)) { - $sem = Seminar::GetInstance($course_id); - if ($sem->addMember($tutor, 'tutor')) { - // Check if we need to add user to course parent as well. - if ($sem->parent_course) { - $this->addTutor($tutor, $sem->parent_course); - } - - return true; - } - } - return false; - } - - private function addDeputy($user_id, $course_id) - { - //Vertretung hinzufügen: - if ($GLOBALS['perm']->have_studip_perm('dozent', $course_id)) { - $sem = Seminar::GetInstance($course_id); - if (Deputy::addDeputy($user_id, $sem->getId())) { - return true; - } - } - return false; - } - - private function addTeacher($dozent, $course_id) + /** + * A helper method since the steps for removing someone are all the same in this controller. + * Only the actions differ. + * + * @param Course $course The course from which to remove a user. + * @param User $user The user to be removed. + * @return void + */ + protected function deleteUserFromCourse(Course $course, User $user) { - $deputies_enabled = Config::get()->DEPUTIES_ENABLE; - $sem = Seminar::GetInstance($course_id); - if ($GLOBALS['perm']->have_studip_perm('dozent', $course_id)) { - if ($sem->addMember($dozent, 'dozent')) { - // Check if we need to add user to course parent as well. - if ($sem->parent_course) { - $this->addTeacher($dozent, $sem->parent_course); - } - - // Only applicable when globally enabled and user deputies enabled too - if ($deputies_enabled) { - // Check whether chosen person is set as deputy - // -> delete deputy entry. - $deputy = Deputy::find([$course_id, $dozent]); - if ($deputy) { - $deputy->delete(); - } - - // Add default deputies of the chosen lecturer... - if (Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) { - $deputies = Deputy::findDeputies($dozent)->pluck('user_id'); - $lecturers = $sem->getMembers(); - foreach ($deputies as $deputy) { - // ..but only if not already set as lecturer or deputy. - if (!isset($lecturers[$deputy])) { - Deputy::addDeputy($deputy, $course_id); - } - } - } - } - - return true; - } - } - return false; + try { + $course->deleteMember($user); + } catch (\Studip\Exception $e) { + PageLayout::postError(_('Ein Fehler ist aufgetreten.'), $e->getMessage()); + return; + } + PageLayout::postSuccess( + studip_interpolate( + _('%{name} wurde aus der Veranstaltung ausgetragen.'), + ['name' => $user->getFullName()] + ) + ); } /** @@ -745,24 +714,10 @@ class Course_BasicdataController extends AuthenticatedController } elseif ($teacher_id === $GLOBALS['user']->id) { PageLayout::postError(_('Sie dürfen sich nicht selbst aus der Veranstaltung austragen.')); } else { - $sem = Seminar::getInstance($course_id); - $sem->deleteMember($teacher_id); - - // Remove user from subcourses as well. - foreach ($sem->children as $child) { - $child->deleteMember($teacher_id); - } - - $this->msg = []; - foreach ($sem->getStackedMessages() as $key => $messages) { - foreach ($messages['details'] as $message) { - $this->msg[] = [ - $key !== 'success' ? $key : 'msg', - $message - ]; - } - } - $this->flash['msg'] = $this->msg; + $this->deleteUserFromCourse( + Course::find($course_id), + User::find($teacher_id) + ); } $this->flash['open'] = 'bd_personal'; @@ -785,23 +740,23 @@ class Course_BasicdataController extends AuthenticatedController } elseif ($deputy_id === $GLOBALS['user']->id) { PageLayout::postError(_('Sie dürfen sich nicht selbst aus der Veranstaltung austragen.')); } else { - $sem = Seminar::getInstance($course_id); + $course = Course::find($course_id); $deputy = Deputy::find([$course_id, $deputy_id]); if ($deputy && $deputy->delete()) { // Remove user from subcourses as well. - if (count($sem->children)) { - $children_ids = $sem->children->pluck('seminar_id'); + if (count($course->children) > 0) { + $children_ids = $course->children->pluck('seminar_id'); Deputy::deleteBySQL('user_id = ? AND range_id IN (?)', [$deputy_id, $children_ids]); } PageLayout::postSuccess(sprintf( _('%s wurde entfernt.'), - htmlReady(get_title_for_status('deputy', 1, $sem->status)) + htmlReady(get_title_for_status('deputy', 1, $course->status)) )); } else { PageLayout::postError(sprintf( _('%s konnte nicht entfernt werden.'), - htmlReady(get_title_for_status('deputy', 1, $sem->status)) + htmlReady(get_title_for_status('deputy', 1, $course->status)) )); } } @@ -824,24 +779,10 @@ class Course_BasicdataController extends AuthenticatedController if (!$GLOBALS['perm']->have_studip_perm('dozent', $course_id)) { PageLayout::postError( _('Sie haben keine Berechtigung diese Veranstaltung zu verändern.')); } else { - $sem = Seminar::getInstance($course_id); - - $sem->deleteMember($tutor_id); - // Remove user from subcourses as well. - foreach ($sem->children as $child) { - $child->deleteMember($tutor_id); - } - - $this->msg = []; - foreach ($sem->getStackedMessages() as $key => $messages) { - foreach ($messages['details'] as $message) { - $this->msg[] = [ - $key !== 'success' ? $key : 'msg', - $message - ]; - } - } - $this->flash['msg'] = $this->msg; + $this->deleteUserFromCourse( + Course::find($course_id), + User::find($teacher_id) + ); } $this->flash['open'] = 'bd_personal'; @@ -849,77 +790,63 @@ class Course_BasicdataController extends AuthenticatedController } /** - * Falls eine Person in der >>Reihenfolge<< hochgestuft werden soll. - * Leitet danach weiter auf View und öffnet den Reiter Personal. + * Moves a course member up one position in the position list for the + * corresponding permission level in the course. + * + * @param string $course_id The course where to increase the priority. + * + * @param string $user_id The user for whom to increase the priority. * - * @param md5 $user_id - * @param string $status + * @param string $status The permission level. This is an unused parameter that is only kept + * for compatibility reasons. */ - public function priorityupfor_action($course_id, $user_id, $status = "dozent") + public function priorityupfor_action(string $course_id, string $user_id, string $status = 'dozent') { - global $perm; - CSRFProtection::verifyUnsafeRequest(); - $sem = Seminar::getInstance($course_id); + $course = Course::find($course_id); + $user = User::find($user_id); $this->msg = []; - if ($perm->have_studip_perm("dozent", $sem->getId())) { - $teilnehmer = $sem->getMembers($status); - $members = []; - foreach($teilnehmer as $key => $member) { - $members[] = $member["user_id"]; - } - foreach($members as $key => $member) { - if ($key > 0 && $member == $user_id) { - $temp_member = $members[$key-1]; - $members[$key-1] = $member; - $members[$key] = $temp_member; - } + if ($GLOBALS['perm']->have_studip_perm('dozent', $course->id)) { + if ($course->moveMemberUp($user) < 0) { + $this->msg[] = ['error', _('Die Person konnte nicht nach oben verschoben werden.')]; } - $sem->setMemberPriority($members); } else { $this->msg[] = ["error", _("Sie haben keine Berechtigung diese Veranstaltung zu verändern.")]; } $this->flash['msg'] = $this->msg; $this->flash['open'] = "bd_personal"; - $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId())); + $this->redirect($this->url_for('course/basicdata/view/' . $course->id)); } /** - * Falls eine Person in der >>Reihenfolge<< runtergestuft werden soll. - * Leitet danach weiter auf View und öffnet den Reiter Personal. + * Moves a course member down one position in the position list for the + * corresponding permission level in the course. + * + * @param string $course_id The course where to decrease the priority. + * + * @param string $user_id The user for whom to decrease the priority. * - * @param md5 $user_id - * @param string $status + * @param string $status The permission level. This is an unused parameter that is only kept + * for compatibility reasons. */ - public function prioritydownfor_action($course_id, $user_id, $status = "dozent") + public function prioritydownfor_action($course_id, $user_id, $status = 'dozent') { - global $perm; - CSRFProtection::verifyUnsafeRequest(); - $sem = Seminar::getInstance($course_id); + $course = Course::find($course_id); + $user = User::find($user_id); $this->msg = []; - if ($perm->have_studip_perm("dozent", $sem->getId())) { - $teilnehmer = $sem->getMembers($status); - $members = []; - foreach($teilnehmer as $key => $member) { - $members[] = $member["user_id"]; + if ($GLOBALS['perm']->have_studip_perm('dozent', $course->id)) { + if ($course->moveMemberDown($user) < 0) { + $this->msg[] = ['error', _('Die Person konnte nicht nach unten verschoben werden.')]; } - foreach($members as $key => $member) { - if ($key < count($members)-1 && $member == $user_id) { - $temp_member = $members[$key+1]; - $members[$key+1] = $member; - $members[$key] = $temp_member; - } - } - $sem->setMemberPriority($members); } else { - $this->msg[] = ["error", _("Sie haben keine Berechtigung diese Veranstaltung zu verändern.")]; + $this->msg[] = ['error', _('Sie haben keine Berechtigung diese Veranstaltung zu verändern.')]; } $this->flash['msg'] = $this->msg; $this->flash['open'] = "bd_personal"; - $this->redirect($this->url_for('course/basicdata/view/' . $sem->getId())); + $this->redirect($this->url_for('course/basicdata/view/' . $course->id)); } public function switchdeputy_action($course_id, $newstatus) @@ -959,7 +886,7 @@ class Course_BasicdataController extends AuthenticatedController $this->redirect($this->url_for('course/basicdata/view/'.$course_id)); } - private function _getTypes($sem, $data, &$changable = true) + private function _getTypes(Course $course, &$changable = true) { $sem_types = []; @@ -971,10 +898,10 @@ class Course_BasicdataController extends AuthenticatedController } } } else { - $sem_classes[] = $sem->getSemClass(); + $sem_classes[] = $course->getSemClass(); } - if (!$sem->isStudyGroup()) { + if (!$course->isStudyGroup()) { $sem_classes = array_filter($sem_classes, function (SemClass $sc) { return !$sc['studygroup_mode']; }); @@ -985,13 +912,12 @@ class Course_BasicdataController extends AuthenticatedController return $st['name']; }, $sc->getSemTypes()); } - - if (!in_array($data['status'], array_flatten(array_values(array_map('array_keys', $sem_types))))) { - $class_name = $sem->getSemClass()->offsetGet('name'); + if (!in_array($course->status, array_flatten(array_values(array_map('array_keys', $sem_types))))) { + $class_name = $course->getSemClass()->offsetGet('name'); if (!isset($sem_types[$class_name])) { $sem_types[$class_name] = []; } - $sem_types[$class_name][] = $sem->getSemType()->offsetGet('name'); + $sem_types[$class_name][] = $course->getSemType()->offsetGet('name'); $changable = false; } diff --git a/app/controllers/course/block_appointments.php b/app/controllers/course/block_appointments.php index ad28b1e140b10a6621e6d654d4ade710e9646e06..1628e94b53fb4d375b56cacafa1f04ab91f9731c 100644 --- a/app/controllers/course/block_appointments.php +++ b/app/controllers/course/block_appointments.php @@ -242,8 +242,8 @@ class Course_BlockAppointmentsController extends AuthenticatedController $result = $d->store(); } else { $result = $d->store(); - $singledate = new SingleDate($d); - $singledate->bookRoom(Request::option('room_id')); + $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance(); + $d->bookRoom($room); } return $result ? $d->getFullName() : null; }, $dates)); diff --git a/app/controllers/course/cancel_dates.php b/app/controllers/course/cancel_dates.php index 8da0d09818f87cddc7d2514dd7cf8c5827097a70..692c1709d16a24712cb2058323e40c7cae74d861 100644 --- a/app/controllers/course/cancel_dates.php +++ b/app/controllers/course/cancel_dates.php @@ -25,17 +25,17 @@ class Course_CancelDatesController extends AuthenticatedController parent::before_filter($action, $args); if (Request::get('termin_id')) { - $this->dates[0] = new SingleDate(Request::option('termin_id')); + $this->dates[0] = CourseDate::find(Request::option('termin_id')); $this->course_id = $this->dates[0]->range_id; } if (Request::get('issue_id')) { $this->issue_id = Request::option('issue_id'); - $this->dates = array_values(array_map(function ($data) { - $d = new SingleDate(); - $d->fillValuesFromArray($data); - return $d; - }, IssueDB::getDatesforIssue(Request::option('issue_id')))); + $this->dates = CourseDate::findBySQL( + "JOIN `themen_termine` USING (`termin_id`) + WHERE `issue_id` = :issue_id ORDER BY `date`", + ['issue_id' => Request::option('issue_id')] + ); $this->course_id = $this->dates[0]->range_id; } if (!get_object_type($this->course_id, ['sem']) || !$perm->have_studip_perm("tutor", $this->course_id)) { @@ -52,13 +52,13 @@ class Course_CancelDatesController extends AuthenticatedController public function store_action() { CSRFProtection::verifyUnsafeRequest(); - $sem = Seminar::getInstance($this->course_id); $msg = ''; foreach ($this->dates as $date) { - $sem->cancelSingleDate($date->getTerminId(), $date->getMetadateId()); - $date->setComment(Request::get('cancel_dates_comment')); - $date->setExTermin(true); - $date->store(); + $ex_date = $date->cancelDate(); + if ($ex_date) { + $ex_date->content = Request::get('cancel_dates_comment'); + $ex_date->store(); + } } if (Request::int('cancel_dates_snd_message') && count($this->dates) > 0) { $snd_messages = raumzeit_send_cancel_message(Request::get('cancel_dates_comment'), $this->dates); @@ -67,7 +67,7 @@ class Course_CancelDatesController extends AuthenticatedController } } PageLayout::postSuccess(_('Folgende Termine wurden abgesagt') . ($msg ? ' (' . $msg . '):' : ':'), array_map(function ($d) { - return $d->toString(); + return $d->getFullName(); }, $this->dates)); $this->redirect($this->url_for('course/dates')); diff --git a/app/controllers/course/dates.php b/app/controllers/course/dates.php index f64d66bab8390a3862324e3f6166dc811330e462..bd889327489d626beb9fe1aa30bba544b54efe6f 100644 --- a/app/controllers/course/dates.php +++ b/app/controllers/course/dates.php @@ -1,5 +1,4 @@ <?php -require_once 'lib/raumzeit/raumzeit_functions.inc.php'; class Course_DatesController extends AuthenticatedController { @@ -79,10 +78,10 @@ class Course_DatesController extends AuthenticatedController Icon::create('add') )->asDialog(); } - if ( - Seminar::setInstance(new Seminar(Course::findCurrent()))->getSlotModule('documents') - && CourseDateFolder::availableInRange(Course::findCurrent(), User::findCurrent() ? User::findCurrent()->id : null) + Course::exists(Context::getId()) + && $this->course->isToolActive(CoreDocuments::class) + && CourseDateFolder::availableInRange($this->course, User::findCurrent()->id) ) { $actions->addLink( _('Sitzungsordner anlegen'), @@ -367,45 +366,42 @@ class Course_DatesController extends AuthenticatedController public function export_action() { - $sem = new Seminar($this->course); - $themen =& $sem->getIssues(); + $themen = CourseTopic::findBySeminar_id($this->course->id); - $termine = getAllSortedSingleDates($sem); + $termine = $this->course->getAllDatesInSemester()->getSingleDates(true, true, true); $dates = []; - if (is_array($termine) && sizeof($termine) > 0) { - foreach ($termine as $singledate_id => $singledate) { - if (!$singledate->isExTermin()) { - $tmp_ids = $singledate->getIssueIDs(); - $title = $description = ''; - if (is_array($tmp_ids)) { - $title = trim(join("\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getTitle();}, $tmp_ids))); - $description = trim(join("\n\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getDescription();}, $tmp_ids))); - } - - $dates[] = [ - 'date' => $singledate->toString(), - 'title' => $title, - 'description' => $description, - 'start' => $singledate->getStartTime(), - 'related_persons' => $singledate->getRelatedPersons(), - 'groups' => $singledate->getRelatedGroups(), - 'room' => $singledate->getRoom() ?: $singledate->raum, - 'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name'] - ]; - } elseif ($singledate->getComment()) { - $dates[] = [ - 'date' => $singledate->toString(), - 'title' => _('fällt aus') . ' (' . _('Kommentar:') . ' ' . $singledate->getComment() . ')', - 'description' => '', - 'start' => $singledate->getStartTime(), - 'related_persons' => [], - 'groups' => [], - 'room' => '', - 'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name'] - ]; + foreach ($termine as $singledate) { + if ($singledate instanceof CourseDate) { + $tmp_ids = $singledate->getIssueIDs(); + $title = $description = ''; + if (is_array($tmp_ids)) { + $title = trim(join("\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getTitle();}, $tmp_ids))); + $description = trim(join("\n\n", array_map(function ($tid) use ($themen) {return $themen[$tid]->getDescription();}, $tmp_ids))); } + + $dates[] = [ + 'date' => $singledate->toString(), + 'title' => $title, + 'description' => $description, + 'start' => $singledate->getStartTime(), + 'related_persons' => $singledate->getRelatedPersons(), + 'groups' => $singledate->getRelatedGroups(), + 'room' => $singledate->getRoom() ?: $singledate->raum, + 'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name'] + ]; + } elseif ($singledate->getComment()) { + $dates[] = [ + 'date' => $singledate->toString(), + 'title' => _('fällt aus') . ' (' . _('Kommentar:') . ' ' . $singledate->getComment() . ')', + 'description' => '', + 'start' => $singledate->getStartTime(), + 'related_persons' => [], + 'groups' => [], + 'room' => '', + 'type' => $GLOBALS['TERMIN_TYP'][$singledate->getDateType()]['name'] + ]; } } @@ -434,9 +430,12 @@ class Course_DatesController extends AuthenticatedController */ public function export_csv_action() { - $sem = new Seminar($this->course); - $dates = getAllSortedSingleDates($sem); - $issues = $sem->getIssues(); + $dates = $this->course->getAllDatesInSemester(true, true, true); + $raw_issues = CourseTopic::findBySeminar_id($this->course->id); + $issues = []; + foreach ($raw_issues as $issue) { + $issues[$issue->id] = $issue; + } $columns = [ _('Wochentag'), @@ -523,7 +522,7 @@ class Course_DatesController extends AuthenticatedController $data[] = $row; } - $filename = $sem->name . '-' . _('Ablaufplan') . '.csv'; + $filename = $this->course->name . '-' . _('Ablaufplan') . '.csv'; $this->render_csv($data, $filename); } diff --git a/app/controllers/course/details.php b/app/controllers/course/details.php index e1c9493ada803dccbdc4a0e227eca9ef1369a269..10f3f57ccc2b50c2bdec26d355ad0b7c01788f48 100644 --- a/app/controllers/course/details.php +++ b/app/controllers/course/details.php @@ -70,7 +70,6 @@ class Course_DetailsController extends AuthenticatedController $this->prelim_discussion = vorbesprechung($this->course->id); $this->title = $this->course->getFullName(); $this->course_domains = UserDomain::getUserDomainsForSeminar($this->course->id); - $this->sem = new Seminar($this->course); $this->links = []; //public folders @@ -203,11 +202,12 @@ class Course_DetailsController extends AuthenticatedController $sidebar = Sidebar::Get(); + $enrolment_info = null; + if ($GLOBALS['SessionSeminar'] === $this->course->id) { Navigation::activateItem('/course/main/details'); } else { - $sidebarlink = true; - $enrolment_info = $this->sem->getEnrolmentInfo($GLOBALS['user']->id); + $enrolment_info = $this->course->getEnrolmentInformation($GLOBALS['user']->id); } $links = new ActionsWidget(); @@ -217,12 +217,12 @@ class Course_DetailsController extends AuthenticatedController Icon::create('print'), ['class' => 'print_action', 'target' => '_blank'] ); - if (isset($enrolment_info) && $enrolment_info['enrolment_allowed'] && $sidebarlink) { - if (in_array($enrolment_info['cause'], ['member', 'root', 'courseadmin'])) { - $abo_msg = _('direkt zur Veranstaltung'); + if ($enrolment_info && $enrolment_info->isEnrolmentAllowed()) { + if (in_array($enrolment_info->getCodeword(), ['already_member', 'root', 'course_admin'])) { + $abo_msg = _('Direkt zur Veranstaltung'); } else { $abo_msg = _('Zugang zur Veranstaltung'); - if ($this->sem->admission_binding) { + if ($this->course->admission_binding) { PageLayout::postInfo(_('Die Anmeldung ist verbindlich, Teilnehmende können sich nicht selbst austragen.')); } } @@ -244,7 +244,7 @@ class Course_DetailsController extends AuthenticatedController if (Config::get()->SCHEDULE_ENABLE && !$GLOBALS['perm']->have_studip_perm('user', $this->course->id) && !$GLOBALS['perm']->have_perm('admin') - && $this->sem->getMetaDateCount() + && count($this->course->cycles) ) { $query = "SELECT 1 FROM `schedule_seminare` @@ -303,8 +303,8 @@ class Course_DetailsController extends AuthenticatedController ); $sidebar->addWidget($share); - if (isset($enrolment_info) && $enrolment_info['description']) { - PageLayout::postInfo($enrolment_info['description']); + if ($enrolment_info) { + PageLayout::postMessage($enrolment_info->toMessageBox()); } } } diff --git a/app/controllers/course/enrolment.php b/app/controllers/course/enrolment.php index 420b5ca91bc86bad618f8fbce6024c55642a038c..8724ff266eed10950d3943f936315e2ae4f310fc 100644 --- a/app/controllers/course/enrolment.php +++ b/app/controllers/course/enrolment.php @@ -39,12 +39,15 @@ class Course_EnrolmentController extends AuthenticatedController if (!get_object_type($this->course_id, ['sem'])) { throw new Trails\Exception(400); } - $course = Seminar::GetInstance($this->course_id); - $enrolment_info = $course->getEnrolmentInfo($GLOBALS['user']->id); + $course = Course::find($this->course_id); + $enrolment_info = $course->getEnrolmentInformation($GLOBALS['user']->id); //Ist bereits Teilnehmer/Admin/freier Zugriff -> gleich weiter - if ($enrolment_info['enrolment_allowed'] && - (in_array($enrolment_info['cause'], words('root courseadmin member')) - || ($enrolment_info['cause'] == 'free_access' && $GLOBALS['user']->id == 'nobody')) + if ( + $enrolment_info->isEnrolmentAllowed() + && ( + in_array($enrolment_info->getCodeword(), ['root', 'course_admin', 'already_member']) + || ($enrolment_info->getCodeword() === 'free_access' && !User::findCurrent()) + ) ) { $redirect_url = URLHelper::getUrl('seminar_main.php', ['auswahl' => $this->course_id]); if (Request::isXhr()) { @@ -56,10 +59,10 @@ class Course_EnrolmentController extends AuthenticatedController return false; } //Grundsätzlich verboten - if (!$enrolment_info['enrolment_allowed']) { - throw new AccessDeniedException($enrolment_info['description']); + if (!$enrolment_info->isEnrolmentAllowed()) { + throw new AccessDeniedException($enrolment_info->getMessage()); } - PageLayout::setTitle($course->getFullName() . " - " . _("Veranstaltungsanmeldung")); + PageLayout::setTitle($course->getFullName() . ' - ' . _('Veranstaltungsanmeldung')); if (Request::submitted('cancel')) { $this->redirect(URLHelper::getURL('dispatch.php/course/details/', ['sem_id' => $this->course_id])); } @@ -109,13 +112,14 @@ class Course_EnrolmentController extends AuthenticatedController } if (StudipLock::get('enrolment' . $this->course_id)) { $course = Course::find($this->course_id); + $user = User::find($user_id); if ($course->getFreeSeats() && !$course->getNumWaiting()) { $enrol_user = true; - } elseif ($course->isWaitlistAvailable()) { - $seminar = new Seminar($course); - if ($maxpos = $seminar->addToWaitlist($user_id, 'last')) { - $msg = _("Diese Veranstaltung ist teilnahmebeschränkt."); - $msg_details[] = sprintf(_("Alle Plätze sind belegt, Sie wurden daher auf Platz %s der Warteliste gesetzt."), $maxpos); + } elseif ($user && $course->isWaitlistAvailable()) { + $application = $course->addMemberToWaitlist($user, 'last'); + if ($application) { + $msg = _('Diese Veranstaltung ist teilnahmebeschränkt.'); + $msg_details[] = sprintf(_('Alle Plätze sind belegt, Sie wurden daher auf Platz %s der Warteliste gesetzt.'), $application->position); } } elseif ($course->admission_disable_waitlist) { $this->admission_error = MessageBox::error(_("Die Anmeldung war nicht erfolgreich. Alle Plätze sind belegt und es steht keine Warteliste zur Verfügung.")); @@ -176,55 +180,62 @@ class Course_EnrolmentController extends AuthenticatedController } if ($enrol_user && $this->confirmed) { - $course = Seminar::GetInstance($this->course_id); + $course = Course::find($this->course_id); if ($course->admission_prelim) { if (Request::get('admission_comment')) { $admission_comment = get_fullname() . ': ' . Request::get('admission_comment'); } else { $admission_comment = ''; } - if ($course->addPreliminaryMember($user_id, $admission_comment)) { - if ($course->isStudygroup()) { - if (StudygroupModel::isInvited($user_id, $this->course_id)) { - // an invitation exists, so accept the join request automatically - $status = 'autor'; - StudygroupModel::accept_user(get_username($user_id), $this->course_id); - StudygroupModel::cancelInvitation(get_username($user_id), $this->course_id); - $success = sprintf( - _("Sie wurden in die Veranstaltung %s als %s eingetragen."), - htmlReady($course->getName()), - htmlReady(get_title_for_status($status, 1, $course->status)) - ); - PageLayout::postSuccess($success); - } else { - $success = sprintf( - _("Sie wurden auf die Anmeldeliste der Studiengruppe %s eingetragen. Die Moderatoren der Studiengruppe können Sie jetzt freischalten."), - htmlReady($course->getName()) - ); - PageLayout::postSuccess($success); - } + try { + $course->addPreliminaryMember($user_id, $admission_comment); + } catch (Exception $e) { + PageLayout::postError($e->getMessage()); + return; + } + if ($course->isStudygroup()) { + if (StudygroupModel::isInvited($user_id, $this->course_id)) { + // an invitation exists, so accept the join request automatically + $status = 'autor'; + StudygroupModel::accept_user(get_username($user_id), $this->course_id); + StudygroupModel::cancelInvitation(get_username($user_id), $this->course_id); + $success = sprintf( + _('Sie wurden in die Veranstaltung %1$s als %2$s eingetragen.'), + htmlReady($course->getFullName()), + htmlReady(get_title_for_status($status, 1, $course->status)) + ); + PageLayout::postSuccess($success); } else { $success = sprintf( - _("Sie wurden in die Veranstaltung %s vorläufig eingetragen."), - htmlReady($course->getName()) + _('Sie wurden auf die Anmeldeliste der Studiengruppe %s eingetragen. Die Personen, die die Studiengruppe moderieren, können Sie jetzt freischalten.'), + htmlReady($course->name) ); PageLayout::postSuccess($success); } - } - } else { - $status = 'autor'; - if ($course->addMember($user_id, $status)) { + } else { $success = sprintf( - _("Sie wurden in die Veranstaltung %s als %s eingetragen."), - htmlReady($course->getName()), - htmlReady(get_title_for_status($status, 1, $course->status)) + _('Sie wurden in die Veranstaltung %s vorläufig eingetragen.'), + htmlReady($course->getFullName()) ); PageLayout::postSuccess($success); + } + } else { + $status = 'autor'; + try { + $course->addMember($user_id, $status); + } catch (Exception $e) { + PageLayout::postError($e->getMessage()); + } + $success = sprintf( + _('Sie wurden in die Veranstaltung %1$s als %2$s eingetragen.'), + htmlReady($course->getFullName()), + htmlReady(get_title_for_status($status, 1, $course->status)) + ); + PageLayout::postSuccess($success); - if (StudygroupModel::isInvited($user_id, $this->course_id)) { - // delete an existing invitation - StudygroupModel::cancelInvitation(get_username($user_id), $this->course_id); - } + if (StudygroupModel::isInvited($user_id, $this->course_id)) { + // delete an existing invitation + StudygroupModel::cancelInvitation(get_username($user_id), $this->course_id); } } unset($this->courseset_message); diff --git a/app/controllers/course/grouping.php b/app/controllers/course/grouping.php index 588872b348ad370768d7e0353d3e432cb24b4770..aec926443ec181e7587310816a81a738bb516004 100644 --- a/app/controllers/course/grouping.php +++ b/app/controllers/course/grouping.php @@ -302,15 +302,16 @@ class Course_GroupingController extends AuthenticatedController { CSRFProtection::verifyUnsafeRequest(); - $source = Seminar::getInstance($source_id); - $target = Seminar::getInstance(Request::option('target')); + $source = Course::find($source_id); + $target = Course::find(Request::option('target')); $success = 0; $fail = 0; - foreach (Request::getArray('users') as $user) { - $m = CourseMember::find([$source_id, $user]); + foreach (Request::getArray('users') as $user_id) { + $m = CourseMember::find([$source_id, $user_id]); $status = $m->status; + $user = User::find($user_id); if ($source->deleteMember($user)) { $target->addMember($user, $status); $success += 1; @@ -336,20 +337,24 @@ class Course_GroupingController extends AuthenticatedController */ public function remove_members_action($course_id, $user_id = null) { - $s = Seminar::getInstance($course_id); + $course = Course::find($course_id); $success = 0; $fail = 0; $users = $user_id ? [$user_id] : $this->flash['users']; - foreach ($users as $user) { - if ($s->deleteMember($user)) { - $success += 1; - } else { - $fail += 1; - } - } + User::findEachMany( + function (User $user) use ($course, &$fail, &$success) { + try { + $course->deleteMember($user); + $success += 1; + } catch (\Studip\MembershipException $e) { + $fail += 1; + } + }, + $users + ); if ($success > 0) { PageLayout::postSuccess(sprintf(_('%u Personen wurden entfernt.'), $success)); @@ -511,10 +516,11 @@ class Course_GroupingController extends AuthenticatedController $fail = []; // Iterate over selected courses... foreach (Request::optionArray('courses') as $course) { - $sem = Seminar::getInstance($course); + $course_obj = Course::find($course); // ... and selected users. foreach (Request::optionArray('users') as $user) { + $user_obj = User::find($user); // Try to add deputies. if (Request::option('permission') == 'deputy') { // If not already deputy, create new entry. @@ -524,20 +530,24 @@ class Course_GroupingController extends AuthenticatedController $d->user_id = $user; // Error on storing. if (!$d->store()) { - $fail[$sem->getFullName()][] = $user; + $fail[$course_obj->getFullname()][] = $user; // Check if new deputy was regular member before, remove entry. } else { $m = CourseMember::find([$course, $user]); // Could not delete old course membership, remove deputy entry. if ($m && !$m->delete()) { $d->delete(); - $fail[$sem->getFullName()][] = $user; + $fail[$course_obj->getFullname()][] = $user; } } } // Add member with given permission. - } elseif (!$sem->addMember($user, Request::option('permission'))) { - $fail[$sem->getFullName()][] = $user; + } else { + try { + $course_obj->addMember($user_obj, Request::option('permission')); + } catch (\Studip\EnrolmentException $e) { + $fail[$course_obj->getFullname()][] = $user; + } } } } @@ -565,8 +575,9 @@ class Course_GroupingController extends AuthenticatedController */ private function sync_users($parent_id, $child_id) { - $sem = Seminar::getInstance($parent_id); - $csem = Seminar::getInstance($child_id); + $parent_course = Course::find($parent_id); + $child_course = Course::find($child_id); + /* * Find users that are in current course but not in parent. */ @@ -582,11 +593,19 @@ class Course_GroupingController extends AuthenticatedController )"; $diff = DBManager::get()->prepare($query); - /* - * Before synchronizing the lecturers, we add all institutes - * from child course. - */ - $sem->setInstitutes(array_merge($sem->getInstitutes(), $csem->getInstitutes())); + //Before synchronizing the lecturers, we add all institutes from the child course: + $parent_institute_ids = [$parent_course->institut_id]; + foreach ($parent_course->institutes as $institute) { + $parent_institute_ids[] = $institute->id; + } + + foreach ($child_course->institutes as $institute) { + if (!in_array($institute->id, $parent_institute_ids)) { + //Add the institute to the parent course: + $parent_course->institutes->append($institute); + } + } + $parent_course->store(); /* * Synchronize all members (including lecturers, tutors @@ -598,20 +617,24 @@ class Course_GroupingController extends AuthenticatedController 'status' => $permission, 'parent' => $parent_id ]); - foreach ($diff->fetchFirst() as $user) { - $sem->addMember($user, $permission); + foreach ($diff->fetchFirst() as $user_id) { + $user = User::find($user_id); + if (!$user) { + continue; + } + $parent_course->addMember($user, $permission); // Add default deputies of current user if applicable. if ($permission === 'dozent' && Config::get()->DEPUTIES_ENABLE && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) { - foreach (Deputy::findByRange_id($user) as $deputy) { + foreach (Deputy::findByRange_id($user_id) as $deputy) { if (!Deputy::exists([$parent_id, $deputy->user_id]) && !CourseMember::exists([$parent_id, $deputy->user_id])) { $d = new Deputy(); $d->range_id = $parent_id; - $d->user_id = $user; + $d->user_id = $user_id; $d->store(); } } diff --git a/app/controllers/course/members.php b/app/controllers/course/members.php index 0ab18f83cd16bd0c8818442a4b43485a6f71b667..fd650772b8425cbeaafc6d322cb2d38f7dcb080a 100644 --- a/app/controllers/course/members.php +++ b/app/controllers/course/members.php @@ -97,7 +97,7 @@ class Course_MembersController extends AuthenticatedController throw new AccessDeniedException(); } - $sem = Seminar::getInstance($this->course_id); + $course = Course::find($this->course_id); $this->sort_by = Request::option('sortby', 'nachname'); $this->order = Request::option('order', 'desc'); $this->sort_status = Request::get('sort_status', ''); @@ -154,12 +154,12 @@ class Course_MembersController extends AuthenticatedController // Check Seminar $this->waitingTitle = _('Warteliste (nicht aktiv)'); $this->waiting_type = 'awaiting'; - if ($this->is_tutor && $sem->isAdmissionEnabled()) { - $this->course = $sem; - $distribution_time = $sem->getCourseSet()->getSeatDistributionTime(); - if ($sem->getCourseSet()->hasAlgorithmRun()) { + if ($this->is_tutor && $course->isAdmissionEnabled()) { + $this->course = $course; + $distribution_time = $course->getCourseSet()->getSeatDistributionTime(); + if ($course->getCourseSet()->hasAlgorithmRun()) { $this->waitingTitle = _("Warteliste"); - if (!$sem->admission_disable_waitlist_move) { + if (!$course->admission_disable_waitlist_move) { $this->waitingTitle .= ' (' . _("automatisches Nachrücken ist eingeschaltet") . ')'; } else { $this->waitingTitle .= ' (' . _("automatisches Nachrücken ist ausgeschaltet") . ')'; @@ -185,8 +185,12 @@ class Course_MembersController extends AuthenticatedController $this->to_waitlist_actions = false; // Check for waitlist availability (influences available actions) // People can be moved to waitlist if waitlist available and no automatic moving up. - if (!$sem->admission_disable_waitlist && $sem->admission_disable_waitlist_move - && $sem->isAdmissionEnabled() && $sem->getCourseSet()->hasAlgorithmRun()) { + if ( + !$course->admission_disable_waitlist + && $course->admission_disable_waitlist_move + && $course->isAdmissionEnabled() + && $course->getCourseSet()->hasAlgorithmRun() + ) { $this->to_waitlist_actions = true; } } @@ -290,21 +294,44 @@ class Course_MembersController extends AuthenticatedController // load MultiPersonSearch object $mp = MultiPersonSearch::load("add_autor" . $this->course_id); - - $countAdded = 0; - $msg = []; - foreach ($mp->getAddedUsers() as $a) { - if ($this->addMember($a, true, Request::bool('consider_contingent', false), $msg)) { - $countAdded++; - } + $course = Course::find($this->course_id); + if (!$course) { + PageLayout::postError(_('Die ausgewählte Veranstaltung wurde nicht gefunden!')); + $this->redirect($this->indexURL()); + return; } - if ($countAdded == 1) { - $text = _('Es wurde eine neue Person hinzugefügt.'); - } else { - $text = sprintf(_('Es wurden %s neue Personen hinzugefügt.'), $countAdded); + $added_c = 0; + $errors = []; + User::findEachMany( + function (User $user) use ($course, &$added_c, &$errors) { + try { + $course->addMember($user); + $added_c++; + } catch (\Studip\Exception $e) { + $errors[] = $e->getMessage(); + } + }, + $mp->getAddedUsers() + ); + if ($added_c > 0) { + PageLayout::postSuccess( + sprintf( + ngettext( + 'Es wurde eine neue Person hinzugefügt.', + 'Es wurden %u neue Personen hinzugefügt.', + $added_c + ), + $added_c + ) + ); + } + if ($errors) { + PageLayout::postError( + _('Die folgenden Fehler traten beim Eintragen von Personen auf:'), + $errors + ); } - PageLayout::postSuccess($text, $msg['success']); $this->redirect($this->indexURL()); } @@ -321,22 +348,33 @@ class Course_MembersController extends AuthenticatedController // load MultiPersonSearch object $mp = MultiPersonSearch::load("add_dozent" . $this->course_id); - $sem = Seminar::GetInstance($this->course_id); - $countAdded = 0; - foreach ($mp->getAddedUsers() as $a) { - if($this->addDozent($a)) { - $countAdded++; - } - } - if($countAdded > 0) { - $status = get_title_for_status('dozent', $countAdded, $sem->status); - if ($countAdded == 1) { - PageLayout::postSuccess(sprintf(_('Ein %s wurde hinzugefügt.'), htmlReady($status))); - } else { - PageLayout::postSuccess(sprintf(_("Es wurden %s %s Personen hinzugefügt."), $countAdded, htmlReady($status))); - } + $course = Course::find($this->course_id); + $added_c = 0; + User::findEachMany( + function (User $user) use ($course, &$added_c) { + try { + $course->addMember($user, 'dozent'); + $added_c++; + } catch (\Studip\Exception $e) { + //Nothing here. + } + }, + $mp->getAddedUsers() + ); + if ($added_c > 0) { + $status = get_title_for_status('dozent', $added_c, $course->status); + PageLayout::postSuccess( + sprintf( + ngettext( + '%1$u %2$s wurde hinzugefügt.', + '%1$u %2$s wurden hinzugefügt.', + $added_c + ), + $added_c, + htmlReady($status) + ) + ); } - $this->redirect('course/members/index'); } @@ -353,61 +391,49 @@ class Course_MembersController extends AuthenticatedController // load MultiPersonSearch object $mp = MultiPersonSearch::load('add_waitlist' . $this->course_id); - $countAdded = 0; - $countFailed = 0; - foreach ($mp->getAddedUsers() as $a) { - if ($this->addToWaitlist($a)) { - $countAdded++; - } else { - $countFailed++; - } - } + $course = Course::find($this->course_id); + $added_c = 0; + $errors = []; + User::findEachMany( + function (User $user) use ($course, &$added_c, &$errors) { + try { + $course->addMemberTowaitlist($user); + $added_c++; + } catch (\Studip\Exception $e) { + $errors[] = $e->getMessage(); + } + }, + $mp->getAddedUsers() + ); - if ($countAdded) { - PageLayout::postSuccess(sprintf(ngettext('Es wurde %u neue Person auf der Warteliste hinzugefügt.', - 'Es wurden %u neue Personen auf der Warteliste hinzugefügt.', $countAdded), $countAdded)); + if ($added_c) { + PageLayout::postSuccess( + sprintf( + ngettext( + 'Eine Person wurde zur Warteliste hinzugefügt.', + '%u Personen wurden zur Warteliste hinzugefügt.', + $added_c + ), + $added_c + ) + ); } - if ($countFailed) { - PageLayout::postError(sprintf(ngettext('%u Person konnte nicht auf die Warteliste eingetragen werden.', - '%u neue Personen konnten nicht auf die Warteliste eingetragen werden.', $countFailed), - $countFailed)); + if ($errors) { + PageLayout::postError( + sprintf( + ngettext( + 'Eine Person konnte nicht zur Warteliste hinzugefügt werden:', + '%u Personen konnten nicht zur Warteliste hinzugefügt werden:', + count($errors) + ), + count($errors) + ), + $errors + ); } $this->redirect('course/members/index'); } - /** - * Helper function to add dozents to a seminar. - */ - private function addDozent($dozent) - { - $sem = Seminar::GetInstance($this->course_id); - if ($sem->addMember($dozent, "dozent")) { - // Only applicable when globally enabled and user deputies enabled too - if (Config::get()->DEPUTIES_ENABLE) { - // Check whether chosen person is set as deputy - // -> delete deputy entry. - $deputy = Deputy::find([$this->course_id, $dozent]); - if ($deputy) { - $deputy->delete(); - } - // Add default deputies of the chosen lecturer... - if (Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) { - $deputies = Deputy::findDeputies($dozent)->pluck('user_id'); - $lecturers = $sem->getMembers(); - foreach ($deputies as $deputy) { - // ..but only if not already set as lecturer or deputy. - if (!isset($lecturers[$deputy])) { - Deputy::addDeputy($deputy, $this->course_id); - } - } - } - } - return true; - } else { - return false; - } - } - /** * Add tutors to a seminar. * @throws AccessDeniedException @@ -421,28 +447,36 @@ class Course_MembersController extends AuthenticatedController // load MultiPersonSearch object $mp = MultiPersonSearch::load("add_tutor" . $this->course_id); - $sem = Seminar::GetInstance($this->course_id); - $countAdded = 0; - foreach ($mp->getAddedUsers() as $a) { - if ($this->addTutor($a)) { - $countAdded++; - } - } - if($countAdded) { - PageLayout::postSuccess(sprintf(_('%s wurde hinzugefügt.'), htmlReady(get_title_for_status('tutor', $countAdded, $sem->status)))); + $course = Course::find($this->course_id); + $added_c = 0; + User::findEachMany( + function (User $user) use ($course, &$added_c) { + try { + $course->addMember($user, 'tutor'); + $added_c++; + } catch (\Studip\Exception $e) { + //Nothing here. + } + }, + $mp->getAddedUsers() + ); + if($added_c > 0) { + $status = get_title_for_status('tutor', $added_c, $course->status); + PageLayout::postSuccess( + sprintf( + ngettext( + '%1$u %2$s wurde hinzugefügt.', + '%1$u %2$s wurden hinzugefügt.', + $added_c + ), + $added_c, + $status + ) + ); } $this->redirect('course/members/index'); } - private function addTutor($tutor) { - $sem = Seminar::GetInstance($this->course_id); - if ($sem->addMember($tutor, "tutor")) { - return true; - } else { - return false; - } - } - /** * Provides a dialog to move or copy selected users to another course. */ @@ -634,6 +668,7 @@ class Course_MembersController extends AuthenticatedController if (Request::get('csv_import')) { // remove duplicate users from csv-import $csv_lines = array_unique($csv_request); + $course = Course::find($this->course_id); foreach ($csv_lines as $csv_line) { $csv_name = preg_split('/[,\t]/', mb_substr($csv_line, 0, 100), -1, PREG_SPLIT_NO_EMPTY); $csv_nachname = trim($csv_name[0]); @@ -671,16 +706,19 @@ class Course_MembersController extends AuthenticatedController $row = reset($csv_users); if (!$row['is_present']) { $consider_contingent = Request::option('consider_contingent_csv'); - - if (CourseMember::insertCourseMember($this->course_id, $row['user_id'], 'autor', isset($consider_contingent), $consider_contingent)) { - $csv_count_insert++; - setTempLanguage($this->user_id); - - $message = sprintf(_('Sie wurden in die Veranstaltung **%s** eingetragen.'), $this->course_title); - - restoreLanguage(); - $messaging->insert_message($message, $row['username'], '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')), TRUE); - } elseif (isset($consider_contingent)) { + $user = User::find($row['user_id']); + $failure = false; + if ($user) { + try { + $course->addMember($user, 'autor', $consider_contingent); + $csv_count_insert++; + } catch (\Studip\Exception $e) { + $failure = true; + } + } else { + $failure = true; + } + if ($failure && isset($consider_contingent)) { $csv_count_contingent_full++; } } else { @@ -695,19 +733,17 @@ class Course_MembersController extends AuthenticatedController $selected_users = Request::getArray('selected_users'); if (!empty($selected_users) && count($selected_users) > 0) { + $course = Course::find($this->course_id); foreach ($selected_users as $selected_user) { - if ($selected_user) { - if (CourseMember::insertCourseMember($this->course_id, get_userid($selected_user), 'autor', isset($consider_contingent), $consider_contingent)) { + $user = User::findByUsername($selected_user); + if ($user) { + try { + $course->addMember($user, 'autor', $consider_contingent); $csv_count_insert++; - setTempLanguage($this->user_id); - $message = sprintf(_('Sie wurden manu - - ell in die Veranstaltung **%s** eingetragen.'), $this->course_title); - - restoreLanguage(); - $messaging->insert_message($message, $selected_user, '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')), TRUE); - } elseif (isset($consider_contingent)) { - $csv_count_contingent_full++; + } catch (\Studip\Exception $e) { + if (isset($consider_contingent)) { + $csv_count_contingent_full++; + } } } } @@ -998,11 +1034,8 @@ class Course_MembersController extends AuthenticatedController throw new AccessDeniedException(); } - if ( - !$this->is_dozent - && in_array($target_status, ['tutor', 'dozent']) - ) { - throw new AccessDeniedException(_('Sie dürfen keine Lehrenden oder Tutor/-innen in diese Veranstaltung eintragen.')); + if (!$this->is_dozent && in_array($target_status, ['tutor', 'dozent'])) { + throw new AccessDeniedException(_('Sie dürfen keine lehrenden Personen oder Hilfspersonen in diese Veranstaltung eintragen.')); } if (isset($this->flash['consider_contingent'])) { @@ -1018,29 +1051,64 @@ class Course_MembersController extends AuthenticatedController } if ($users) { - $msgs = $this->insertAdmissionMember( - $users, - $target_status, - Request::bool('consider_contingent', false), - $status === 'accepted' - ); - if ($msgs) { - if ($cmd === 'add_user') { - $message = sprintf(_('%s wurde in die Veranstaltung mit dem Status <b>%s</b> eingetragen.'), htmlReady(join(',', $msgs)), $this->decoratedStatusGroups['autor']); - } else { - if ($status === 'awaiting') { - $message = sprintf(_('%s wurde aus der Anmelde bzw. Warteliste mit dem Status - <b>%s</b> in die Veranstaltung eingetragen.'), htmlReady(join(', ', $msgs)), $this->decoratedStatusGroups[$target_status]); - } else { - $message = sprintf(_('%s wurde mit dem Status <b>%s</b> endgültig akzeptiert - und damit in die Veranstaltung aufgenommen.'), htmlReady(join(', ', $msgs)), $this->decoratedStatusGroups[$target_status]); + $enrolled_user_names = []; + $errors = []; + $course = Course::find($this->course_id); + foreach ($users as $user_id => $value) { + if ($value) { + $user = User::find($user_id); + if ($user) { + try { + //Add the user but do not renumber the admission list. This is done manually + //to avoid a mass of mails being sent. + $course->addMember( + $user, + $target_status, + Request::bool('consider_contingent', false), + true, + false + ); + $enrolled_user_names[] = $user->getFullName(); + } catch (\Studip\Exception $e) { + $errors[] = sprintf( + '%1$s: %2$s', + $user->getFullName(), + $e->getMessage() + ); + } } } + } - PageLayout::postSuccess($message); + //Renumber the admission list: + AdmissionApplication::renumberAdmission($this->course_id); + + if ($enrolled_user_names) { + $message = sprintf( + _('%1$s wurde in die Veranstaltung mit dem Status „%2$s“ eingetragen.'), + htmlReady(join(',', $enrolled_user_names)), + $this->decoratedStatusGroups['autor'] + ); } else { - $message = _("Es stehen keine weiteren Plätze mehr im Teilnehmendenkontingent zur Verfügung."); - PageLayout::postError($message); + if ($status === 'awaiting') { + $message = sprintf( + _('%1$s wurde aus der Anmelde- bzw. Warteliste mit dem Status "%2$s" in die Veranstaltung eingetragen.'), + htmlReady(implode(', ', $enrolled_user_names)), + $this->decoratedStatusGroups[$target_status] + ); + } else { + $message = sprintf(_('%1$s wurde mit dem Status "%2$s" endgültig akzeptiert und damit in die Veranstaltung eingetragen.'), + htmlReady(implode(', ', $enrolled_user_names)), + $this->decoratedStatusGroups[$target_status] + ); + } + PageLayout::postSuccess($message); + } + if ($errors) { + PageLayout::postError( + _('Es traten Fehler beim Hochstufen von Personen auf:'), + $errors + ); } } else { PageLayout::postError(_('Sie haben niemanden zum Hochstufen ausgewählt.')); @@ -1062,7 +1130,7 @@ class Course_MembersController extends AuthenticatedController throw new AccessDeniedException(); } - $course = Seminar::GetInstance($this->course_id); + $course = Course::find($this->course_id); if (!Request::submitted('no')) { if (Request::submitted('yes')) { CSRFProtection::verifyUnsafeRequest(); @@ -1072,10 +1140,39 @@ class Course_MembersController extends AuthenticatedController $this->validateTutorPermission($users, $this->course_id); } if (!empty($users)) { + $removed_users = []; + $errors = []; if (in_array($status, words('accepted awaiting claiming'))) { - $msgs = $course->cancelAdmissionSubscription($users, $status); + foreach ($users as $user) { + $course->removePreliminaryMember($user); + } } else { - $msgs = $course->cancelSubscription($users); + foreach ($users as $user) { + try { + $course->cancelSubscription($user); + $removed_users[] = $user->getFullName(); + } catch (Exception $e) { + $errors[] = $e->getMessage(); + } + } + } + + if (!empty($errors)) { + PageLayout::postError( + _('Die folgenden Fehler traten beim Austragen von Personen auf:'), + $errors + ); + } + if (count($removed_users) > 5) { + PageLayout::postSuccess( + _('%u Personen wurden ausgetragen.'), + count($removed_users) + ); + } elseif (count($removed_users) > 0) { + PageLayout::postSuccess( + _('Die folgenden Personen wurden ausgetragen:'), + $removed_users + ); } // deleted authors @@ -1236,22 +1333,50 @@ class Course_MembersController extends AuthenticatedController throw new AccessDeniedException(); } - $users = []; + $user_ids = []; if (!empty($this->flash['users'])) { - $users = array_keys(array_filter($this->flash['users'])); - } - - if (!empty($users)) { - $msg = $this->moveToWaitlist($users, $which_end); - if (count($msg['success'])) { - PageLayout::postSuccess(sprintf(_('%s Person(en) wurden auf die Warteliste verschoben.'), - count($msg['success'])), - count($msg['success']) <= 5 ? $msg['success'] : []); + $user_ids = array_keys(array_filter($this->flash['users'])); + } + + if (!empty($user_ids)) { + $course = Course::find($this->course_id); + $success_c = 0; + $errors = []; + User::findEachMany( + function (User $user) use ($course, &$success_c, &$errors) { + try { + $course->moveMemberToWaitlist($user, true); + $success_c++; + } catch (\Studip\Exception $e) { + $errors[] = $e->getMessage(); + } + }, + $user_ids + ); + if ($success_c > 0) { + PageLayout::postSuccess( + studip_interpolate( + ngettext( + 'Eine Person wurden auf die Warteliste verschoben.', + '%{number} Personen wurden auf die Warteliste verschoben.', + $success_c + ), + ['number' => $success_c] + ) + ); } - if (count($msg['error'])) { - PageLayout::postError(sprintf(_('%s Person(en) konnten nicht auf die Warteliste verschoben werden.'), - count($msg['error'])), - count($msg['error']) <= 5 ? $msg['error'] : []); + if (count($errors)) { + PageLayout::postError( + studip_interpolate( + ngettext( + 'Eine Person konnten nicht auf die Warteliste verschoben werden:', + '%{number} Personen konnten nicht auf die Warteliste verschoben werden:', + count($errors) + ), + count($errors) + ), + $errors + ); } } else { PageLayout::postError(_('Sie haben keine Personen zum Verschieben auf die Warteliste ausgewählt')); @@ -1402,7 +1527,7 @@ class Course_MembersController extends AuthenticatedController */ private function getSubject() { - $result = Seminar::GetInstance($this->course_id)->getNumber(); + $result = Course::find($this->course_id)->veranstaltungsnummer; return ($result == '') ? sprintf('[%s]', $this->course_title) : sprintf(_('[%s: %s]'), $result, $this->course_title); @@ -1410,7 +1535,6 @@ class Course_MembersController extends AuthenticatedController private function createSidebar($filtered_members) { - $sem = Seminar::GetInstance($this->course_id); $course = Course::find($this->course_id); $sidebar = Sidebar::get(); @@ -1441,9 +1565,9 @@ class Course_MembersController extends AuthenticatedController } if ($this->is_dozent) { if (!$this->dozent_is_locked) { - $sem_institutes = $sem->getInstitutes(); + $institute_ids = $course->institutes->pluck('id'); - if (SeminarCategories::getByTypeId($sem->status)->only_inst_user) { + if (SeminarCategories::getByTypeId($course->status)->only_inst_user) { $search_template = 'user_inst_not_already_in_sem'; } else { $search_template = 'user_not_already_in_sem'; @@ -1454,12 +1578,12 @@ class Course_MembersController extends AuthenticatedController $search_template, sprintf( _('%s suchen'), - get_title_for_status('dozent', 1, $sem->status) + get_title_for_status('dozent', 1, $course->status) ), 'user_id', [ 'permission' => 'dozent', - 'institute' => $sem_institutes, + 'institute' => $institute_ids, 'seminar_id' => $course->id, ] ); @@ -1476,10 +1600,10 @@ class Course_MembersController extends AuthenticatedController // add "add dozent" to infobox $mp = MultiPersonSearch::get("add_dozent{$this->course_id}") - ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('dozent', 1, $sem->status))) + ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('dozent', 1, $course->status))) ->setDefaultSelectedUser($filtered_members['dozent']->pluck('user_id')) ->setLinkIconPath("") - ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('dozent', 1, $sem->status))) + ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('dozent', 1, $course->status))) ->setExecuteURL($this->url_for('course/members/execute_multipersonsearch_dozent')) ->setSearchObject($searchtype) ->addQuickfilter( @@ -1498,9 +1622,9 @@ class Course_MembersController extends AuthenticatedController $widget->addElement($element); } if (!$this->tutor_is_locked) { - $sem_institutes = $sem->getInstitutes(); + $institute_ids = $course->institutes->pluck('id'); - if (SeminarCategories::getByTypeId($sem->status)->only_inst_user) { + if (SeminarCategories::getByTypeId($course->status)->only_inst_user) { $search_template = 'user_inst_not_already_in_sem'; } else { $search_template = 'user_not_already_in_sem'; @@ -1511,12 +1635,12 @@ class Course_MembersController extends AuthenticatedController $search_template, sprintf( _('%s suchen'), - get_title_for_status('tutor', 1, $sem->status) + get_title_for_status('tutor', 1, $course->status) ), 'user_id', [ 'permission' => ['dozent', 'tutor'], - 'institute' => $sem_institutes, + 'institute' => $institute_ids, 'seminar_id' => $course->id, ] ); @@ -1533,10 +1657,10 @@ class Course_MembersController extends AuthenticatedController // add "add tutor" to infobox $mp = MultiPersonSearch::get("add_tutor{$this->course_id}") - ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('tutor', 1, $sem->status))) + ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('tutor', 1, $course->status))) ->setDefaultSelectedUser($filtered_members['tutor']->pluck('user_id')) ->setLinkIconPath('') - ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('tutor', 1, $sem->status))) + ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('tutor', 1, $course->status))) ->setExecuteURL($this->url_for('course/members/execute_multipersonsearch_tutor')) ->setSearchObject($searchType) ->addQuickfilter( @@ -1571,7 +1695,7 @@ class Course_MembersController extends AuthenticatedController 'user_not_already_in_sem', sprintf( _('%s suchen'), - get_title_for_status('autor', 1, $sem->status) + get_title_for_status('autor', 1, $course->status) ), 'user_id', [ @@ -1593,10 +1717,10 @@ class Course_MembersController extends AuthenticatedController // add "add autor" to infobox $mp = MultiPersonSearch::get("add_autor{$this->course_id}") - ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('autor', 1, $sem->status))) + ->setLinkText(sprintf(_('%s eintragen'), get_title_for_status('autor', 1, $course->status))) ->setDefaultSelectedUser($filtered_members['autor']->pluck('user_id')) ->setLinkIconPath('') - ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('autor', 1, $sem->status))) + ->setTitle(sprintf(_('%s eintragen'), get_title_for_status('autor', 1, $course->status))) ->setExecuteURL($this->url_for('course/members/execute_multipersonsearch_autor')) ->setSearchObject($searchType) ->addQuickfilter( @@ -1619,10 +1743,10 @@ class Course_MembersController extends AuthenticatedController // add "add person to waitlist" to sidebar if ( - $sem->isAdmissionEnabled() - && $sem->getCourseSet()->hasAlgorithmRun() - && !$sem->admission_disable_waitlist - && (!$sem->getFreeSeats() || $sem->admission_disable_waitlist_move) + $course->isAdmissionEnabled() + && $course->getCourseSet()->hasAlgorithmRun() + && !$course->admission_disable_waitlist + && (!$course->getFreeSeats() || $course->admission_disable_waitlist_move) ) { $ignore = array_merge( $filtered_members['dozent']->pluck('user_id'), @@ -1815,11 +1939,11 @@ class Course_MembersController extends AuthenticatedController $this->dozent_count = CourseMember::countBySql("seminar_id=? AND status=?" . $visibility_constraint, [$this->course_id, 'dozent']); //Use the correct names for thte four status groups: - $sem = Seminar::GetInstance($this->course_id); - $this->user_name = get_title_for_status('user', 0, $sem->status); - $this->autor_name = get_title_for_status('autor', 0, $sem->status); - $this->tutor_name = get_title_for_status('tutor', 0, $sem->status); - $this->dozent_name = get_title_for_status('dozent', 0, $sem->status); + $course = Course::find($this->course_id); + $this->user_name = get_title_for_status('user', 0, $course->status); + $this->autor_name = get_title_for_status('autor', 0, $course->status); + $this->tutor_name = get_title_for_status('tutor', 0, $course->status); + $this->dozent_name = get_title_for_status('dozent', 0, $course->status); $this->default_subject = Request::get('default_subject'); @@ -2000,78 +2124,6 @@ class Course_MembersController extends AuthenticatedController return $msgs; } - public function addMember(string $user_id, bool $accepted = false, bool $consider_contingent = null, &$msg = []): bool - { - $user = User::find($user_id); - $messaging = new messaging; - - $status = 'autor'; - // insert - $copy_course = $accepted || $consider_contingent; - $admission_user = CourseMember::insertCourseMember($this->course_id, $user_id, $status, $copy_course, $consider_contingent, true); - - if ($admission_user) { - setTempLanguage($user_id); - $message = sprintf( - _('Sie wurden in die Veranstaltung **%s** eingetragen.'), - $this->course_title - ); - restoreLanguage(); - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')), - true - ); - $msg['success'][] = sprintf( - _('%1$s wurde in die Veranstaltung mit dem Status <b>%2$s</b> eingetragen.'), - $user->getFullName(), - $status - ); - } else if ($consider_contingent) { - PageLayout::postError(_('Es stehen keine weiteren Plätze mehr im Teilnehmendenkontingent zur Verfügung.')); - return false; - } else { - PageLayout::postError( - _('Beim Eintragen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut oder wenden Sie sich an die Administrierenden') - ); - return false; - } - //Warteliste neu sortieren - AdmissionApplication::renumberAdmission($this->course_id); - return true; - } - - /** - * Adds the given user to the waitlist of the current course and sends a - * corresponding message. - * - * @param String $user_id The user to add - * @return bool Successful operation? - */ - private function addToWaitlist(string $user_id): bool - { - $course = Seminar::getInstance($this->course_id); - // Insert user in waitlist at current position. - if ($course->addToWaitlist($user_id, 'last')) { - setTempLanguage($user_id); - $message = sprintf(_('Sie wurden von einem/einer Veranstaltungsleiter/-in (%1$s) ' . - 'oder einem/einer Administrator/-in auf die Warteliste der Veranstaltung **%2$s** gesetzt.'), - get_title_for_status('dozent', 1), $this->course_title); - restoreLanguage(); - messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'), - _('Auf Warteliste gesetzt')), $message); - - return true; - } - return false; - } - /** * Adds the given users to the target course. * @param array $users users to add @@ -2082,127 +2134,29 @@ class Course_MembersController extends AuthenticatedController private function sendToCourse(array $users, string $target_course_id, bool $move = false): array { $msg = []; - foreach ($users as $user) { - if (!CourseMember::exists([$target_course_id, $user])) { - $target_course = Seminar::GetInstance($target_course_id); + foreach ($users as $user_id) { + if (!CourseMember::exists([$target_course_id, $user_id])) { + $user = User::find($user_id); + if (!$user) { + continue; + } + $target_course = Course::find($target_course_id); if ($target_course->addMember($user)) { if ($move) { - $remove_from = Seminar::getInstance($this->course_id); + $remove_from = Course::find($this->course_id); $remove_from->deleteMember($user); } - $msg['success'][] = $user; + $msg['success'][] = $user_id; } else { - $msg['failed'][] = $user; + $msg['failed'][] = $user_id; } } else { - $msg['existing'][] = $user; + $msg['existing'][] = $user_id; } } return $msg; } - private function insertAdmissionMember(array $users, string $next_status, bool $consider_contingent, bool $accepted = false, string $cmd = 'add_user'): array - { - $messaging = new messaging; - $msgs = []; - foreach ($users as $user_id => $value) { - if ($value) { - $user = User::find($user_id); - if ($user) { - $admission_user = CourseMember::insertCourseMember( - $this->course_id, - $user_id, - $next_status, - $accepted || $consider_contingent, - $consider_contingent - ); - - // only if user was on the waiting list - if ($admission_user) { - setTempLanguage($user_id); - restoreLanguage(); - - if ($cmd === 'add_user') { - $message = sprintf( - _('Sie wurden in die Veranstaltung **%s** eingetragen.'), - $this->course_title - ); - } else { - if (!$accepted) { - $message = sprintf( - _('Sie wurden aus der Warteliste in die Veranstaltung **%s** aufgenommen und sind damit zugelassen.'), - $this->course_title - ); - } else { - $message = sprintf( - _('Sie wurden vom Status **vorläufig akzeptiert** auf **teilnehmend** in der Veranstaltung **%s** hochgestuft und sind damit zugelassen.'), - $this->course_title - ); - } - } - - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')), - true - ); - $msgs[] = $user->getFullName(); - } - } - } - } - - // resort admissionlist - AdmissionApplication::renumberAdmission($this->course_id); - - return $msgs; - } - - /** - * Adds given users to the course waitlist, either at list beginning or end. - * System messages are sent to affected users. - * - * @param array $users array of user ids to add - * @param String $which_end 'last' or 'first': which list end to append to - * @return array Array of messages (stating success and/or errors) - */ - public function moveToWaitlist($users, $which_end) - { - $course = Seminar::getInstance($this->course_id); - $msgs = [ - 'success' => [], - 'error' => [], - ]; - foreach ($users as $user_id) { - // Delete member from seminar - $temp_user = User::find($user_id); - if ($course->deleteMember($user_id)) { - setTempLanguage($user_id); - $message = sprintf(_('Sie wurden aus der Veranstaltung **%s** abgemeldet. '. - 'Sie wurden auf die Warteliste dieser Veranstaltung gesetzt.'), - $this->course_title); - restoreLanguage(); - messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'), - _('Anmeldung aufgehoben, auf Warteliste gesetzt')), $message); - if ($course->addToWaitlist($user_id, $which_end)) { - $msgs['success'][] = $temp_user->getFullName('no_title'); - } else { - $msgs['error'][] = $temp_user->getFullName('no_title'); - } - // Something went wrong on inserting the user in waitlist. - } else { - $msgs['error'][] = $temp_user->getFullName('no_title'); - } - } - return $msgs; - } - /** * Get the position out of the database * @param String $user_id diff --git a/app/controllers/course/overview.php b/app/controllers/course/overview.php index 876de5ada18f4a17a4d91e33633726766e967402..e1da00e303f194ffacb2bb459dc81eda1f17eaa2 100644 --- a/app/controllers/course/overview.php +++ b/app/controllers/course/overview.php @@ -29,10 +29,6 @@ class Course_OverviewController extends AuthenticatedController PageLayout::setHelpKeyword('Basis.InVeranstaltungKurzinfo'); PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Kurzinfo')); Navigation::activateItem('/course/main/info'); - - $this->sem = Seminar::getInstance($this->course_id); - $sem_class = $this->sem->getSemClass(); - $this->studygroup_mode = $sem_class['studygroup_mode']; } /** @@ -63,26 +59,27 @@ class Course_OverviewController extends AuthenticatedController } - if (!$this->studygroup_mode) { + if (!$this->course->isStudygroup()) { $this->avatar = CourseAvatar::getAvatar($this->course_id); // Fetch dates $response = $this->relay("calendar/contentbox/display/{$this->course_id}/1210000"); $this->dates = $response->body; - $this->next_date = $this->sem->getNextDate(); - $this->first_date = $this->sem->getFirstDate(); + $this->next_date = $this->course->getNextDate(); + $this->first_date = $this->course->getFirstDate(); $show_link = $GLOBALS["perm"]->have_studip_perm('autor', $this->course_id) && $this->course->isToolActive('schedule'); - $this->times_rooms = $this->sem->getDatesTemplate('dates/seminar_html', ['link_to_dates' => $show_link, 'show_room' => true]); - - // Fettch teachers - $dozenten = $this->sem->getMembers('dozent'); - $this->num_dozenten = count($dozenten); - $show_dozenten = []; - foreach ($dozenten as $dozent) { - $show_dozenten[] = '<a href="' . URLHelper::getLink('dispatch.php/profile', ['username' => $dozent['username']]) . '">' - . htmlready($this->num_dozenten > 10 ? get_fullname($dozent['user_id'], 'no_title_short') : $dozent['fullname']) - . '</a>'; + $this->times_rooms = implode('<br>', $this->course->getAllDatesInSemester()->toStringArray()); + + //Load lecturers: + $lecturers = $this->course->getMembersWithStatus('dozent'); + $this->num_lecturers = count($lecturers); + $this->lecturer_html = []; + foreach ($lecturers as $lecturer) { + $this->lecturer_html[] = sprintf( + '<a href="%s">%s</a>', + URLHelper::getLink('dispatch.php/profile', ['username' => $lecturer->user->username], true), + htmlReady($lecturer->user->getFullName($this->num_lecturers > 10 ? 'no_title_short' : 'default')) + ); } - $this->show_dozenten = $show_dozenten; // Check lock rules if (!$GLOBALS['perm']->have_studip_perm('dozent', $this->course_id)) { @@ -107,7 +104,7 @@ class Course_OverviewController extends AuthenticatedController } } } else { - $this->all_mods = $this->sem->getMembers('dozent') + $this->sem->getMembers('tutor'); + $this->all_mods = $this->course->getMembersWithStatus(['dozent', 'tutor']); $this->avatar = StudygroupAvatar::getAvatar($this->course_id); } @@ -129,7 +126,7 @@ class Course_OverviewController extends AuthenticatedController } $share = new ShareWidget(); - if ($this->studygroup_mode) { + if ($this->course->isStudygroup()) { $share->addCopyableLink( _('Link zu dieser Studiengruppe kopieren'), $this->url_for('course/studygroup/details/' . $this->course->id, [ diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php index 9a137384f5a3b04f4d37987862e8142fa18a9f4a..dc282ff4f3f33edb34f30b2e2a8270ffc52590e3 100644 --- a/app/controllers/course/statusgroups.php +++ b/app/controllers/course/statusgroups.php @@ -890,8 +890,6 @@ class Course_StatusgroupsController extends AuthenticatedController SeminarCycleDate::findBySeminar_id($this->course_id)); foreach ($cycles as $c) { - $cd = new CycleData($c); - $name = $c->toString(); // Append description to group title if applicable. @@ -900,15 +898,10 @@ class Course_StatusgroupsController extends AuthenticatedController } // Get name of most used room and append to group title. - if ($rooms = $cd->getPredominantRoom()) { - $room_keys = array_keys($rooms); - $room_name = DBManager::get()->fetchOne( - "SELECT `name` FROM `resources` WHERE `id` = ?", - [array_pop($room_keys)]); - $name .= ' (' . $room_name['name'] . ')'; + if ($room = $c->getMostBookedRoom()) { + $name .= ' (' . $room->name . ')'; } else { - $free_text_predominant_rooms = array_keys($cd->getFreeTextPredominantRoom()); - $room = trim(array_pop($free_text_predominant_rooms)); + $room = $c->getMostBookedFreetextRoom(); if ($room) { $name .= ' (' . $room . ')'; } @@ -1439,14 +1432,42 @@ class Course_StatusgroupsController extends AuthenticatedController CSRFProtection::verifyUnsafeRequest(); $members = Request::getArray('members'); - $course = Seminar::GetInstance($this->course_id); - $removed_names = $course->cancelSubscription($members); - - PageLayout::postSuccess( - _('Die folgenden Personen wurden aus der Veranstaltung ausgetragen'), - $removed_names + $course = Course::find($this->course_id); + $removed_users = []; + $errors = []; + User::findEachMany( + function (User $user) use ($course, &$errors, &$removed_users) { + try { + $course->deleteMember($user, true); + $removed_users = $user->getFullName(); + } catch (Exception $e) { + $errors[] = $e->getMessage(); + } + + }, + array_column($members, 'user_id') ); + if (!empty($errors)) { + PageLayout::postError( + _('Die folgenden Fehler traten beim Austragen von Personen auf:'), + $errors + ); + } + if (!empty($removed_users)) { + if (count($removed_users) <= 5) { + PageLayout::postSuccess( + _('Die folgenden Personen wurden ausgetragen:'), + $removed_users + ); + } else { + PageLayout::postSuccess( + _('%u Personen wurden ausgetragen.'), + count($removed_users) + ); + } + } + $this->relocate('course/statusgroups'); } diff --git a/app/controllers/course/studygroup.php b/app/controllers/course/studygroup.php index 1f9c4a48405cc8d812ad7eeae57ad15c29822092..f164b5707989a4967fe86e036e86a3cbd7ec2a21 100644 --- a/app/controllers/course/studygroup.php +++ b/app/controllers/course/studygroup.php @@ -75,7 +75,7 @@ class Course_StudygroupController extends AuthenticatedController $id = Context::getId(); } - $studygroup = new Seminar($id); + $studygroup = Course::find($id); $this->sidebarActions = []; if (Request::isXhr()) { PageLayout::setTitle(_('Studiengruppendetails')); @@ -365,15 +365,14 @@ class Course_StudygroupController extends AuthenticatedController // if we are permitted to edit the studygroup get some data... if ($id && $perm->have_studip_perm('dozent', $id)) { - $sem = Seminar::getInstance($id); + $this->course = Course::find($id); PageLayout::setTitle(Context::getHeaderLine() . ' - ' . _('Studiengruppe bearbeiten')); Navigation::activateItem('/course/admin/main'); - $this->sem_id = $id; - $this->sem = $sem; - $this->sem_class = $GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$sem->status]['class']]; - $this->tutors = $sem->getMembers('tutor'); + $this->course_id = $id; + $this->sem_class = $GLOBALS['SEM_CLASS'][$GLOBALS['SEM_TYPE'][$this->course->status]['class']]; + $this->tutors = CourseMember::findByCourseAndStatus($this->course->id, 'tutor'); $this->founders = StudygroupModel::getFounders($id); $actions = new ActionsWidget(); @@ -775,8 +774,8 @@ class Course_StudygroupController extends AuthenticatedController // save invite in database StudygroupModel::inviteMember($receiver, $id); // send invite message to user - $msg = new messaging(); - $sem = new Seminar($id); + $msg = new messaging(); + $sem = Course::find($id); $message = sprintf(_("%s möchte Sie auf die Studiengruppe %s aufmerksam machen. Klicken Sie auf den folgenden Link, um direkt zur Studiengruppe zu gelangen.\n\n %s"), get_fullname(), $sem->name, URLHelper::getlink("dispatch.php/course/studygroup/details/" . $id, ['cid' => null])); $subject = _("Sie wurden in eine Studiengruppe eingeladen"); @@ -823,14 +822,10 @@ class Course_StudygroupController extends AuthenticatedController if ($perm->have_studip_perm('dozent', $id)) { if ($approveDelete && check_ticket(Request::get('studip_ticket'))) { - $messages = []; - $sem = new Seminar($id); - $sem->delete(); - if ($messages = $sem->getStackedMessages()) { - $this->flash['messages'] = $messages; + $course = Course::find($id); + if (!$course->delete()) { + PageLayout::postError(_('Die Studiengruppe konnte nicht gelöscht werden.')); } - unset($sem); - $this->redirect(URLHelper::getURL('dispatch.php/studygroup/browse', [], true)); return; } elseif (!$approveDelete) { diff --git a/app/controllers/course/timesrooms.php b/app/controllers/course/timesrooms.php index bbc7196135be9af294f8df1d66516865f51da1b6..5eab38ab5e8f9093dab367a22328e86002513a2c 100644 --- a/app/controllers/course/timesrooms.php +++ b/app/controllers/course/timesrooms.php @@ -31,7 +31,7 @@ class Course_TimesroomsController extends AuthenticatedController } // Get seminar instance - $this->course = new Seminar(Course::findCurrent()); + $this->course = Course::findCurrent(); if (Navigation::hasItem('course/admin/dates')) { Navigation::activateItem('course/admin/dates'); @@ -60,29 +60,41 @@ class Course_TimesroomsController extends AuthenticatedController PageLayout::setTitle($title); + $dates_in_time_range = CourseDate::countBySql( + "`range_id` = :course_id AND `date` BETWEEN :beginning AND :end", + [ + 'course_id' => $this->course->id, + 'beginning' => $this->course->start_semester->beginn, + 'end' => $this->course->end_semester->vorles_ende + ] + ) > 0; URLHelper::bindLinkParam('semester_filter', $this->semester_filter); if (empty($this->semester_filter)) { - if (!$this->course->hasDatesOutOfDuration() && count($this->course->semesters) == 1) { + if ($dates_in_time_range && 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())) { + + $dates_outside_of_time_range = CourseDate::countBySql( + "`range_id` = :course_id AND `date` NOT BETWEEN :beginning AND :end", + [ + 'course_id' => $this->course->id, + 'beginning' => $this->course->start_semester->beginn, + 'end' => $this->course->end_semester->vorles_ende + ] + ) > 0; + + if (count($selectable_semesters) > 1 || (count($selectable_semesters) == 1 && $dates_outside_of_time_range)) { $selectable_semesters[] = ['name' => _('Alle Semester'), 'semester_id' => 'all']; } $this->selectable_semesters = array_reverse($selectable_semesters); @@ -91,14 +103,20 @@ class Course_TimesroomsController extends AuthenticatedController $this->setSidebar(); } elseif (Request::isXhr() && $this->flash['update-times']) { $semester_id = $GLOBALS['user']->cfg->MY_COURSES_SELECTED_CYCLE ?? ''; + $semester = null; + if ($this->semester_filter !== 'all') { + $semester = Semester::find($this->semester_filter); + if (!$semester && $semester_id) { + $semester = Semester::find($this->semester_id); + } + } + + $dates = $this->course->getAllDatesInSemester($semester, $semester); $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'), + 'html' => $dates->toHtml(false, true), ]) ); } @@ -243,7 +261,7 @@ class Course_TimesroomsController extends AuthenticatedController unset($_SESSION['_checked_dates']); } if (Request::isDialog()) { - $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "'.$this->course->getId().'"}'); + $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "' . $this->course->id . '"}'); } } @@ -291,15 +309,16 @@ class Course_TimesroomsController extends AuthenticatedController $course->semesters = $selected_semesters; } - // set the semester-chooser to the first semester - $this->course->setFilter($course->getStartSemester()); + //Set the semester selector to the first semester: $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); + $start = $this->course->start_semester->beginn; + $end = $this->course->end_semester->vorles_ende; + SeminarCycleDate::removeOutRangedSingleDates($start, $end, $course->id); $cycles = SeminarCycleDate::findBySeminar_id($course->id); foreach ($cycles as $cycle) { $cycle->end_offset = $this->getNewEndOffset($cycle, $old_start_weeks, $new_start_weeks); @@ -308,14 +327,10 @@ class Course_TimesroomsController extends AuthenticatedController } } - $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().'"}'); + $this->response->add_header('X-Dialog-Execute', '{"func": "STUDIP.AdminCourses.App.loadCourse", "payload": "' . $this->course->id . '"}'); } } } @@ -331,6 +346,12 @@ class Course_TimesroomsController extends AuthenticatedController $this->date = CourseDate::find($termin_id) ?: CourseExDate::find($termin_id); $this->attributes = []; + $request = RoomRequest::findByDate($this->date->id); + if ($request) { + $this->params = ['request_id' => $request->id]; + } else { + $this->params = ['new_room_request_type' => 'date_' . $this->date->id]; + } $this->only_bookable_rooms = Request::submitted('only_bookable_rooms'); if (Config::get()->RESOURCES_ENABLE) { @@ -427,29 +448,37 @@ class Course_TimesroomsController extends AuthenticatedController } $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) - )); + if ($time_changed) { + if ($termin->metadate_id != '') { + //time changed for regular date. create normal singledate and cancel the regular date + $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,' + . ' sodass 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 = new CourseDate(); + unset($termin_values['metadate_id']); + $termin->setData($termin_values); + $termin->date = $date; + $termin->end_time = $end_time; + $termin->setId($termin->getNewId()); + } else { + //Time changed for single date. + $termin->date = $date; + $termin->end_time = $end_time; + } } $termin->date_typ = Request::get('course_type'); // Set assigned teachers $assigned_teachers = Request::optionArray('assigned_teachers'); - $dozenten = $this->course->getMembers(); + $dozenten = $this->course->getMembersWithStatus('dozent'); 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. @@ -471,15 +500,7 @@ class Course_TimesroomsController extends AuthenticatedController } // 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(); - } + $old_room_id = $termin->room_booking->resource_id ?? ''; if ((Request::option('room') == 'room') || Request::option('room') == 'nochange') { //Storing the SingleDate above has deleted the room booking @@ -500,57 +521,61 @@ class Course_TimesroomsController extends AuthenticatedController } } 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>' - ) - ); + $room = Resource::find($room_id)?->getDerivedClassInstance(); + if ($room_id !== $old_room_id) { + $failure = false; + if ($room instanceof Room) { + try { + $failure = !$termin->bookRoom($room, $preparation_time ?: 0); + } catch (ResourceBookingException|ResourceBookingOverlapException $e) { + PageLayout::postError(sprintf( + _('Der angegebene Raum konnte für den Termin %1$s nicht gebucht werden: %2$s'), + '<strong>' . htmlReady($termin->getFullName()) . '</strong>', + $e->getMessage() + )); + } } - } elseif ($termin->room_booking->preparation_time != ($preparation_time * 60)) { - $singledate->bookRoom($room_id, $preparation_time ?: 0); + if ($failure) { + PageLayout::postError(sprintf( + _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'), + '<strong>' . htmlReady($termin->getFullName()) . '</strong>' + )); + } + } elseif ($room instanceof Room && $termin->room_booking->preparation_time != ($preparation_time * 60)) { + $termin->bookRoom($room, $preparation_time ?: 0); } - } else if ($old_room_id && !$singledate->resource_id) { - $this->course->createInfo( + } else if ($old_room_id && empty($termin->room_booking->resource_id)) { + PageLayout::postInfo( sprintf( _('Die Raumbuchung für den Termin %s wurde aufgehoben, da die neuen Zeiten außerhalb der alten liegen!'), - '<strong>'.htmlReady( $singledate->toString()) .'</strong>' + '<strong>'.htmlReady($termin->getFullName()) .'</strong>' )); } else if (Request::get('room_id_parameter')) { - $this->course->createInfo( + PageLayout::postInfo( _('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>' + $termin->raum = Request::get('freeRoomText_sd'); + if ($termin->room_booking) { + $termin->room_booking->delete(); + } + $termin->store(); + PageLayout::postSuccess(sprintf( + _('Der Termin %s wurde geändert, Raumbuchungen zu diesem Termin wurden entfernt und stattdessen der angegebene Freitext eingetragen!'), + '<strong>' . htmlReady($termin->getFullName()) . '</strong>' )); } 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>' + $termin->raum = ''; + if ($termin->room_booking) { + $termin->room_booking->delete(); + } + $termin->store(); + PageLayout::postSuccess(sprintf( + _('Der Termin %s wurde geändert, freie Ortsangaben und Raumbuchungen an diesem Termin wurden entfernt.'), + '<strong>' . htmlReady($termin) . '</strong>' )); } - if (!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]); } @@ -569,7 +594,7 @@ class Course_TimesroomsController extends AuthenticatedController if (Config::get()->RESOURCES_ENABLE) { $this->setAvailableRooms(null); } - $this->teachers = $this->course->getMembers('dozent'); + $this->teachers = $this->course->getMembersWithStatus('dozent'); $this->groups = Statusgruppen::findBySeminar_id($this->course->id); } @@ -633,12 +658,13 @@ class Course_TimesroomsController extends AuthenticatedController $termin->store(); } else { $termin->store(); - $singledate = new SingleDate($termin); - $singledate->bookRoom(Request::option('room_id')); - $this->course->appendMessages($singledate->getMessages()); + $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance(); + if ($room instanceof Room) { + $termin->bookRoom($room); + } } if (Request::get('room_id_parameter')) { - $this->course->createInfo( + PageLayout::postInfo( _('Um eine Raumbuchung durchzuführen, müssen Sie einen Raum aus dem Suchergebnis auswählen!') ); } @@ -646,13 +672,8 @@ class Course_TimesroomsController extends AuthenticatedController // 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()))); + PageLayout::postSuccess(sprintf(_('Der Termin %s wurde hinzugefügt!'), htmlReady($termin->getFullname()))); $this->course->store(); - $this->displayMessages(); $this->relocate('course/timesrooms/index'); } @@ -667,11 +688,10 @@ class Course_TimesroomsController extends AuthenticatedController $ex_termin = CourseExDate::find($termin_id); $termin = $ex_termin->unCancelDate(); if ($termin) { - $this->course->createMessage(sprintf( + PageLayout::postSuccess(sprintf( _('Der Termin %s wurde wiederhergestellt!'), htmlReady($termin->getFullName()) )); - $this->displayMessages(); } if ($from_dates) { @@ -733,7 +753,7 @@ class Course_TimesroomsController extends AuthenticatedController private function editStack($cycle_id) { $this->cycle_id = $cycle_id; - $this->teachers = $this->course->getMembers(); + $this->teachers = $this->course->getMembersWithStatus('dozent'); $this->gruppen = Statusgruppen::findBySeminar_id($this->course->id); $checked_course_dates = CourseDate::findMany($_SESSION['_checked_dates']); $this->only_bookable_rooms = Request::submitted('only_bookable_rooms'); @@ -876,13 +896,12 @@ class Course_TimesroomsController extends AuthenticatedController $ex_termin->content = ''; $termin = $ex_termin->unCancelDate(); if ($termin !== null) { - $this->course->createMessage(sprintf( + PageLayout::postSuccess(sprintf( _('Der Termin %s wurde wiederhergestellt!'), htmlReady($termin->getFullName()) )); } } - $this->displayMessages(); $this->relocate('course/timesrooms/index', ['contentbox_open' => $cycle_id]); } @@ -911,8 +930,6 @@ class Course_TimesroomsController extends AuthenticatedController $this->saveRequestStack(); } - $this->displayMessages(); - $this->relocate('course/timesrooms/index', ['contentbox_open' => $cycle_id]); } @@ -937,7 +954,7 @@ class Course_TimesroomsController extends AuthenticatedController 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.')); + PageLayout::postSuccess(_('Alle Teilnehmenden wurden benachrichtigt.')); } } } @@ -969,7 +986,7 @@ class Course_TimesroomsController extends AuthenticatedController // Update related persons if (in_array($action, words('add delete'))) { - $course_lectures = $this->course->getMembers(); + $course_lectures = $this->course->getMembersWithStatus('dozent'); $persons = User::findMany($persons); foreach ($singledates as $singledate) { if ($action === 'add') { @@ -1000,7 +1017,7 @@ class Course_TimesroomsController extends AuthenticatedController } if ($lecture_changed) { - $this->course->createMessage(_('Zuständige Personen für die Termine wurden geändert.')); + PageLayout::postSuccess(_('Die zuständigen Personen für die Termine wurden geändert.')); } if (in_array($group_action, words('add delete'))) { @@ -1034,72 +1051,95 @@ class Course_TimesroomsController extends AuthenticatedController } if ($groups_changed) { - $this->course->createMessage(_('Zugewiesene Gruppen für die Termine wurden geändert.')); + PageLayout::postSuccess(_('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); + $errors = []; + foreach ($singledates as $singledate) { + if ($singledate instanceof CourseExDate) { + continue; + } if (Request::option('action') == 'room') { $preparation_time = Request::get('preparation_time'); $max_preparation_time = Config::get()->RESOURCES_MAX_PREPARATION_TIME; if ($preparation_time > $max_preparation_time) { - $this->course->createError( - sprintf( - _('Die eingegebene Rüstzeit überschreitet das erlaubte Maximum von %d Minuten!'), - $max_preparation_time - ) + $errors[] = sprintf( + studip_interpolate( + _('%{date}: Die eingegebene Rüstzeit überschreitet das erlaubte Maximum von %d Minuten!'), + ['date' => $singledate->getFullName()] + ), + $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( + $room = Resource::find(Request::option('room_id'))?->getDerivedClassInstance(); + if ($room instanceof Room) { + $failure = false; + try { + $failure = !$singledate->bookRoom($room, intval($preparation_time)); + } catch (ResourceBookingException|ResourceBookingOverlapException $e) { + $errors[] = sprintf( + _('Der angegebene Raum konnte für den Termin %1$s nicht gebucht werden: %2$s'), + '<strong>' . htmlReady($singledate->getFullName()) . '</strong>', + $e->getMessage() + ); + } + if ($failure) { + $errors[] = sprintf( _('Der angegebene Raum konnte für den Termin %s nicht gebucht werden!'), - '<strong>' . htmlReady($date->toString() ) . '</strong>') + '<strong>' . htmlReady($singledate->getFullName()) . '</strong>' ); + } else { + PageLayout::postSuccess(sprintf( + _('Die Änderungen am Termin %s wurden gespeichert.'), + $singledate->getFullName() + )); } - } 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( + PageLayout::postInfo( ('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( + $singledate->raum = Request::get('freeRoomText'); + $singledate->store(); + if ($singledate->room_booking instanceof ResourceBooking) { + $singledate->room_booking->delete(); + } + PageLayout::postSuccess(sprintf( _('Der Termin %s wurde geändert, etwaige Raumbuchungen wurden entfernt und stattdessen der angegebene Freitext eingetragen!'), - '<strong>' . htmlReady($date->toString()) . '</strong>' + '<strong>' . htmlReady($singledate->getFullName()) . '</strong>' )); } elseif (Request::option('action') == 'noroom') { - $date->setFreeRoomText(''); - $date->store(); - $date->killAssign(); - $this->course->createMessage(sprintf( + $singledate->raum = ''; + $singledate->store(); + if ($singledate->room_booking instanceof ResourceBooking) { + $singledate->room_booking->delete(); + } + PageLayout::postSuccess(sprintf( _('Der Termin %s wurde geändert, etwaige freie Ortsangaben und Raumbuchungen wurden entfernt.'), - '<strong>' . htmlReady($date->toString()) . '</strong>' + '<strong>' . htmlReady($singledate) . '</strong>' )); } if (Request::get('course_type') != '') { - $date->setDateType(Request::get('course_type')); - $date->store(); - $this->course->createMessage(sprintf( + $singledate->date_typ = Request::get('course_type'); + $singledate->store(); + PageLayout::postSuccess(sprintf( _('Die Art des Termins %s wurde geändert.'), - '<strong>' . htmlReady($date->toString()) . '</strong>' + '<strong>' . htmlReady($singledate) . '</strong>' )); } } + if ($errors) { + PageLayout::postError( + _('Die folgenden Fehler traten auf:'), + $errors + ); + } } } @@ -1134,21 +1174,17 @@ class Course_TimesroomsController extends AuthenticatedController } if (!$appointments) { - $this->course->createError( - _('Es wurden keine gültigen Termin-IDs übergeben!') - ); + PageLayout::postError(_('Es wurden keine gültigen Termine übergeben!')); return; } $request = new RoomRequest(); - $request->course_id = $this->course->getId(); + $request->course_id = $this->course->id; $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!') - ); + PageLayout::postError(_('Fehler beim Speichern der Anfrage!')); return; } @@ -1162,9 +1198,7 @@ class Course_TimesroomsController extends AuthenticatedController } if ($set_properties && $successfully_stored < count($set_properties)) { - $this->course->createError( - _('Es wurden nicht alle zur Anfrage gehörenden Eigenschaften gespeichert!') - ); + PageLayout::postError(_('Es wurden nicht alle zur Anfrage gehörenden Eigenschaften gespeichert!')); } //Finally we can create ResourceRequestAppointment @@ -1181,15 +1215,11 @@ class Course_TimesroomsController extends AuthenticatedController } if (($successfully_stored < count($appointments)) && count($appointments)) { - $this->course->createError( - _('Es wurden nicht alle zur Anfrage gehörenden Terminzuordnungen gespeichert!') - ); + PageLayout::postError(_('Es wurden nicht alle zur Anfrage gehörenden Terminzuordnungen gespeichert!')); return; } - $this->course->createMessage( - _('Die Raumanfrage wurde gespeichert!') - ); + PageLayout::postSuccess(_('Die Raumanfrage wurde gespeichert!')); } @@ -1200,7 +1230,7 @@ class Course_TimesroomsController extends AuthenticatedController */ public function createCycle_action($cycle_id = null) { - PageLayout::setTitle(Course::findCurrent()->getFullName() . " - " . _('Regelmäßige Termine anlegen')); + 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') ); @@ -1327,18 +1357,16 @@ class Course_TimesroomsController extends AuthenticatedController $cycle_info = $cycle->toString(); NotificationCenter::postNotification('CourseDidChangeSchedule', $this->course); - $this->course->createMessage(sprintf( + PageLayout::postSuccess(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( + PageLayout::postError( _('Die regelmäßige Veranstaltungszeit konnte nicht hinzugefügt werden! Bitte überprüfen Sie Ihre Eingabe.') ); - $this->displayMessages(); $this->redirect('course/timesrooms/createCycle'); } } @@ -1466,29 +1494,28 @@ class Course_TimesroomsController extends AuthenticatedController 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()) + PageLayout::postSuccess(sprintf( + _('Der Kommentar des gelöschten Termins %s wurde geändert.'), + htmlReady($termin->getFullname()) )); } else { - $this->course->createInfo(sprintf( + PageLayout::postInfo(sprintf( _('Der gelöschte Termin %s wurde nicht verändert.'), - htmlReady($termin->getFullName()) + htmlReady($termin->getFullname()) )); } } else { - $this->course->createInfo(sprintf( + PageLayout::postInfo(sprintf( _('Der gelöschte Termin %s wurde nicht verändert.'), - htmlReady($termin->getFullName()) + 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.')); + PageLayout::postInfo(_('Alle Teilnehmenden wurden benachrichtigt.')); } } - $this->displayMessages(); $this->redirect('course/timesrooms/index', ['contentbox_open' => $termin->metadate_id]); } @@ -1521,10 +1548,23 @@ class Course_TimesroomsController extends AuthenticatedController } Sidebar::Get()->addWidget($widget); + if ($GLOBALS['perm']->have_perm('admin')) { + $list = new SelectWidget( + _('Veranstaltungen'), + $this->indexURL(), + 'cid' + ); - if ($GLOBALS['perm']->have_studip_perm('admin', $this->course->id)) { - $widget = new CourseManagementSelectWidget(); - Sidebar::get()->addWidget($widget); + foreach (AdminCourseFilter::get()->getCoursesForAdminWidget() as $seminar) { + $list->addElement(new SelectElement( + $seminar['Seminar_id'], + $seminar['Name'], + $seminar['Seminar_id'] === Context::getId(), + $seminar['VeranstaltungsNummer'] . ' ' . $seminar['Name'] + )); + } + $list->size = 8; + Sidebar::Get()->addWidget($list); } } @@ -1558,20 +1598,6 @@ class Course_TimesroomsController extends AuthenticatedController 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. * @@ -1602,23 +1628,32 @@ class Course_TimesroomsController extends AuthenticatedController } 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>' - )); + PageLayout::postSuccess( + sprintf( + _('Dem Termin %s war ein Thema zugeordnet. Sie können das Thema im Ablaufplan einem anderen Termin (z.B. einem Ausweichtermin) zuordnen.'), + htmlReady($termin_date) + ), + [ + sprintf( + '<a href="%s">%s</a>', + URLHelper::getLink('dispatch.php/course/topics'), + _('Zum Ablaufplan') + ) + ] + ); } 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) - )); + PageLayout::postSuccess( + studip_interpolate( + _('Der Termin %{date} wurde gelöscht! Die Buchung für den Raum %{room} wurde gelöscht.'), + [ + 'date' => htmlReady($termin_date), + 'room' => htmlReady($termin_room) + ] + ) + ); } else { - $this->course->createMessage(sprintf( - _('Der Termin %s wurde gelöscht!'), - htmlReady($termin_date) - )); + PageLayout::postSuccess(_('Der Termin %s wurde gelöscht!'), htmlReady($termin_date)); } return $termin; @@ -1657,9 +1692,9 @@ class Course_TimesroomsController extends AuthenticatedController /** * Restores a previously stored request from trails' flash object */ - private function restoreRequest(array $fields, $request = null) + private function restoreRequest(array $fields) { - $request = $this->flash['request'] ?? $request; + $request = $this->flash['request']; if ($request) { foreach ($fields as $field) { @@ -1719,7 +1754,7 @@ class Course_TimesroomsController extends AuthenticatedController } else { $user_rooms = RoomManager::getUserRooms($current_user); foreach ($user_rooms as $room) { - if ($room->userHasBookingRights($current_user, $begin ?? null, $end ?? null)) { + if ($room->userHasBookingRights($current_user, $begin, $end)) { $rooms_with_booking_permissions++; if ($only_bookable_rooms) { foreach ($all_time_intervals as $interval) { diff --git a/app/controllers/course/topics.php b/app/controllers/course/topics.php index 2d9749058b19291bd996c2b10ccb5b84355bb404..36e25ecd851f551c8b0d25ecc2f6349a5b853ba6 100644 --- a/app/controllers/course/topics.php +++ b/app/controllers/course/topics.php @@ -17,9 +17,9 @@ class Course_TopicsController extends AuthenticatedController Navigation::activateItem('/course/schedule/topics'); PageLayout::setTitle(sprintf('%s - %s', Course::findCurrent()->getFullName(), _("Themen"))); - $seminar = new Seminar(Course::findCurrent()); - $this->forum_activated = $seminar->getSlotModule('forum'); - $this->documents_activated = $seminar->getSlotModule('documents'); + $course = Course::findCurrent(); + $this->forum_activated = $course->isToolActive(CoreForum::class); + $this->documents_activated = $course->isToolActive(CoreDocuments::class); if ($action !== 'index' && !$GLOBALS['perm']->have_studip_perm('tutor', Context::getId())) { throw new AccessDeniedException(); diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php index e17fc91b38b9c1bc1711cb5c9356010d231366a3..d21f2d508f2b0a1d249061097f8240ac79205be0 100644 --- a/app/controllers/my_courses.php +++ b/app/controllers/my_courses.php @@ -377,13 +377,13 @@ class MyCoursesController extends AuthenticatedController */ public function decline_action($course_id, $waiting = null) { - $current_seminar = Seminar::getInstance($course_id); + $course = Course::find($course_id); $ticket_check = Seminar_Session::check_ticket(Request::option('studipticket')); if (LockRules::Check($course_id, 'participants')) { $lockdata = LockRules::getObjectRule($course_id); PageLayout::postError(sprintf( _('Sie können sich nicht von der Veranstaltung <b>%s</b> abmelden.'), - htmlReady($current_seminar->name) + htmlReady($course->name) )); if ($lockdata['description']) { PageLayout::postInfo(formatLinks($lockdata['description'])); @@ -393,7 +393,6 @@ class MyCoursesController extends AuthenticatedController } // Ensure last teacher cannot leave course - $course = Course::find($course_id); $teacher = $course->members->findOneBy('user_id', User::findCurrent()->id); if ( $teacher @@ -402,7 +401,7 @@ class MyCoursesController extends AuthenticatedController ) { PageLayout::postError(sprintf( _('Sie können sich nicht von der Veranstaltung <b>%s</b> abmelden.'), - htmlReady($current_seminar->name) + htmlReady($course->name) )); $this->redirect('my_courses/index'); return; @@ -414,45 +413,49 @@ class MyCoursesController extends AuthenticatedController } if (Request::option('cmd') != 'kill' && Request::option('cmd') != 'kill_admission') { - if ($current_seminar->admission_binding && Request::get('cmd') != 'suppose_to_kill_admission' && !LockRules::Check($current_seminar->getId(), 'participants')) { + if ( + $course->admission_binding + && Request::get('cmd') != 'suppose_to_kill_admission' + && !LockRules::Check($course->id, 'participants') + ) { PageLayout::postError(sprintf(_("Die Veranstaltung <b>%s</b> ist als <b>bindend</b> angelegt. Wenn Sie sich abmelden wollen, müssen Sie sich an die Lehrende der Veranstaltung wenden."), - htmlReady($current_seminar->name))); + htmlReady($course->name))); $this->redirect('my_courses/index'); return; } if (Request::get('cmd') == 'suppose_to_kill') { // check course admission - list(,$admission_end_time) = @array_values($current_seminar->getAdmissionTimeFrame()); + list(,$admission_end_time) = @array_values($course->getAdmissionTimeFrame()); - $admission_enabled = $current_seminar->isAdmissionEnabled(); - $admission_locked = $current_seminar->isAdmissionLocked(); + $admission_enabled = $course->isAdmissionEnabled(); + $admission_locked = $course->isAdmissionLocked(); - if ($admission_enabled || $admission_locked || (int)$current_seminar->admission_prelim == 1) { + if ($admission_enabled || $admission_locked || (int) $course->admission_prelim === 1) { $message = sprintf( _('Wollen Sie sich von der teilnahmebeschränkten Veranstaltung "%s" wirklich abmelden? Sie verlieren damit die Berechtigung für die Veranstaltung und müssen sich ggf. neu anmelden!'), - htmlReady($current_seminar->name) + htmlReady($course->name) ); } else if (isset($admission_end_time) && $admission_end_time < time()) { $message = sprintf( _('Wollen Sie sich von der teilnahmebeschränkten Veranstaltung "%s" wirklich abmelden? Der Anmeldezeitraum ist abgelaufen und Sie können sich nicht wieder anmelden!'), - htmlReady($current_seminar->name) + htmlReady($course->name) ); } else { - $message = sprintf(_('Wollen Sie sich von der Veranstaltung "%s" wirklich abmelden?'), htmlReady($current_seminar->name)); + $message = sprintf(_('Wollen Sie sich von der Veranstaltung "%s" wirklich abmelden?'), htmlReady($course->name)); } $cmd = 'kill'; } else { if (AdmissionApplication::checkMemberPosition($GLOBALS['user']->id, $course_id) === false) { $message = sprintf( _('Wollen Sie sich von der Anmeldeliste der Veranstaltung "%s" wirklich abmelden?'), - htmlReady($current_seminar->name) + htmlReady($course->name) ); } else { $message = sprintf( _('Wollen Sie sich von der Warteliste der Veranstaltung "%s" wirklich abmelden? Sie verlieren damit die bereits erreichte Position und müssen sich ggf. neu anmelden!'), - htmlReady($current_seminar->name) + htmlReady($course->name) ); } $cmd = 'kill_admission'; @@ -489,13 +492,13 @@ class MyCoursesController extends AuthenticatedController AdmissionApplication::addMembers($course_id); // If this course is a child of another course... - if ($current_seminar->parent_course) { + if ($course->parent) { // ... check if user is member in another sibling ... $other = CourseMember::findBySQL( "`user_id` = :user AND `Seminar_id` IN (:courses) AND `Seminar_id` != :this", [ 'user' => $GLOBALS['user']->id, - 'courses' => $current_seminar->parent->children->pluck('seminar_id'), + 'courses' => $course->parent->children->pluck('seminar_id'), 'this' => $course_id ] ); @@ -503,7 +506,7 @@ class MyCoursesController extends AuthenticatedController // ... and delete from parent course if this was the only // course membership in this family. if (count($other) == 0) { - $m = CourseMember::find([$current_seminar->parent_course, $GLOBALS['user']->id]); + $m = CourseMember::find([$course->parent_course, $GLOBALS['user']->id]); if ($m) { $m->delete(); } @@ -512,14 +515,14 @@ class MyCoursesController extends AuthenticatedController PageLayout::postSuccess(sprintf( _("Erfolgreich von Veranstaltung <b>%s</b> abgemeldet."), - htmlReady($current_seminar->name) + htmlReady($course->name) )); } } else { // LOGGING StudipLog::log('SEM_USER_DEL', $course_id, $GLOBALS['user']->id, 'Hat sich selbst aus der Warteliste ausgetragen'); - if ($current_seminar->isAdmissionEnabled()) { - $prio_delete = AdmissionPriority::unsetPriority($current_seminar->getCourseSet()->getId(), $GLOBALS['user']->id, $course_id); + if ($course->isAdmissionEnabled()) { + $prio_delete = AdmissionPriority::unsetPriority($course->getCourseSet()->getId(), $GLOBALS['user']->id, $course_id); } NotificationCenter::postNotification('UserDidLeaveWaitingList', $course_id, $GLOBALS['user']->id); $deleted = AdmissionApplication::deleteBySQL( @@ -533,7 +536,7 @@ class MyCoursesController extends AuthenticatedController AdmissionApplication::addMembers($course_id); PageLayout::postSuccess(sprintf( _("Der Eintrag in der Anmelde- bzw. Warteliste der Veranstaltung <b>%s</b> wurde aufgehoben. Wenn Sie an der Veranstaltung teilnehmen wollen, müssen Sie sich erneut bewerben."), - htmlReady($current_seminar->name) + htmlReady($course->name) )); } } diff --git a/app/controllers/tree.php b/app/controllers/tree.php index 655e7170d2052d99184566e79bfc5b70d2476d10..e87f1344689384d80949c70580f093cff6c2e009 100644 --- a/app/controllers/tree.php +++ b/app/controllers/tree.php @@ -21,7 +21,6 @@ class TreeController extends AuthenticatedController $data = []; foreach ($courses as $course) { - $sem = Seminar::getInstance($course->id); $lecturers = SimpleCollection::createFromArray( CourseMember::findByCourseAndStatus($course->id, 'dozent') )->orderBy('position, nachname, vorname'); @@ -37,7 +36,7 @@ class TreeController extends AuthenticatedController $course->veranstaltungsnummer, $course->getFullName('type-number-name'), $course->getTextualSemester(), - $sem->getDatesExport(), + implode("\n", $course->getAllDatesInSemester()->toStringArray()), implode(', ', $lecturersSorted) ]; } diff --git a/app/views/calendar/schedule/_entry_course.php b/app/views/calendar/schedule/_entry_course.php index ece53924afd545ce8fea47686ec627a18c73c83f..f6002184daf8d37443e13f397b55132ddcdb9fa6 100644 --- a/app/views/calendar/schedule/_entry_course.php +++ b/app/views/calendar/schedule/_entry_course.php @@ -1,5 +1,5 @@ <?php -$sem = Seminar::getInstance($show_entry['id']); +$course = Course::find($show_entry['id']); ?> <form class="default" action="<?= $controller->link_for('calendar/schedule/editseminar/' . $show_entry['id'] . '/' . $show_entry['cycle_id']) ?>" @@ -23,21 +23,23 @@ $sem = Seminar::getInstance($show_entry['id']); <section> <strong><?= _('Veranstaltungsnummer') ?></strong><br> - <?= htmlReady($sem->getNumber()) ?> + <?= htmlReady($course->veranstaltungsnummer) ?> </section> <section> <strong><?= _('Name') ?></strong><br> - <?= htmlReady($sem->getName()) ?> + <?= htmlReady($course->name) ?> </section> <section> <strong><?= _('Lehrende') ?></strong><br> - <? $pos = 0; - foreach ($sem->getMembers('dozent') as $dozent) :?> - <?php if ($pos > 0) echo ', '; ?> - <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $dozent['username']]) ?>"> - <?= htmlReady($dozent['fullname']) ?> + <? + $pos = 0; + $lecturers = CourseMember::findByCourseAndStatus($course->id, 'dozent'); + foreach ($lecturers as $lecturer) :?> + <?= $pos > 0 ? ', ' : '' ?> + <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $lecturer->user->username]) ?>"> + <?= htmlReady($lecturer->user->getFullName()) ?> </a> <? $pos++ ?> <? endforeach ?> @@ -45,7 +47,7 @@ $sem = Seminar::getInstance($show_entry['id']); <section> <strong><?= _('Veranstaltungszeiten') ?></strong><br> - <?= $sem->getDatesHTML(['show_room' => true]) ?><br> + <?= $course->getAllDatesInSemester()->toHtml(true) ?><br> </section> <section> diff --git a/app/views/calendar/schedule/_entry_inst.php b/app/views/calendar/schedule/_entry_inst.php index fb9435bc3b1a1e6c4e17c05b293e7d0a48cca8d7..228751f47f1b18bcbfd2e49da8db6f908abaecb7 100644 --- a/app/views/calendar/schedule/_entry_inst.php +++ b/app/views/calendar/schedule/_entry_inst.php @@ -15,38 +15,38 @@ </tr> </thead> <tbody> - <? foreach ($seminars as $seminar) : ?> + <? foreach ($courses as $course) : ?> <tr> - <td><?= htmlReady($seminar->getNumber()) ?></td> + <td><?= htmlReady($course->veranstaltungsnummer) ?></td> <td> - <a href="<?= URLHelper::getLink('dispatch.php/course/details/', ['sem_id' => $seminar->getId()]) ?>"> + <a href="<?= URLHelper::getLink('dispatch.php/course/details/', ['sem_id' => $course->id]) ?>"> <?= Icon::create('link-intern') ?> - <?= htmlReady($seminar->getName()) ?> + <?= htmlReady($course->name) ?> </a> </td> <td class="schedule-adminbind"> - <? $cycles = CalendarScheduleModel::getSeminarCycleId($seminar, $start, $end, $day) ?> + <? $cycles = CalendarScheduleModel::getSeminarCycleId($course->id, $start, $end, $day) ?> <? foreach ($cycles as $cycle) : ?> <span><?= $cycle->toString() ?></span> - <? $visible = CalendarScheduleModel::isSeminarVisible($seminar->getId(), $cycle->getMetadateId()) ?> + <? $visible = CalendarScheduleModel::isSeminarVisible($course->id, $cycle->getMetadateId()) ?> <?= Studip\LinkButton::create( _('Ausblenden'), - $controller->url_for('calendar/schedule/adminbind/' . $seminar->getId() . '/' . $cycle->getMetadateId() . '/0'), + $controller->url_for('calendar/schedule/adminbind/' . $course->id . '/' . $cycle->getMetadateId() . '/0'), [ - 'id' => $seminar->getId() . '_' . $cycle->getMetadateId() . '_hide', - 'onclick' => "STUDIP.Schedule.instSemUnbind('" . $seminar->getId() . "','" . $cycle->getMetadateId() . "'); return false;", + 'id' => $course->id . '_' . $cycle->getMetadateId() . '_hide', + 'onclick' => "STUDIP.Schedule.instSemUnbind('" . $course->id . "','" . $cycle->getMetadateId() . "'); return false;", 'style' => ($visible ? '' : 'display: none') ]) ?> <?= Studip\LinkButton::create( _('Einblenden'), - $controller->url_for('calendar/schedule/adminbind/' . $seminar->getId() . '/' . $cycle->getMetadateId() . '/1'), + $controller->url_for('calendar/schedule/adminbind/' . $course->id . '/' . $cycle->getMetadateId() . '/1'), [ - 'id' => $seminar->getId() . '_' . $cycle->getMetadateId() . '_show', - 'onclick' => "STUDIP.Schedule.instSemBind('" . $seminar->getId() . "','" . $cycle->getMetadateId() . "'); return false;", + 'id' => $course->id . '_' . $cycle->getMetadateId() . '_show', + 'onclick' => "STUDIP.Schedule.instSemBind('" . $course->id . "','" . $cycle->getMetadateId() . "'); return false;", 'style' => ($visible ? 'display: none' : '') ]) ?> <br> diff --git a/app/views/course/cancel_dates/index.php b/app/views/course/cancel_dates/index.php index 4f3e2978f214c7d14fbee2c085daa33c55a09350..0bb113831b6d0512f13aa49a247ac1beced9b4e6 100644 --- a/app/views/course/cancel_dates/index.php +++ b/app/views/course/cancel_dates/index.php @@ -6,7 +6,7 @@ </legend> <div style="padding: 5px; margin: 5px;font-weight: bold;"> <? echo join(', ', array_map(function ($d) { - return $d->toString(); + return $d->getFullName(); }, $dates)); ?> </div> @@ -21,9 +21,9 @@ </label> </fieldset> <? if (!empty($issue_id)) : ?> - <input type="hidden" name="issue_id" value="<?= $issue_id ?>"> + <input type="hidden" name="issue_id" value="<?= htmlReady($issue_id) ?>"> <? else : ?> - <input type="hidden" name="termin_id" value="<?= $dates[0]->getTerminId() ?>"> + <input type="hidden" name="termin_id" value="<?= htmlReady($dates[0]->id) ?>"> <? endif ?> <footer data-dialog-button> <?= Studip\Button::createAccept(_('Speichern')) ?> diff --git a/app/views/course/details/index.php b/app/views/course/details/index.php index 545454a26ac97a54dd8f1e6c3e4cccf286c13f38..29db9bdc31fee37df91810166ffa0db9d238b244 100644 --- a/app/views/course/details/index.php +++ b/app/views/course/details/index.php @@ -4,7 +4,6 @@ * @var Course $course * @var Course[] $siblings * @var Course_DetailsController $controller - * @var Seminar $sem * @var string $prelim_discussion * @var stdClass $id_sfx */ @@ -72,7 +71,7 @@ <tr> <td> <strong> - <? if ($sem->isAdmissionEnabled()) : ?> + <? if ($course->isAdmissionEnabled()) : ?> <?= _('maximale Teilnehmendenanzahl') ?> <? else : ?> <?= _('erwartete Teilnehmendenanzahl') ?> @@ -82,7 +81,7 @@ <td><?= htmlReady($course->admission_turnout) ?></td> </tr> <? endif ?> - <? if ($sem->isAdmissionEnabled() && $course->getNumWaiting()) : ?> + <? if ($course->isAdmissionEnabled() && $course->getNumWaiting()) : ?> <tr> <td> <strong><?= _('Wartelisteneinträge') ?></strong> @@ -174,18 +173,18 @@ <td><?= $prelim_discussion ?></td> </tr> <? endif ?> - <? $next_date = $sem->getNextDate() ?> + <? $next_date = $course->getNextDate() ?> <? if ($next_date) : ?> <tr> <td><strong><?= _('Nächster Termin') ?></strong></td> - <td><?= $next_date ?></td> + <td><?= htmlReady($next_date->getFullname()) ?></td> </tr> <? else : ?> - <? $firstTerm = $sem->getFirstDate() ?> + <? $firstTerm = $course->getFirstDate() ?> <? if ($firstTerm) : ?> <tr> <td><strong><?= _('Erster Termin') ?></strong></td> - <td><?= $firstTerm ?></td> + <td><?= htmlReady($firstTerm->getFullname()) ?></td> </tr> <? endif ?> <? endif ?> @@ -324,10 +323,7 @@ <h1><?= _('Räume und Zeiten') ?></h1> </header> <section> - <?= $sem->getDatesTemplate( - 'dates/seminar_html_location', - ['ort' => $course->ort] - ) ?> + <?= $course->getAllDatesInSemester()->toHtml(true) ?> </section> </article> <? if ($this->studymodules) : ?> @@ -441,7 +437,7 @@ if (!empty($mvv_tree)) : ?> </article> <? endif ?> -<? if ($courseset = $sem->getCourseSet()) : ?> +<? if ($courseset = $course->getCourseSet()) : ?> <article class="studip"> <header> <h1><?=_("Anmelderegeln")?></h1> diff --git a/app/views/course/overview/index.php b/app/views/course/overview/index.php index b23874089249f5232ec40539018aea03028b57ce..e538a6f36056455134cd5fb9bce3bedacbc02377 100644 --- a/app/views/course/overview/index.php +++ b/app/views/course/overview/index.php @@ -10,38 +10,38 @@ <?= htmlReady(Context::get()->Untertitel) ?> </dd> <? endif ?> - <? if (!$studygroup_mode) : ?> + <? if (!$course->isStudygroup()) : ?> <dt><?= _('Zeit / Veranstaltungsort') ?></dt> <dd> <?= $times_rooms ?: _('Die Zeiten der Veranstaltung stehen nicht fest.') ?> </dd> <? if ($next_date) : ?> <dt><?= _('Nächster Termin') ?></dt> - <dd><?= $next_date ?></dd> + <dd><?= $next_date->getFullName('long') ?></dd> <? else : ?> <dt><?= _('Erster Termin') ?></dt> <dd> <? if ($first_date) : ?> - <?= $first_date ?> + <?= $first_date->getFullName('long') ?> <? else : ?> <?= _('Die Zeiten der Veranstaltung stehen nicht fest.') ?> <? endif ?> </dd> <? endif ?> - <dt><?= htmlReady(get_title_for_status('dozent', $num_dozenten)) ?></dt> - <dd><?= implode(', ', $show_dozenten) ?> </dd> + <dt><?= htmlReady(get_title_for_status('dozent', $num_lecturers)) ?></dt> + <dd><?= implode(', ', $lecturer_html) ?> </dd> <? else : ?> - <? if ($sem->description) : ?> + <? if ($course->beschreibung) : ?> <dt><?= _('Beschreibung') ?></dt> - <dd><?= formatLinks($sem->description) ?></dd> + <dd><?= formatLinks($course->beschreibung) ?></dd> <? endif ?> <dt><?= _('Moderiert von') ?></dt> <dd> <ul class="list-csv"> <? foreach ($all_mods as $mod) : ?> <li> - <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $mod['username']]) ?>"> - <?= htmlready($mod['fullname']) ?> + <a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $mod->user->username]) ?>"> + <?= htmlready($mod->user->getFullName()) ?> </a> </li> <? endforeach ?> diff --git a/app/views/course/studygroup/edit.php b/app/views/course/studygroup/edit.php index eaba1f7a72c0f0384b1da8c48b71b59a7a111885..28a8cc5901862c8e5f3c2d1f389487fb4113aad3 100644 --- a/app/views/course/studygroup/edit.php +++ b/app/views/course/studygroup/edit.php @@ -1,9 +1,3 @@ -<? -# Lifter010: TODO -use Studip\Button, Studip\LinkButton; - -?> - <?= $this->render_partial("course/studygroup/_feedback") ?> <form action="<?= $controller->update() ?>" method="post" class="default"> @@ -16,39 +10,39 @@ use Studip\Button, Studip\LinkButton; <input type='submit' class="invisible" name="<?=_('Änderungen übernehmen') ?>" aria-hidden="true"> <label> <span class="required"><?= _('Name') ?></span> - <input type='text' name='groupname' value="<?= htmlReady($sem->getName()) ?>"> + <input type='text' name='groupname' value="<?= htmlReady($course->name) ?>"> </label> <label> <?= _('Beschreibung') ?> - <textarea name="groupdescription"><?= htmlReady($sem->description) ?></textarea> + <textarea name="groupdescription"><?= htmlReady($course->beschreibung) ?></textarea> </label> - <? if ($GLOBALS['perm']->have_studip_perm('dozent', $sem_id)) : ?> - <?= $this->render_partial('course/studygroup/_replace_founder', compact('tutors')) ?> - <? endif; ?> + <? if ($GLOBALS['perm']->have_studip_perm('dozent', $course->id)) : ?> + <?= $this->render_partial('course/studygroup/_replace_founder', ['tutors' => $tutors]) ?> + <? endif ?> <label> <?= _('Zugang') ?> <select name="groupaccess"> - <option value="all" <? if (!$sem->admission_prelim) echo 'selected'; ?>> + <option value="all" <? if (!$course->admission_prelim) echo 'selected'; ?>> <?= _('Offen für alle') ?> </option> - <option value="invite" <? if ($sem->admission_prelim) echo 'selected'; ?>> + <option value="invite" <? if ($course->admission_prelim) echo 'selected'; ?>> <?= _('Auf Anfrage') ?> </option> - <? if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED || !$sem->visible): ?> - <option value="invisible" <? if (!$sem->visible) echo 'selected'; ?> <? if (!Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED) echo 'disabled'; ?>> + <? if (Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED || !$course->visible): ?> + <option value="invisible" <? if (!$course->visible) echo 'selected'; ?> <? if (!Config::get()->STUDYGROUPS_INVISIBLE_ALLOWED) echo 'disabled'; ?>> <?= _('Unsichtbar') ?> </option> - <? endif; ?> + <? endif ?> </select> </label> </fieldset> <footer> - <?= Button::createAccept(_('Übernehmen'), ['title' => _("Änderungen übernehmen")]); ?> - <?= LinkButton::createCancel(_('Abbrechen'), URLHelper::getURL('seminar_main.php')); ?> + <?= Studip\Button::createAccept(_('Übernehmen'), ['title' => _("Änderungen übernehmen")]); ?> + <?= Studip\LinkButton::createCancel(_('Abbrechen'), URLHelper::getURL('seminar_main.php')); ?> </footer> </form> diff --git a/app/views/course/timesrooms/_regularEvents.php b/app/views/course/timesrooms/_regularEvents.php index baaa2d8852bd1b1d5edc1e952648fd00df21b7ac..98b6f6fbb37db9d4cef618d7da470adaaf539108 100644 --- a/app/views/course/timesrooms/_regularEvents.php +++ b/app/views/course/timesrooms/_regularEvents.php @@ -23,15 +23,28 @@ <?= CSRFProtection::tokenTag() ?> <article id="<?= $metadate_id ?>" class="<?= ContentBoxHelper::classes($metadate_id) ?>"> - <header class="<?= $course->getCycleColorClass($metadate_id) ?>"> + <? + $booking_status_icon = $cycle['cycle']->getIconForBookingStatus(); + $booking_status_message = $cycle['cycle']->getMessageForBookingStatus(); + $booking_status = $cycle['cycle']->getBookingStatus(); + $booking_status_class = ''; + if ($booking_status === SeminarCycleDate::BOOKING_STATUS_NOT_BOOKED) { + $booking_status_class = 'red'; + } elseif ($booking_status === SeminarCycleDate::BOOKING_STATUS_PARTIALLY_BOOKED) { + $booking_status_class = 'yellow'; + } elseif ($booking_status === SeminarCycleDate::BOOKING_STATUS_ALL_BOOKED) { + $booking_status_class = 'green'; + } + ?> + <header class="<?= htmlReady($booking_status_class) ?>"> <h1> - <? if ($info = $course->getBookedRoomsTooltip($metadate_id)) : ?> - <?= tooltipHtmlIcon($info) ?> - <? elseif ($course->getCycleColorClass($metadate_id) === 'red'): ?> - <?= tooltipIcon(_('Keine Raumbuchungen vorhanden')) ?> - <? else: ?> - <?= tooltipIcon(_('Keine offenen Raumbuchungen')) ?> - <? endif ?> + <span class="as-link tooltip" + tabindex="0" + data-tooltip + aria-label="<?= htmlReady($booking_status_message) ?>"> + <?= $booking_status_icon->asImg(['class' => 'text-bottom']) ?> + <span class="tooltip-content"><?= $booking_status_message ?></span> + </span> <a href="<?= ContentBoxHelper::href($metadate_id) ?>"> <?= htmlReady($cycle['cycle']->toString('long')) ?> </a> diff --git a/app/views/course/timesrooms/createSingleDate.php b/app/views/course/timesrooms/createSingleDate.php index 4364feb074eb0ce9ddea6727782eabfe351539c5..f8e08d5be800abb8e21cf7f95d2252565135d307 100644 --- a/app/views/course/timesrooms/createSingleDate.php +++ b/app/views/course/timesrooms/createSingleDate.php @@ -67,13 +67,13 @@ <select id="related_teachers" name="related_teachers[]" multiple class="multiple"> <? foreach ($teachers as $dozent) : ?> <option <?= in_array($dozent['user_id'], Request::getArray('related_teachers')) ? 'selected' : '' ?> - value="<?= $dozent['user_id'] ?>"><?= htmlReady($dozent['fullname']) ?></option> + value="<?= $dozent['user_id'] ?>"><?= htmlReady($dozent->user->getFullName()) ?></option> <? endforeach ?> </select> <? else : ?> <p style="margin-left: 15px"> <? $dozent = array_pop($teachers) ?> - <?= htmlReady($dozent['fullname']) ?> + <?= htmlReady($dozent->getUserFullname()) ?> </p> <? endif ?> </label> diff --git a/app/views/course/timesrooms/editStack.php b/app/views/course/timesrooms/editStack.php index c664f5e1f2061e440af3cd682448d0e1104cffb7..cd4df4013f40a17bc4af7a728465cf313cad1327 100644 --- a/app/views/course/timesrooms/editStack.php +++ b/app/views/course/timesrooms/editStack.php @@ -133,8 +133,8 @@ <?= _('Lehrende') ?> <select name="related_persons[]" id="related_persons" multiple> <? foreach ($teachers as $teacher) : ?> - <option value="<?= htmlReady($teacher['user_id']) ?>"> - <?= htmlReady($teacher['fullname']) ?> + <option value="<?= htmlReady($teacher->user_id) ?>"> + <?= htmlReady($teacher->user->getFullName()) ?> </option> <? endforeach ?> </select> diff --git a/composer.json b/composer.json index 4a8096741b271a7150e92f8c3693936984c3f8e3..e1288181c1c53a2f060cfd6b40d1f5ff5831307a 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,11 @@ "Studip\\Cache\\": "lib/classes/cache/", "Studip\\Calendar\\": "lib/classes/calendar/", "Studip\\Forms\\": "lib/classes/forms/", - "Studip\\": "lib/classes/", + "Studip\\": [ + "lib/classes/", + "lib/exceptions/", + "lib/exceptions/course/" + ], "Trails\\": "lib/trails/", "": [ "lib/calendar/", diff --git a/db/migrations/1.6_step_25_raumzeit_db_conversion.php b/db/migrations/1.6_step_25_raumzeit_db_conversion.php index 9a8ffd73c2f2c4087518e62350964624f04f5b53..56e90df56e30d69dd8c76e2e76c0c09838cc158f 100644 --- a/db/migrations/1.6_step_25_raumzeit_db_conversion.php +++ b/db/migrations/1.6_step_25_raumzeit_db_conversion.php @@ -54,12 +54,6 @@ class Step25RaumzeitDbConversion extends Migration $STEP_SIZE= 300; - // include business logic - require_once('lib/classes/Seminar.php'); - require_once('lib/resources/lib/VeranstaltungResourcesAssign.php'); - - - // lets go... fwrite($logfile_handle, "(". date("Y-m-d H:i:s T") .") Starting conversion of imported seminar dates.\n"); diff --git a/lib/classes/Context.php b/lib/classes/Context.php index 695da80ab409772c777b35a1d39360e50a2a03e7..389a8cc276170514b74dc191798b71ac50f3d9e3 100644 --- a/lib/classes/Context.php +++ b/lib/classes/Context.php @@ -222,8 +222,6 @@ class Context if (self::isCourse()) { $course = self::get(); - Seminar::setInstance(new Seminar($course)); - // check if current user can access the object if (!$perm->get_studip_perm($course['Seminar_id'])) { if ($course['lesezugriff'] > 0 || !Config::get()->ENABLE_FREE_ACCESS) { diff --git a/lib/classes/CourseAvatar.php b/lib/classes/CourseAvatar.php index 8c153a800c00168531757c76454533e00b3d99d8..8e75eb76df87d0da51291b012bea0921d94a5ccf 100644 --- a/lib/classes/CourseAvatar.php +++ b/lib/classes/CourseAvatar.php @@ -29,9 +29,9 @@ class CourseAvatar extends Avatar */ public function getDefaultTitle() { - return Seminar::GetInstance($this->user_id)->name; + return Course::find($this->user_id)->name; } - + /** * Return if avatar is visible to the current user. * @return boolean: true if visible diff --git a/lib/classes/CourseDateList.php b/lib/classes/CourseDateList.php new file mode 100644 index 0000000000000000000000000000000000000000..881d6da90e231571e8bf08104010bc91feedc7f5 --- /dev/null +++ b/lib/classes/CourseDateList.php @@ -0,0 +1,323 @@ +<?php +/** + * CourseDateList.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023-2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +/** + * The CourseDateCollection class helps with managing all types of + * course dates: regular, irregular and cancelled course dates. + * It provides helper methods for getting all the time ranges from + * the different date type instances it contains. + */ +class CourseDateList implements Stringable +{ + /** + * @var array $regular_dates contains regular course dates. + */ + protected array $regular_dates = []; + + /** + * @var array $single_dates contains single course dates. + * These can be part of a regular course date, or they can + * be irregular dates, depending on the use case of the + * CourseDateCollection instance. + */ + protected array $single_dates = []; + + /** + * @var array $cancelled_dates contains cancelled course dates. + */ + protected array $cancelled_dates = []; + + public function addRegularDate(SeminarCycleDate $regular_date) + { + $this->regular_dates[] = $regular_date; + } + + public function addSingleDate(CourseDate $date) + { + $this->single_dates[] = $date; + } + + public function addCancelledDate(CourseExDate $cancelled_date) + { + $this->cancelled_dates[] = $cancelled_date; + } + + public function isEmpty() : bool + { + return empty($this->regular_dates) + && empty($this->single_dates) + && empty($this->cancelled_dates); + } + + public function getAllRegularDates() : array + { + return $this->regular_dates; + } + + /** + * Compares two regular dates by looking at their sort position, their weekday + * and their start hour. If the first date is less than the second date in those + * attributes, -1 is returned. In the oppsite case, 1 is returted. In case both + * regular dates are equal, 0 is returned. + * + * @param SeminarCycleDate $a The first regular date to compare. + * + * @param SeminarCycleDate $b The second regular date to compare. + * + * @return int The result of the comparison result: -1, 0 or 1. + */ + public static function compareRegularDates(SeminarCycleDate $a, SeminarCycleDate $b) : int + { + return $a->sorter - $b->sorter + ?: $a->weekday - $b->weekday + ?: $a->start_hour - $b->start_hour; + } + + /** + * @see CourseDateList::compareSingleDatesOrCancelledDates + */ + public static function compareSingleDates(CourseDate $a, CourseDate $b) : int + { + return self::compareSingleDatesOrCancelledDates($a, $b); + } + + /** + * @see CourseDateList::compareSingleDatesOrCancelledDates + */ + public static function compareCancelledDates(CourseExDate $a, CourseExDate $b) : int + { + return self::compareSingleDatesOrCancelledDates($a, $b); + } + + /** + * Compares two single dates or cancelled dates. If the first date starts before the second, + * it is considered less than the second date and -1 is returned. In the opposite + * case, it is considered more than the second date and 1 is returned. In case both + * dates start on the exact same point in time, the end time of both dates is compared + * in the same manner. Only in the case that the end time is also equal, both dates + * are considered equal and 0 is returned. + * + * @param $a The first date for the comparison. + * + * @param $b The second date for the comparison. + * + * @return int -1 if a < b, 0 if a == b and 1 if a > b. + */ + protected static function compareSingleDatesOrCancelledDates($a, $b) : int + { + return $a->date - $b->date + ?: $a->end_time - $b->end_time; + } + + /** + * Sorts all dates that are present in this collection. + * + * @return void + */ + public function sort() : void + { + uasort($this->regular_dates, self::compareRegularDates(...)); + uasort($this->single_dates, self::compareSingleDates(...)); + uasort($this->cancelled_dates, self::compareCancelledDates(...)); + } + + public function getRegularDates() : array + { + return $this->regular_dates; + } + + public function getSingleDates( + bool $include_regular_dates = false, + bool $include_cancelled_dates = false, + bool $sorted = false + ): array { + if ($include_regular_dates) { + $all_single_dates = []; + foreach ($this->regular_dates as $regular_date) { + foreach ($regular_date->dates as $date) { + $all_single_dates[] = $date; + } + } + $all_single_dates = array_merge($all_single_dates, $this->single_dates); + if ($include_cancelled_dates) { + $all_single_dates = array_merge($all_single_dates, $this->cancelled_dates); + } + if ($sorted) { + uasort($all_single_dates, self::compareSingleDatesOrCancelledDates(...)); + } + return $all_single_dates; + } else { + if ($include_cancelled_dates || $sorted) { + $all_single_dates = $this->single_dates; + if ($include_cancelled_dates) { + $all_single_dates = array_merge($all_single_dates, $this->cancelled_dates); + } + if ($sorted) { + uasort($all_single_dates, self::compareSingleDatesOrCancelledDates(...)); + } + return $all_single_dates; + } else { + return $this->single_dates; + } + } + } + + public function getCancelledDates() : array + { + return $this->cancelled_dates; + } + + public function toHtml( + bool $group_by_rooms = false, + bool $with_room_names = false, + bool $with_cancelled_dates = false + ) : string { + if ($this->isEmpty()) { + return _('Die Zeiten der Veranstaltung stehen nicht fest.'); + } + + $template = null; + if ($group_by_rooms) { + $grouped_dates = []; + foreach ($this->regular_dates as $regular_date) { + $room = $regular_date->getMostBookedRoom(); + if ($room instanceof Room) { + if (!array_key_exists($room->name, $grouped_dates)) { + $grouped_dates[$room->name] = new CourseDateList(); + } + $grouped_dates[$room->name]->addRegularDate($regular_date); + } else { + if (!array_key_exists(_('Ohne Raum'), $grouped_dates)) { + $grouped_dates[_('Ohne Raum')] = new CourseDateList(); + } + $grouped_dates[_('Ohne Raum')]->addRegularDate($regular_date); + } + } + foreach ($this->single_dates as $date) { + $room_name = $date->getRoomName(); + if ($room_name) { + if (!array_key_exists($room_name, $grouped_dates)) { + $grouped_dates[$room_name] = new CourseDateList(); + } + $grouped_dates[$room_name]->addSingleDate($date); + } else { + if (!array_key_exists(_('Ohne Raum'), $grouped_dates)) { + $grouped_dates[_('Ohne Raum')] = new CourseDateList(); + } + $grouped_dates[_('Ohne Raum')]->addSingleDate($date); + } + } + if ($with_cancelled_dates) { + foreach ($this->cancelled_dates as $date) { + if (!array_key_exists(_('Ohne Raum'), $grouped_dates)) { + $grouped_dates[_('Ohne Raum')] = new CourseDateList(); + } + $grouped_dates[_('Ohne Raum')]->addCancelledDate($date); + } + } + $template = $GLOBALS['template_factory']->open('dates/room_grouped_course_date_list'); + $template->grouped_dates = $grouped_dates; + } else { + $template = $GLOBALS['template_factory']->open('dates/course_date_list'); + $template->with_room_names = $with_room_names; + $template->collection = $this; + } + $template->with_cancelled_dates = $with_cancelled_dates; + return $template->render(); + } + + public function toStringArray(bool $group_by_rooms = false, bool $with_room_names = false, bool $with_cancelled_dates = false) : array + { + $output = []; + foreach ($this->regular_dates as $regular_date) { + $date_line = $regular_date->toString('long-start'); + if ($with_room_names || $group_by_rooms) { + $room = $regular_date->getMostBookedRoom(); + if ($room instanceof Room) { + if ($with_room_names) { + $date_line .= ' (' . sprintf(_('Raum %s'), $room->name) . ')'; + $output[] = $date_line; + } else { + //Group by rooms. + if (!is_array($output[$room->name])) { + $output[$room->name] = []; + } + $output[$room->name][] = $date_line; + } + } elseif ($group_by_rooms) { + //Use the "null" room name: + $null_room_name = _('Kein Raum'); + if (!is_array($output[$null_room_name])) { + $output[$null_room_name] = []; + } + $output[$null_room_name][] = $date_line; + } + } else { + $output[] = $date_line; + } + } + foreach ($this->single_dates as $single_date) { + $date_line = $single_date->getFullName($with_room_names ? 'long-include-room' : 'long'); + if ($group_by_rooms) { + $room_name = _('Kein Raum'); + if ($single_date->room instanceof Room) { + $room_name = $single_date->room->name; + } + if (!is_array($output[$room_name])) { + $output[$room_name] = []; + } + $output[$room_name][] = $date_line; + } else { + $output[] = $date_line; + } + } + if ($with_cancelled_dates) { + foreach ($this->cancelled_dates as $cancelled_date) { + if ($group_by_rooms) { + $room_name = _('Kein Raum'); + if (!is_array($output[$room_name])) { + $output[$room_name] = []; + } + $output[$room_name][] = $cancelled_date->toString(); + } else { + $output[] = $cancelled_date->toString(); + } + } + } + if ($group_by_rooms) { + ksort($output); + $flat_output = []; + foreach ($output as $room_name => $date_lines) { + $flat_output[] = sprintf('%s:', $room_name); + $flat_output = array_merge($flat_output, $date_lines); + //Separate the grouped output with an empty string: + $flat_output[] = ''; + } + return $flat_output; + } else { + return $output; + } + } + + + public function __toString() + { + if ($this->isEmpty()) { + return _('Die Zeiten der Veranstaltung stehen nicht fest.'); + } + + return implode("\n", $this->toStringArray()); + } +} diff --git a/lib/classes/DateFormatter.php b/lib/classes/DateFormatter.php deleted file mode 100644 index 5309cbc92dba6670e2614c4f072d09c4115fa313..0000000000000000000000000000000000000000 --- a/lib/classes/DateFormatter.php +++ /dev/null @@ -1,176 +0,0 @@ -<?php -# Lifter010: TODO -/** - * DateFormater.php - Handles the formatting of one date and associated rooms. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author sbrummer <soenke.brummerloh@uni-osnabrueck.de> - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * Formates one SingleDate object or a series of SingleDate objects into a nice format. - */ -class DateFormatter { - /** - * @var array holds the dates use for formatting - */ - private $dates; - - /** - * @var string holds the return-type, may be int or string - */ - private $return_mode; - - /** - * @param $dates an array with an array of SingleDate objects. - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return void - */ - private function __construct($dates, $return_mode = 'string') - { - $this->dates = $dates; - $this->return_mode = $return_mode; - } - - /** - * Formats one single date into a nice format. - * @static - * @param $date SingleDate object - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return string - */ - public static function formatDateAndRoom($date, $return_mode = 'string') - { - $dates = DateFormatter::wrapDateWithArray($date); - return DateFormatter::formatDateWithAllRooms($dates, $return_mode); - } - - /** - * Formats a series of SingleDate objects into a nice format. The dates parameter is an array of dates. - * The array has to have the key 'termin' with an array of SingleDate objects as value. - * @static - * @param $dates an array with an array of SingleDate objects - * @param string $return_mode expected values are 'int', 'string' and 'export'. The default value is 'string'. - * @return string - */ - public static function formatDateWithAllRooms($dates, $return_mode = 'string') - { - $dateFormatter = new DateFormatter($dates, $return_mode); - return $dateFormatter->internalFormatDateWithAllRooms(); - } - - private static function wrapDateWithArray($date) - { - $dates = []; - $dates['termin'] = [$date]; - return $dates; - } - - private function internalFormatDateWithAllRooms() - { - $dateWithRooms = ''; - if ($this->dates['termin']) { - // if we have multiple rooms at the same time we display them all - foreach ($this->dates['termin'] as $num => $termin_id) { - $date = new SingleDate($termin_id); - - // if we want an int and format the date ourself - if ($this->return_mode == 'int') { - return $date->getStartTime(); - } - - $isFirstDate = ($num == 0); - if ($isFirstDate) { - $dateWithRooms = $this->internalFormatDateAndRoom($date); - } else { - $dateWithRooms .= ', ' . $this->formatRoom($date); - } - } - } - return $dateWithRooms; - } - - private function internalFormatDateAndRoom($date) - { - $ret = $this->formatDate($date); - - if ($this->return_mode != 'int') { - $formatedRooms = $this->formatRoom($date); - if ($formatedRooms) { - $ret .= ', '; - $ret .= _("Ort:") . ' '; - $ret .= $formatedRooms; - } - } - - return $ret; - } - - private function formatDate($date) - { - if ($this->return_mode == 'int') { - return $date->getStartTime(); - } - else { - return $date->toString(); - } - } - - private function formatRoom($date) - { - if ($this->return_mode == 'int') { - return ''; - } - else { - return $this->formatLocationText($date); - } - } - - private function formatLocationText($date) - { - if ($this->hasResource($date)) { - $resObj = Resource::find($date->getResourceID()); - return $this->generateLocationTextFromResourceObject($resObj); - } else if ($this->hasFreeRoomText($date)) { - return $this->generateLocationTextFromFreeRoomText($date); - } else { - return ''; - } - } - - private function generateLocationTextFromResourceObject($resObj) - { - if ($resObj) { - if ($this->return_mode == 'string') { - return '<a href="' . $resObj->getActionLink() . '" data-dialog="1">' - . htmlReady($resObj->name) - . '</a>'; - } - else { - return htmlReady($resObj->name); - } - } - return ''; - } - - private function generateLocationTextFromFreeRoomText($date) - { - return '(' . htmlReady($date->getFreeRoomText()) . ')'; - } - - private function hasResource($date) - { - return $date && $date->getResourceID(); - } - - private function hasFreeRoomText($date) - { - return $date && $date->getFreeRoomText(); - } -} diff --git a/lib/classes/EnrolmentInformation.php b/lib/classes/EnrolmentInformation.php new file mode 100644 index 0000000000000000000000000000000000000000..e14adca44437fc06ea9e5a0098dc18a846515f2a --- /dev/null +++ b/lib/classes/EnrolmentInformation.php @@ -0,0 +1,64 @@ +<?php +/** + * EnrolmentInformation.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * + */ + +namespace Studip; + +use Studip\Information; + +/** + * The EnrolmentInformation class holds information regarding the ability + * of a user to enrol into a specific course. + */ +class EnrolmentInformation extends Information +{ + /** + * @var bool An indicator whether enrolment is allowed according + * to the message (true) or forbidden (false). + */ + protected bool $enrolment_allowed = false; + + public function __construct( + string $message, + int $type = Information::INFO, + string $codeword = '', + bool $enrolment_allowed = false + ) { + $this->enrolment_allowed = $enrolment_allowed; + parent::__construct($message, $type, $codeword); + } + + /** + * The setter for the enrolment_allowed attribute. + * + * @param bool $enrolment_allowed The new status for the enrolment_allowed attribute. + * + * @return void + */ + public function setEnrolmentAllowed(bool $enrolment_allowed) : void + { + $this->enrolment_allowed = $enrolment_allowed; + } + + /** + * The getter for the enrolment_allowed attribute. + * + * @return bool The status of the enrolment_allowed attribute. + */ + public function isEnrolmentAllowed() : bool + { + return $this->enrolment_allowed; + } +} diff --git a/lib/classes/ForumPerm.php b/lib/classes/ForumPerm.php index 20ee1952a4aa114f439f6452cd54867e3de8c34e..24934dbc3d15185c4c7afe7943c44fcfe97d0913 100644 --- a/lib/classes/ForumPerm.php +++ b/lib/classes/ForumPerm.php @@ -68,11 +68,11 @@ class ForumPerm { if ($user_id == 'nobody' || $status == false) { // which status has nobody - read only or read/write? if (get_object_type($seminar_id) == 'sem') { - $sem = Seminar::getInstance($seminar_id); + $course = Course::find($seminar_id); - if ($sem->write_level == 0) { + if ($course->schreibzugriff == 0) { $status = 'nobody_write'; - } else if ($sem->read_level == 0) { + } else if ($course->lesezugriff == 0) { $status = 'nobody_read'; } else { return false; diff --git a/lib/classes/Information.php b/lib/classes/Information.php new file mode 100644 index 0000000000000000000000000000000000000000..4489d2bf313aacc02e7059ce218c8883daf75de5 --- /dev/null +++ b/lib/classes/Information.php @@ -0,0 +1,157 @@ +<?php +/** + * Information.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023-2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +namespace Studip; + +/** + * The Information class represents an information from the internals of Stud.IP + * that shall be displayed to the user, but not necessarily right away in the + * form of an exception or a simple piece of text. The information class allows + * the use of codewords (error codes) and to indicate whether the information + * is just of informative character, a warning or an error. + */ +class Information implements \Stringable +{ + const INFO = 0; + const WARNING = 1; + const ERROR = 2; + + /** + * @var int The type of information that shall be displayed. + */ + protected int $type = Information::INFO; + + /** + * @var string A machine-readable codeword. + */ + protected string $codeword; + + /** + * @var string A user-readable message for the information. + */ + protected string $message; + + /** + * @var \Range|null The Stud.IP range object that the information is related to. + */ + protected ?\Range $range = null; + + public function __construct( + string $message = '', + int $type = Information::INFO, + string $codeword = '', + ?\Range $range = null + ) { + $this->message = $message; + $this->type = $type; + $this->codeword = $codeword; + $this->range = $range; + } + + public function getMessage() : string + { + return $this->message; + } + + public function setMessage(string $message) : void + { + $this->message = $message; + } + + public function getType() : int + { + return $this->type; + } + + public function setType(int $type) : void + { + $this->type = $type; + } + + public function getCodeword() : string + { + return $this->codeword; + } + + public function setCodeword(string $codeword) : void + { + $this->codeword = $codeword; + } + + public function getRange() : ?\Range + { + return $this->range; + } + + public function setRange(\Range $range) : void + { + $this->range = $range; + } + + /** + * Generates a string representation of the information. + * + * @return string The string representation of the information. + */ + public function __toString() : string + { + $prefix = match ($this->type) { + Information::INFO => _('Hinweis'), + Information::WARNING => _('Warnung'), + Information::ERROR => _('Fehler'), + default => '', + }; + if ($prefix) { + $prefix .= ': '; + } + if ($this->range) { + $prefix .= sprintf('%s: ', $this->range->getFullName()); + } + if ($this->codeword) { + $prefix .= sprintf('%s: ', $this->codeword); + } + return $prefix . $this->message; + } + + /** + * Generates a Stud.IP message box for the information. + * + * @param $verbose bool Whether to include the codeword (true) or not (false). + * Defaults to false. + * @return \MessageBox The generated message box for the information. + */ + public function toMessageBox(bool $verbose = false) : \MessageBox + { + $text = ''; + if ($verbose) { + if ($this->range) { + $text = sprintf('%1$s: %2$s: %3$s', $this->range->getFullName(), $this->codeword, $this->message); + } else { + $text = sprintf('%1$s: %2$s', $this->codeword, $this->message); + } + } else { + if ($this->range) { + $text = sprintf('%1$s: %2$s', $this->range->getFullName(), $this->message); + } else { + $text = $this->message; + } + } + return match ($this->type) { + Information::WARNING => \MessageBox::warning($text), + Information::ERROR => \MessageBox::error($text), + default => \MessageBox::info($text), + }; + } +} diff --git a/lib/classes/JsonApi/Routes/Blubber/Authority.php b/lib/classes/JsonApi/Routes/Blubber/Authority.php index 9d4cf67b188163bfc5821b950c6f856c8a866a18..b03b6aafad6eb18031175382206b3279981d6876 100644 --- a/lib/classes/JsonApi/Routes/Blubber/Authority.php +++ b/lib/classes/JsonApi/Routes/Blubber/Authority.php @@ -4,7 +4,6 @@ namespace JsonApi\Routes\Blubber; use BlubberComment; use BlubberThread; -use Seminar; use User; class Authority diff --git a/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php b/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php index cef1077ecb7b1d8f15b515092865ab128c703c4f..b3a31efbc5f4add99d10ad21d341d12a8655152a 100644 --- a/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php +++ b/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php @@ -21,16 +21,13 @@ class DetailsOfTreeNodeCourse extends NonJsonApiController } // Get course dates in textual form - $dates = \Seminar::GetInstance($args['id'])->getDatesHTML([ - 'semester_id' => null, - 'show_room' => true, - ]); + $dates = $course->getAllDatesInSemester(); $data = [ 'semester' => $course->semester_text, 'lecturers' => [], 'admissionstate' => null, - 'dates' => $dates + 'dates' => $dates->toHtml(false, true) ]; // Get lecturers diff --git a/lib/classes/JsonApi/Schemas/SeminarCycleDate.php b/lib/classes/JsonApi/Schemas/SeminarCycleDate.php index 355e85682b94810d576c6bb6645dea16d0f3ad59..6ad9c89f7f0a70c7905689c12a042df9fbab0d2b 100644 --- a/lib/classes/JsonApi/Schemas/SeminarCycleDate.php +++ b/lib/classes/JsonApi/Schemas/SeminarCycleDate.php @@ -94,15 +94,11 @@ class SeminarCycleDate extends SchemaProvider private static function createLocation(\SeminarCycleDate $entry) { - $cycle = new \CycleData($entry); - // check, if the date is assigned to a room - if ($rooms = $cycle->getPredominantRoom(0, 0)) { + if ($rooms = $entry->getMostBookedRooms()) { return array_unique(getPlainRooms($rooms)); - } elseif ($rooms = $cycle->getFreeTextPredominantRoom(0, 0)) { - unset($rooms['']); - - return array_keys($rooms); + } elseif ($rooms = $entry->getMostUsedFreetextRoomNames()) { + return $rooms; } return []; diff --git a/lib/classes/SemBrowse.php b/lib/classes/SemBrowse.php index 536b7e3aa08c60f85e31cccb027ec05e6a0f45a2..19e6f6cea497fa01806ed29444b9a7518d3ecde6 100644 --- a/lib/classes/SemBrowse.php +++ b/lib/classes/SemBrowse.php @@ -674,12 +674,10 @@ class SemBrowse { && (empty($sem_data[key($sem_data[$seminar_id]['parent_course'])]) || $child)) { // create instance of seminar-object - $seminar_obj = new Seminar($seminar_id); - // is this sem a studygroup? - $studygroup_mode = SeminarCategories::GetByTypeId($seminar_obj->getStatus())->studygroup_mode; + $course = Course::find($seminar_id); - $sem_name = $seminar_obj->getFullName('type-name'); - $seminar_number = key($sem_data[$seminar_id]['VeranstaltungsNummer']); + $sem_name = $course->getFullName('type-name'); + $seminar_number = $course->veranstaltungsnummer; $visibleChildren = []; @@ -690,12 +688,14 @@ class SemBrowse { } $row .= '>'; - if ($studygroup_mode) { + if ($course->isStudygroup()) { $sem_name .= ' (' . _('Studiengruppe'); - if ($seminar_obj->admission_prelim) $sem_name .= ', ' . _('Zutritt auf Anfrage'); + if ($course->admission_prelim) { + $sem_name .= ', ' . _('Zutritt auf Anfrage'); + } $sem_name .= ')'; $row .= '<td width="1%" class="hidden-tiny-down">'; - $row .= StudygroupAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); + $row .= StudygroupAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $course->name]); $row .= '</td>'; } else { $sem_number_start = key($sem_data[$seminar_id]['sem_number']); @@ -709,7 +709,7 @@ class SemBrowse { $sem_name .= " (" . $this->search_obj->sem_dates[$sem_number_start]['name'] . ')'; } $row .= '<td width="1%" class="hidden-tiny-down">'; - $row .= CourseAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $seminar_obj->getName()]); + $row .= CourseAvatar::getAvatar($seminar_id)->getImageTag(Avatar::SMALL, ['title' => $course->name]); $row .= '</td>'; } @@ -724,10 +724,10 @@ class SemBrowse { $row .= '<td style="width: 66%" colspan="2">'; // Show the "more" icon only if there are visible children. - if (count($seminar_obj->children) > 0) { + if (count($course->children) > 0) { // If you are not root, perhaps not all available subcourses are visible. - $visibleChildren = $seminar_obj->children; + $visibleChildren = $course->children; if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { $visibleChildren = $visibleChildren->filter(function($c) { return $c->visible; @@ -754,18 +754,12 @@ class SemBrowse { } $row .= htmlReady($sem_name) . '</a><br>'; //create Turnus field - if ($studygroup_mode) { + if ($course->isStudygroup()) { $row .= '<div style="font-size:smaller">' - . htmlReady(mb_substr($seminar_obj->description, 0, 100)) + . htmlReady(mb_substr($course->description, 0, 100)) . '</div>'; } else { - $temp_turnus_string = $seminar_obj->getDatesExport( - [ - 'short' => true, - 'shrink' => true, - 'semester_id' => '' - ] - ); + $temp_turnus_string = implode(" ", $course->getAllDatesInSemester()->toStringArray()); //Shorten, if string too long (add link for details.php) if (mb_strlen($temp_turnus_string) > 70) { $temp_turnus_string = htmlReady(mb_substr($temp_turnus_string, 0, mb_strpos(mb_substr($temp_turnus_string, 70, mb_strlen($temp_turnus_string)), ',') + 71)); @@ -777,7 +771,7 @@ class SemBrowse { $row .= '<div style="margin-left:5px;font-size:smaller">' . htmlReady($seminar_number) . '</div>'; } $row .= '<div style="margin-left:5px;font-size:smaller">' . $temp_turnus_string . '</div>'; - if (count($seminar_obj->children) > 0 && count($visibleChildren) > 0) { + if (count($course->children) > 0 && count($visibleChildren) > 0) { $row .= '<div style="margin-left:5px;font-size:smaller">'; $row .= sprintf(_('%u Unterveranstaltungen'), count($visibleChildren)); $row .= '</div>'; @@ -822,8 +816,7 @@ class SemBrowse { $row .= ')</td>'; if (Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) { $row .= '<td>'; - switch (self::getStatusCourseAdmission($seminar_id, - $seminar_obj->admission_prelim)) { + switch (self::getStatusCourseAdmission($seminar_id, $course->admission_prelim)) { case 1: $row .= Icon::create( 'info-circle', @@ -851,7 +844,7 @@ class SemBrowse { } // Process children. - foreach ($seminar_obj->children as $child) { + foreach ($course->children as $child) { $row .= $this->printCourseRow($child->id, $sem_data, true); } diff --git a/lib/classes/Seminar.php b/lib/classes/Seminar.php deleted file mode 100644 index 0fccdbce6455da0cd6c099f656dd4f4d8522eed3..0000000000000000000000000000000000000000 --- a/lib/classes/Seminar.php +++ /dev/null @@ -1,2439 +0,0 @@ -<? -# Lifter002: TODO -# Lifter003: TEST -# Lifter007: TODO -# Lifter010: TODO -/** - * Seminar.php - This class represents a Seminar in Stud.IP - * - * This class provides functions for seminar-members, seminar-dates, and seminar-modules - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Till Glöggler <tgloeggl@uni-osnabrueck.de> - * @author Stefan Suchi <suchi@data-quest> - * @author Suchi & Berg GmbH <info@data-quest.de> - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -require_once 'lib/dates.inc.php'; - -class Seminar -{ - var $issues = null; // Array of Issue - var $irregularSingleDates = null; // Array of SingleDates - var $messages = []; // occured errors, infos, and warnings - var $semester = null; - var $filterStart = 0; - var $filterEnd = 0; - var $hasDatesOutOfDuration = -1; - var $message_stack = []; - - var $user_number = 0;//? - var $commands; //? - var $BookedRoomsStatTemp; //??? - - var $request_id;//TODO - var $requestData; - var $room_request; - - private $_metadate = null; // MetaDate - - private $alias = [ - 'seminar_number' => 'VeranstaltungsNummer', - 'subtitle' => 'Untertitel', - 'description' => 'Beschreibung', - 'location' => 'Ort', - 'misc' => 'Sonstiges', - 'read_level' => 'Lesezugriff', - 'write_level' => 'Schreibzugriff', - 'semester_start_time' => 'start_time', - 'semester_duration_time' => 'duration_time', - 'form' => 'art', - 'participants' => 'teilnehmer', - 'requirements' => 'vorrausetzungen', - 'orga' => 'lernorga', - ]; - - private $course = null; - - private $course_set = null; - - private static $seminar_object_pool; - - public static function GetInstance($id = false, $refresh_cache = false) - { - if ($id) { - if ($refresh_cache) { - self::$seminar_object_pool[$id] = null; - } - if (!empty(self::$seminar_object_pool[$id]) && is_object(self::$seminar_object_pool[$id]) && self::$seminar_object_pool[$id]->getId() == $id) { - return self::$seminar_object_pool[$id]; - } else { - self::$seminar_object_pool[$id] = new Seminar($id); - return self::$seminar_object_pool[$id]; - } - } else { - return new Seminar(false); - } - } - - public static function setInstance(Seminar $seminar) - { - return self::$seminar_object_pool[$seminar->id] = $seminar; - } - - /** - * Constructor - * - * Pass nothing to create a seminar, or the seminar_id from an existing seminar to change or delete - * @access public - * @param string $seminar_id the seminar to be retrieved - */ - public function __construct($course_or_id = FALSE) - { - $course = Course::toObject($course_or_id); - if ($course) { - $this->course = $course; - } elseif ($course_or_id === false) { - $this->course = new Course(); - $this->course->setId($this->course->getNewId()); - } else { //hmhmhm - throw new Exception(sprintf(_('Fehler: Konnte das Seminar mit der ID %s nicht finden!'), $course_or_id)); - } - } - - public function __get($field) - { - if ($field == 'is_new') { - return $this->course->isNew(); - } - if ($field == 'metadate') { - if ($this->_metadate === null) { - $this->_metadate = new MetaDate($this->id); - $this->_metadate->setSeminarStartTime($this->start_time); - $this->_metadate->setSeminarDurationTime($this->duration_time); - } - return $this->_metadate; - } - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return $this->course->$field; - } - - public function __set($field, $value) - { - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - if ($field == 'metadate') { - return $this->_metadate = $value; - } - return $this->course->$field = $value; - } - - public function __isset($field) - { - if ($field == 'metadate') { - return is_object($this->_metadate); - } - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return isset($this->course->$field); - } - - public function __call($method, $params) - { - return call_user_func_array([$this->course, $method], $params); - } - - public static function GetSemIdByDateId($date_id) - { - $stmt = DBManager::get()->prepare("SELECT range_id FROM termine WHERE termin_id = ? LIMIT 1"); - $stmt->execute([$date_id]); - return $stmt->fetchColumn(); - } - - /** - * - * creates an new id for this object - * @access private - * @return string the unique id - */ - public function createId() - { - return $this->course->getNewId(); - } - - public function getMembers($status = 'dozent') - { - $ret = []; - foreach($this->course->getMembersWithStatus($status) as $m) { - $ret[$m->user_id]['user_id'] = $m->user_id; - $ret[$m->user_id]['username'] = $m->username; - $ret[$m->user_id]['Vorname'] = $m->vorname; - $ret[$m->user_id]['Nachname'] = $m->nachname; - $ret[$m->user_id]['Email'] = $m->email; - $ret[$m->user_id]['position'] = $m->position; - $ret[$m->user_id]['label'] = $m->label; - $ret[$m->user_id]['status'] = $m->status; - $ret[$m->user_id]['mkdate'] = $m->mkdate; - $ret[$m->user_id]['fullname'] = $m->getUserFullname(); - } - return $ret; - } - - public function getAdmissionMembers($status = 'awaiting') - { - $ret = []; - foreach($this->course->admission_applicants->findBy('status', $status)->orderBy('position nachname') as $m) { - $ret[$m->user_id]['user_id'] = $m->user_id; - $ret[$m->user_id]['username'] = $m->username; - $ret[$m->user_id]['Vorname'] = $m->vorname; - $ret[$m->user_id]['Nachname'] = $m->nachname; - $ret[$m->user_id]['Email'] = $m->email; - $ret[$m->user_id]['position'] = $m->position; - $ret[$m->user_id]['status'] = $m->status; - $ret[$m->user_id]['mkdate'] = $m->mkdate; - $ret[$m->user_id]['fullname'] = $m->getUserFullname(); - } - return $ret; - } - - public function getId() - { - return $this->id; - } - - public function getName() - { - return $this->name; - } - - /** - * return the field VeranstaltungsNummer for the seminar - * - * @return string the seminar-number for the current seminar - */ - public function getNumber() - { - return $this->seminar_number; - } - - public function isVisible() - { - return $this->visible; - } - - public function getInstitutId() - { - return $this->institut_id; - } - - public function getSemesterStartTime() - { - return $this->semester_start_time; - } - - public function getSemesterDurationTime() - { - return $this->semester_duration_time; - } - - public function getNextDate($return_mode = 'string') - { - $next_date = ''; - if ($return_mode == 'int') { - echo __class__.'::'.__function__.', line '.__line__.', return_mode "int" ist not supported by this function!';die; - } - - if (!$termine = SeminarDB::getNextDate($this->id)) - return false; - - foreach ($termine['termin'] as $singledate_id) { - $next_date .= DateFormatter::formatDateAndRoom($singledate_id, $return_mode) . '<br>'; - } - - if (!empty($termine['ex_termin'])) { - foreach ($termine['ex_termin'] as $ex_termin_id) { - $ex_termin = new SingleDate($ex_termin_id); - $template = $GLOBALS['template_factory']->open('dates/missing_date.php'); - $template->formatted_date = DateFormatter::formatDateAndRoom($ex_termin_id, $return_mode); - $template->ex_termin = $ex_termin; - $missing_date = $template->render(); - - if (!empty($termine['termin'])) { - $termin = new SingleDate($termine['termin'][0]); - if ($ex_termin->getStartTime() <= $termin->getStartTime()) { - return $next_date . $missing_date; - } else { - return $next_date; - } - } else { - return $missing_date; - } - } - } else { - return $next_date; - } - - return false; - } - - public function getFirstDate($return_mode = 'string') { - if (!$dates = SeminarDB::getFirstDate($this->id)) { - return false; - } - - return DateFormatter::formatDateWithAllRooms(['termin' => $dates], $return_mode); - } - - /** - * This function returns an associative array of the dates owned by this seminar - * - * @returns mixed a multidimensional array of seminar-dates - */ - public function getUndecoratedData($filter = false) - { - - // Caching - $cache = \Studip\Cache\Factory::getCache(); - $cache_key = 'course/undecorated_data/'. $this->id; - - if ($filter) { - $sub_key = ($_SESSION['_language'] ?? 'none') .'/'. $this->filterStart .'-'. $this->filterEnd; - } else { - $sub_key = ($_SESSION['_language'] ?? 'none') .'/unfiltered'; - } - - $data = unserialize($cache->read($cache_key)); - - // build cache from scratch - if (empty($data) || empty($data[$sub_key])) { - $cycles = $this->metadate->getCycleData(); - $dates = $this->getSingleDates($filter, $filter); - $rooms = []; - - foreach (array_keys($cycles) as $id) { - if ($this->filterStart && $this->filterEnd - && !$this->metadate->hasDates($id, $this->filterStart, $this->filterEnd)) - { - unset($cycles[$id]); - continue; - } - - $cycles[$id]['first_date'] = CycleDataDB::getFirstDate($id); - $cycles[$id]['last_date'] = CycleDataDB::getLastDate($id); - if (!empty($cycles[$id]['assigned_rooms'])) { - foreach ($cycles[$id]['assigned_rooms'] as $room_id => $count) { - if (!isset($rooms[$room_id])) { - $rooms[$room_id] = 0; - } - $rooms[$room_id] += $count; - } - } - } - - // besser wieder mit direktem Query statt Objekten - if (is_array($cycles) && count($cycles) === 0) { - $cycles = false; - } - - $ret['regular']['turnus_data'] = $cycles; - - // the irregular single-dates - foreach ($dates as $val) { - $zw = [ - 'metadate_id' => $val->getMetaDateID(), - 'termin_id' => $val->getTerminID(), - 'date_typ' => $val->getDateType(), - 'start_time' => $val->getStartTime(), - 'end_time' => $val->getEndTime(), - 'mkdate' => $val->getMkDate(), - 'chdate' => $val->getMkDate(), - 'ex_termin' => $val->isExTermin(), - 'orig_ex' => $val->isExTermin(), - 'range_id' => $val->getRangeID(), - 'author_id' => $val->getAuthorID(), - 'resource_id' => $val->getResourceID(), - 'raum' => $val->getFreeRoomText(), - 'typ' => $val->getDateType(), - 'tostring' => $val->toString() - ]; - - if ($val->getResourceID()) { - if (!isset($rooms[$val->getResourceID()])) { - $rooms[$val->getResourceID()] = 0; - } - $rooms[$val->getResourceID()]++; - } - - $ret['irregular'][$val->getTerminID()] = $zw; - } - - $ret['rooms'] = $rooms; - $ret['ort'] = $this->location; - - $data[$sub_key] = $ret; - - // write data to cache - $cache->write($cache_key, serialize($data), 600); - } - - return $data[$sub_key]; - } - - public function getFormattedTurnus($short = FALSE) - { - // activate this with StEP 00077 - /* $cache = Cache::instance(); - * $cache_key = "formatted_turnus".$this->id; - * if (! $return_string = $cache->read($cache_key)) - * { - */ - return $this->getDatesExport(['short' => $short, 'shrink' => true]); - - // activate this with StEP 00077 - // $cache->write($cache_key, $return_string, 60*60); - // } - } - - public function getFormattedTurnusDates($short = FALSE) - { - if ($cycles = $this->metadate->getCycles()) { - $return_string = []; - foreach ($cycles as $id => $c) { - $return_string[$id] = $c->toString($short); - //hmm tja... - if ($c->description){ - $return_string[$id] .= ' ('. htmlReady($c->description) .')'; - } - } - return $return_string; - } else - return FALSE; - } - - public function getMetaDateCount() - { - return sizeof($this->metadate->cycles); - } - - public function getMetaDateValue($key, $value_name) - { - return $this->metadate->cycles[$key]->$value_name; - } - - public function setMetaDateValue($key, $value_name, $value) - { - $this->metadate->cycles[$key]->$value_name = $value; - } - - /** - * restore the data - * - * the complete data of the object will be loaded from the db - * @access public - * @throws Exception if there is no such course - * @return boolean always true - */ - public function restore() - { - if ($this->course->id) { - $this->course->restore(); - } - $this->irregularSingleDates = null; - $this->issues = null; - $this->_metadate = null; - $this->course_set = null; - - return TRUE; - } - - /** - * returns an array of variables from the seminar-object, excluding variables - * containing objects or arrays - * - * @return array - */ - public function getSettings() { - $settings = $this->course->toRawArray(); - unset($settings['config']); - return $settings; - } - - public function store($trigger_chdate = true) - { - // activate this with StEP 00077 - // $cache = Cache::instance(); - // $cache->expire("formatted_turnus".$this->id); - - //check for security consistency - if ($this->write_level < $this->read_level) // hier wusste ein Lehrender nicht, was er tat - $this->write_level = $this->read_level; - - if ($this->irregularSingleDates) { - foreach ($this->irregularSingleDates as $val) { - $val->store(); - } - } - - if ($this->issues) { - foreach ($this->issues as $val) { - $val->store(); - } - } - - $metadate_changed = isset($this->metadate) ? $this->metadate->store() : 0; - $course_changed = $this->course->store(); - if ($metadate_changed && $trigger_chdate) { - return $this->course->triggerChdate(); - } else { - return $course_changed ?: false; - } - } - - public function setStartSemester($start) - { - global $perm; - - if ($perm->have_perm('tutor') && $start != $this->semester_start_time) { - // logging >>>>>> - StudipLog::log("SEM_SET_STARTSEMESTER", $this->getId(), $start); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - $this->semester_start_time = $start; - $this->metadate->setSeminarStartTime($start); - $this->createMessage(_("Das Startsemester wurde geändert.")); - $this->createInfo(_("Beachten Sie, dass Termine, die nicht mit den Einstellungen der regelmäßigen Zeit übereinstimmen (z.B. auf Grund einer Verschiebung der regelmäßigen Zeit), teilweise gelöscht sein könnten!")); - return TRUE; - } - return FALSE; - } - - public function removeAndUpdateSingleDates() - { - SeminarCycleDate::removeOutRangedSingleDates( - $this->semester_start_time, - $this->getEndSemesterVorlesEnde(), - $this->id - ); - - foreach ($this->metadate->cycles as $key => $val) { - $this->metadate->cycles[$key]->readSingleDates(); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = NULL; - } - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - - public function getStartSemester() - { - return $this->semester_start_time; - } - - /* - * setEndSemester - * @param end integer 0 (one Semester), -1 (eternal), or timestamp of last happening semester - * @returns TRUE on success, FALSE on failure - */ - public function setEndSemester($end) - { - global $perm; - - $previousEndSemester = $this->getEndSemester(); // save the end-semester before it is changed, so we can choose lateron in which semesters we need to be rebuilt the SingleDates - - if ($end != $this->getEndSemester()) { // only change Duration if it differs from the current one - - if ($end == 0) { // the seminar takes place just in the selected start-semester - $this->semester_duration_time = 0; - $this->metadate->setSeminarDurationTime(0); - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: 1 Semester'); - // logging <<<<<< - } else if ($end == -1) { // the seminar takes place in every semester above and including the start-semester - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end, 'Laufzeit: unbegrenzt'); - // logging <<<<<< - $this->semester_duration_time = -1; - $this->metadate->setSeminarDurationTime(-1); - SeminarCycleDate::removeOutRangedSingleDates( - $this->semester_start_time, - $this->getEndSemesterVorlesEnde(), - $this->id - ); - } else { // the seminar takes place between the selected start~ and end-semester - // logging >>>>>> - StudipLog::log("SEM_SET_ENDSEMESTER", $this->getId(), $end); - // logging <<<<<< - $this->semester_duration_time = $end - $this->semester_start_time; // the duration is stored, not the real end-point - $this->metadate->setSeminarDurationTime($this->semester_duration_time); - } - - $this->createMessage(_("Die Dauer wurde geändert.")); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - - /* - * If the duration has been changed, we have to create new SingleDates - * if the new duration is longer than the previous one - */ - if ( ($previousEndSemester != -1) && ( ($previousEndSemester < $this->getEndSemester()) || (($previousEndSemester == 0) && ($this->getEndSemester() == -1) ) )) { - // if the previous duration was unlimited, the only option choosable is - // a shorter duration then 'ever', so there cannot be any new SingleDates - - // special case: if the previous selection was 'one semester' and the new one is 'eternal', - // than we have to find out the end of the only semester, the start-semester - if ($previousEndSemester == 0) { - $startAfterTimeStamp = $this->course->start_semester->ende; - } else { - $startAfterTimeStamp = $previousEndSemester; - } - - foreach ($this->metadate->cycles as $key => $val) { - $this->metadate->createSingleDates(['metadate_id' => $key, 'startAfterTimeStamp' => $startAfterTimeStamp]); - $this->metadate->cycles[$key]->termine = NULL; // emtpy the SingleDates for each cycle, so that SingleDates, which were not in the current view, are not loaded and therefore should not be visible - } - } - } - - return TRUE; - } - - /* - * getEndSemester - * @returns 0 (one Semester), -1 (eternal), or TimeStamp of last Semester for this Seminar - */ - public function getEndSemester() - { - if ($this->semester_duration_time == 0) return 0; // seminar takes place only in the start-semester - if ($this->semester_duration_time == -1) return -1; // seminar takes place eternally - return $this->semester_start_time + $this->semester_duration_time; // seminar takes place between start~ and end-semester - } - - public function getEndSemesterVorlesEnde() - { - if ($this->semester_duration_time == -1) { - $semesters = Semester::getAll(); - $very_last_semester = array_pop($semesters); - return $very_last_semester->vorles_ende; - } - return $this->course->end_semester->vorles_ende; - } - - /** - * return the name of the seminars start-semester - * - * @return string the name of the start-semester or false if there is no start-semester - */ - public function getStartSemesterName() - { - return $this->course->start_semester->name; - } - - /** - * return an array of singledate-objects for the submitted cycle identified by metadate_id - * - * @param string $metadate_id the id identifying the cycle - * - * @return mixed an array of singledate-objects - */ - public function readSingleDatesForCycle($metadate_id) - { - return $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - - public function readSingleDates($force = FALSE, $filter = FALSE) - { - if (!$force) { - if (is_array($this->irregularSingleDates)) { - return TRUE; - } - } - $this->irregularSingleDates = []; - - if ($filter) { - $data = SeminarDB::getSingleDates($this->id, $this->filterStart, $this->filterEnd); - } else { - $data = SeminarDB::getSingleDates($this->id); - } - - foreach ($data as $val) { - unset($termin); - $termin = new SingleDate(); - $termin->fillValuesFromArray($val); - $this->irregularSingleDates[$val['termin_id']] =& $termin; - } - } - - public function &getSingleDate($singleDateID, $cycle_id = '') - { - if ($cycle_id == '') { - $this->readSingleDates(); - return $this->irregularSingleDates[$singleDateID]; - } else { - $dates = $this->metadate->getSingleDates($cycle_id, $this->filterStart, $this->filterEnd); - $data =& $dates; - return $data[$singleDateID]; - } - } - - public function &getSingleDates($filter = false, $force = false, $include_deleted_dates = false) - { - $this->readSingleDates($force, $filter); - if (!$include_deleted_dates) { - return $this->irregularSingleDates; - } else { - $deleted_dates = []; - foreach (SeminarDB::getDeletedSingleDates($this->getId(), $this->filterStart, $this->filterEnd) as $val) { - $termin = new SingleDate(); - $termin->fillValuesFromArray($val); - $deleted_dates[$val['termin_id']] = $termin; - } - $dates = array_merge($this->irregularSingleDates, $deleted_dates); - uasort($dates, function($a,$b) { - if ($a->getStartTime() == $b->getStartTime()) return 0; - return $a->getStartTime() < $b->getStartTime() ? -1 : 1;} - ); - return $dates; - } - } - - public function getCycles() - { - return $this->metadate->getCycles(); - } - - public function &getSingleDatesForCycle($metadate_id) - { - if (!$this->metadate->cycles[$metadate_id]->termine) { - $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - if (!$this->metadate->cycles[$metadate_id]->termine) { - $this->readSingleDates(); - $this->metadate->createSingleDates($metadate_id, $this->irregularSingleDates); - $this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - //$this->metadate->readSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - } - $dates = $this->metadate->getSingleDates($metadate_id, $this->filterStart, $this->filterEnd); - return $dates; - } - - public function readIssues($force = false) - { - if (!is_array($this->issues) || $force) { - $this->issues = []; - $data = SeminarDB::getIssues($this->id); - - foreach ($data as $val) { - unset($issue); - $issue = new Issue(); - $issue->fillValuesFromArray($val); - $this->issues[$val['issue_id']] =& $issue; - } - } - } - - public function addSingleDate(&$singledate) - { - // logging >>>>>> - StudipLog::log("SEM_ADD_SINGLEDATE", $this->getId(), $singledate->toString(), 'SingleDateID: '.$singledate->getTerminID()); - // logging <<<<<< - - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/'. $this->getId()); - - $this->readSingleDates(); - $this->irregularSingleDates[$singledate->getSingleDateID()] =& $singledate; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - - public function addIssue(&$issue) - { - $this->readIssues(); - if ($issue instanceof Issue) { - $max = -1; - if (is_array($this->issues)) foreach ($this->issues as $val) { - if ($val->getPriority() > $max) { - $max = $val->getPriority(); - } - } - $max++; - $issue->setPriority($max); - $this->issues[$issue->getIssueID()] =& $issue; - return TRUE; - } else { - return FALSE; - } - } - - public function deleteSingleDate($date_id, $cycle_id = '') - { - $this->readSingleDates(); - // logging >>>>>> - StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); - // logging <<<<<< - if ($cycle_id == '') { - $this->irregularSingleDates[$date_id]->delete(true); - unset ($this->irregularSingleDates[$date_id]); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } else { - $this->metadate->deleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - } - - public function cancelSingleDate($date_id, $cycle_id = '') - { - if ($cycle_id) { - return $this->deleteSingleDate($date_id, $cycle_id); - } - $this->readSingleDates(); - // logging >>>>>> - StudipLog::log("SEM_DELETE_SINGLEDATE",$date_id, $this->getId(), 'appointment cancelled'); - // logging <<<<<< - $this->irregularSingleDates[$date_id]->setExTermin(true); - $this->irregularSingleDates[$date_id]->store(); - unset ($this->irregularSingleDates[$date_id]); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - return TRUE; - } - - public function unDeleteSingleDate($date_id, $cycle_id = '') - { - // logging >>>>>> - StudipLog::log("SEM_UNDELETE_SINGLEDATE",$date_id, $this->getId(), 'Cycle_id: '.$cycle_id); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - if ($cycle_id == '') { - $termin = new SingleDate($date_id); - if (!$termin->isExTermin()) { - return false; - } - $termin->setExTermin(false); - $termin->store(); - return true; - } else { - return $this->metadate->unDeleteSingleDate($cycle_id, $date_id, $this->filterStart, $this->filterEnd); - } - } - - /** - * return all stacked messages as a multidimensional array - * - * The array has the following structure: - * array( 'type' => ..., 'message' ... ) - * where type is one of error, info and success - * - * @return mixed the array of stacked messages - */ - public function getStackedMessages() - { - if ( is_array( $this->message_stack ) ) { - $ret = []; - - // cycle through message types and set title and details appropriate - foreach ($this->message_stack as $type => $messages ) { - switch ( $type ) { - case 'error': - $ret['error'] = [ - 'title' => _("Es sind Fehler/Probleme aufgetreten!"), - 'details' => $this->message_stack['error'] - ]; - break; - - case 'info': - $ret['info'] = [ - 'title' => implode('<br>', $this->message_stack['info']), - 'details' => [] - ]; - break; - - case 'success': - $ret['success'] = [ - 'title' => _("Ihre Änderungen wurden gespeichert!"), - 'details' => $this->message_stack['success'] - ]; - break; - } - } - - return $ret; - } - - return false; - } - - /** - * return the next stacked messag-string - * - * @return string a message-string - */ - public function getNextMessage() - { - if ($this->messages[0]) { - $ret = $this->messages[0]; - unset ($this->messages[0]); - sort($this->messages); - return $ret; - } - return FALSE; - } - - /** - * stack an error-message - * - * @param string $text the message to stack - */ - public function createError($text) - { - $this->messages[] = 'error§'.$text.'§'; - $this->message_stack['error'][] = $text; - } - - /** - * stack an info-message - * - * @param string $text the message to stack - */ - public function createInfo($text) - { - $this->messages[] = 'info§'.$text.'§'; - $this->message_stack['info'][] = $text; - } - - /** - * stack a success-message - * - * @param string $text the message to stack - */ - public function createMessage($text) - { - $this->messages[] = 'msg§'.$text.'§'; - $this->message_stack['success'][] = $text; - } - - /** - * add an array of messages to the message-stack - * - * @param mixed $messages array of pre-marked message-strings - * @param bool returns true on success - */ - public function appendMessages( $messages ) - { - if (!is_array($messages)) return false; - - foreach ( $messages as $type => $msgs ) { - foreach ($msgs as $msg) { - $this->message_stack[$type][] = $msg; - } - } - return true; - } - - public function addCycle($data = []) - { - $new_id = $this->metadate->addCycle($data); - if($new_id){ - $this->setStartWeek($data['startWeek'], $new_id); - $this->setTurnus($data['turnus'], $new_id); - } - // logging >>>>>> - if($new_id){ - $cycle_info = $this->metadate->cycles[$new_id]->toString(); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - StudipLog::log("SEM_ADD_CYCLE", $this->getId(), NULL, $cycle_info, '<pre>'.print_r($data,true).'</pre>'); - } - // logging <<<<<< - return $new_id; - } - - /** - * Change a regular timeslot of the seminar. The data is passed as an array - * conatining the following fields: - * start_stunde, start_minute, end_stunde, end_minute - * description, turnus, startWeek, day, sws - * - * @param array $data the cycle-data - * - * @return void - */ - public function editCycle($data = []) - { - $cycle = $this->metadate->cycles[$data['cycle_id']]; - $new_start = mktime($data['start_stunde'], $data['start_minute']); - $new_end = mktime($data['end_stunde'], $data['end_minute']); - $old_start = mktime($cycle->getStartStunde(),$cycle->getStartMinute()); - $old_end = mktime($cycle->getEndStunde(), $cycle->getEndMinute()); - $do_changes = false; - - // check, if the new timeslot exceeds the old one - if (($new_start < $old_start) || ($new_end > $old_end) || ($data['day'] != $cycle->day) ) { - $has_bookings = false; - - // check, if there are any booked rooms - foreach($cycle->getSingleDates() as $singleDate) { - if ($singleDate->getStarttime() > (time() - 3600) && $singleDate->hasRoom()) { - $has_bookings = true; - break; - } - } - - // if the timeslot exceeds the previous one and has some booked rooms - // they would be lost, so ask the user for permission to do so. - if (!$data['really_change'] && $has_bookings) { - $link_params = [ - 'editCycle_x' => '1', - 'editCycle_y' => '1', - 'cycle_id' => $data['cycle_id'], - 'start_stunde' => $data['start_stunde'], - 'start_minute' => $data['start_minute'], - 'end_stunde' => $data['end_stunde'], - 'end_minute' => $data['end_minute'], - 'day' => $data['day'], - 'really_change' => 'true' - ]; - $question = _("Wenn Sie die regelmäßige Zeit auf %s ändern, verlieren Sie die Raumbuchungen für alle in der Zukunft liegenden Termine!") - ."\n". _("Sind Sie sicher, dass Sie die regelmäßige Zeit ändern möchten?"); - $question_time = '**'. strftime('%A', $data['day']) .', '. $data['start_stunde'] .':'. $data['start_minute'] - .' - '. $data['end_stunde'] .':'. $data['end_minute'] .'**'; - - echo (string)QuestionBox::create( - sprintf($question, $question_time), - URLHelper::getURL('', $link_params) - ); - - } else { - $do_changes = true; - } - } else { - $do_changes = true; - } - - $messages = false; - $same_time = false; - - // only apply changes, if the user approved the change or - // the change does not need any approval - if ($do_changes) { - if ($data['description'] != $cycle->getDescription()) { - $this->createMessage(_("Die Beschreibung des regelmäßigen Eintrags wurde geändert.")); - $message = true; - $do_changes = true; - } - - if ($old_start == $new_start && $old_end == $new_end) { - $same_time = true; - } - if ($data['startWeek'] != $cycle->week_offset) { - $this->setStartWeek($data['startWeek'], $cycle->metadate_id); - $message = true; - $do_changes = true; - } - if ($data['turnus'] != $cycle->cycle) { - $this->setTurnus($data['turnus'], $cycle->metadate_id); - $message = true; - $do_changes = true; - } - if ($data['day'] != $cycle->day) { - $message = true; - $same_time = false; - $do_changes = true; - } - if (round(str_replace(',','.', $data['sws']),1) != $cycle->sws) { - $cycle->sws = $data['sws']; - $this->createMessage(_("Die Semesterwochenstunden für Lehrende des regelmäßigen Eintrags wurden geändert.")); - $message = true; - $do_changes = true; - } - - $change_from = $cycle->toString(); - if ($this->metadate->editCycle($data)) { - if (!$same_time) { - // logging >>>>>> - StudipLog::log("SEM_CHANGE_CYCLE", $this->getId(), NULL, $change_from .' -> '. $cycle->toString()); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - $this->createMessage(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" für alle in der Zukunft liegenden Termine geändert!"), - '<b>'.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. - $data['end_stunde'] . ':' . $data['end_minute'] . '</b>')); - $message = true; - } - } else { - if (!$same_time) { - $this->createInfo(sprintf(_("Die regelmäßige Veranstaltungszeit wurde auf \"%s\" geändert, jedoch gab es keine Termine die davon betroffen waren."), - '<b>'.getWeekday($data['day']) . ', ' . $data['start_stunde'] . ':' . $data['start_minute'].' - '. - $data['end_stunde'] . ':' . $data['end_minute'] . '</b>')); - $message = true; - } - } - $this->metadate->sortCycleData(); - - if (!$message) { - $this->createInfo("Sie haben keine Änderungen vorgenommen!"); - } - } - } - - public function deleteCycle($cycle_id) - { - // logging >>>>>> - $cycle_info = $this->metadate->cycles[$cycle_id]->toString(); - StudipLog::log("SEM_DELETE_CYCLE", $this->getId(), NULL, $cycle_info); - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - // logging <<<<<< - return $this->metadate->deleteCycle($cycle_id); - } - - public function setTurnus($turnus, $metadate_id = false) - { - if ($this->metadate->getTurnus($metadate_id) != $turnus) { - $this->metadate->setTurnus($turnus, $metadate_id); - $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; - $this->createMessage(sprintf(_("Der Turnus für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = null; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - return TRUE; - } - - public function getTurnus($metadate_id = false) - { - return $this->metadate->getTurnus($metadate_id); - } - - - /** - * get StatOfNotBookedRooms returns an array: - * open: number of rooms with no booking - * all: number of singleDates, which can have a booking - * open_rooms: array of singleDates which have no booking - * - * @param String $cycle_id Id of cycle - * @return array as described above - */ - public function getStatOfNotBookedRooms($cycle_id) - { - if (!isset($this->BookedRoomsStatTemp[$cycle_id])) { - $this->BookedRoomsStatTemp[$cycle_id] = SeminarDB::getStatOfNotBookedRooms($cycle_id, $this->id, $this->filterStart, $this->filterEnd); - } - return $this->BookedRoomsStatTemp[$cycle_id]; - } - - public function getStatus() - { - return $this->status; - } - - public function getBookedRoomsTooltip($cycle_id) - { - $stat = $this->getStatOfNotBookedRooms($cycle_id); - $pattern = '%s , %s, %s-%s <br />'; - $return = ''; - if ($stat['open'] > 0 && $stat['open'] !== $stat['all']) { - $return = _('Folgende Termine haben keine Raumbuchung:') . '<br />'; - - foreach ($stat['open_rooms'] as $aSingleDate) { - $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), - strftime('%d.%m.%Y', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['end_time'])); - } - } - - // are there any dates with declined room-requests? - if ($stat['declined'] > 0) { - $return .= _('Folgende Termine haben eine abgelehnte Raumanfrage') . '<br />'; - foreach ($stat['declined_dates'] as $aSingleDate) { - $return .= sprintf($pattern,strftime('%a', $aSingleDate['date']), - strftime('%d.%m.%Y', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['date']), - strftime('%H:%M', $aSingleDate['end_time'])); - } - } - - return $return; - } - - /** - * @param $cycle_id - * @return string - */ - public function getCycleColorClass($cycle_id) - { - if (Config::get()->RESOURCES_ENABLE && Config::get()->RESOURCES_ENABLE_BOOKINGSTATUS_COLORING) { - if (!$this->metadate->hasDates($cycle_id, $this->filterStart, $this->filterEnd)) { - return 'red'; - } - - $stat = $this->getStatOfNotBookedRooms($cycle_id); - - if ($stat['open'] > 0 && $stat['open'] == $stat['all']) { - return 'red'; - } - if ($stat['open'] > 0) { - return 'yellow '; - } - return 'green '; - } - - return ''; - } - - public function &getIssues($force = false) - { - $this->readIssues($force); - $this->renumberIssuePrioritys(); - if (is_array($this->issues)) { - uasort($this->issues, function ($a, $b) { - return $a->getPriority() - $b->getPriority(); - }); - } - return $this->issues; - } - - public function deleteIssue($issue_id) - { - $this->issues[$issue_id]->delete(); - unset($this->issues[$issue_id]); - return TRUE; - } - - public function &getIssue($issue_id) - { - $this->readIssues(); - return $this->issues[$issue_id]; - } - - /* - * changeIssuePriority - * - * changes an issue with an given id to a new priority - * - * @param - * issue_id the issue_id of the issue to be changed - * new_priority the new priority - */ - public function changeIssuePriority($issue_id, $new_priority) - { - /* REMARK: - * This function only works, when an issue is moved ONE slote higher or lower - * It does NOT work with ARBITRARY movements! - */ - $this->readIssues(); - $old_priority = $this->issues[$issue_id]->getPriority(); // get old priority, so we can just exchange prioritys of two issues - foreach ($this->issues as $id => $issue) { // search for the concuring issue - if ($issue->getPriority() == $new_priority) { - $this->issues[$id]->setPriority($old_priority); // the concuring issue gets the old id of the changed issue - $this->issues[$id]->store(); // ###store_problem### - } - } - - $this->issues[$issue_id]->setPriority($new_priority); // changed issue gets the new priority - $this->issues[$issue_id]->store(); // ###store_problem### - - } - - public function renumberIssuePrioritys() - { - if (is_array($this->issues)) { - - $sorter = []; - foreach ($this->issues as $id => $issue) { - $sorter[$id] = $issue->getPriority(); - } - asort($sorter); - $i = 0; - foreach ($sorter as $id => $old_priority) { - $this->issues[$id]->setPriority($i); - $i++; - } - } - } - - public function autoAssignIssues($themen, $cycle_id) - { - $this->metadate->cycles[$cycle_id]->autoAssignIssues($themen, $this->filterStart, $this->filterEnd); - } - - - public function applyTimeFilter($start, $end) - { - $this->filterStart = $start; - $this->filterEnd = $end; - } - - public function setFilter($timestamp) - { - if ($timestamp == 'all') { - $_SESSION['raumzeitFilter'] = 'all'; - $this->applyTimeFilter(0, 0); - } else { - $filterSemester = Semester::findByTimestamp($timestamp); - $_SESSION['raumzeitFilter'] = $filterSemester->beginn; - $this->applyTimeFilter($filterSemester->beginn, $filterSemester->ende); - } - } - - public function registerCommand($command, $function) - { - $this->commands[$command] = $function; - } - - public function processCommands() - { - global $cmd; - - // workaround for multiple submit-buttons with new Button-API - foreach ($this->commands as $r_cmd => $func) { - if (Request::submitted($r_cmd)) { - $cmd = $r_cmd; - } - } - - if (!isset($cmd) && Request::option('cmd')) $cmd = Request::option('cmd'); - if (!isset($cmd)) return FALSE; - - if (isset($this->commands[$cmd])) { - call_user_func($this->commands[$cmd], $this); - } - } - - public function getFreeTextPredominantRoom($cycle_id) - { - if (!($room = $this->metadate->cycles[$cycle_id]->getFreeTextPredominantRoom($this->filterStart, $this->filterEnd))) { - return FALSE; - } - return $room; - } - - public function getPredominantRoom($cycle_id, $list = FALSE) - { - if (!($rooms = $this->metadate->cycles[$cycle_id]->getPredominantRoom($this->filterStart, $this->filterEnd))) { - return FALSE; - } - if ($list) { - return $rooms; - } else { - return $rooms[0]; - } - } - - - public function hasDatesOutOfDuration($force = false) - { - if ($this->hasDatesOutOfDuration == -1 || $force) { - $this->hasDatesOutOfDuration = SeminarDB::hasDatesOutOfDuration($this->getStartSemester(), $this->getEndSemesterVorlesEnde(), $this->id); - } - return $this->hasDatesOutOfDuration; - } - - public function getStartWeek($metadate_id = false) - { - return $this->metadate->getStartWoche($metadate_id); - } - - public function setStartWeek($week, $metadate_id = false) - { - if ($this->metadate->getStartWoche($metadate_id) == $week) { - return FALSE; - } else { - $this->metadate->setStartWoche($week, $metadate_id); - $key = $metadate_id ? $metadate_id : $this->metadate->getFirstMetadate()->metadate_id; - $this->createMessage(sprintf(_("Die Startwoche für den Termin %s wurde geändert."), $this->metadate->cycles[$key]->toString())); - $this->metadate->createSingleDates($key); - $this->metadate->cycles[$key]->termine = null; - NotificationCenter::postNotification("CourseDidChangeSchedule", $this); - } - } - - - /** - * instance method - * - * returns number of participants for each usergroup in seminar, - * total, lecturers, tutors, authors, users - * - * @param string (optional) return count only for given usergroup - * - * @return array <description> - */ - - public function getNumberOfParticipants() - { - $args = func_get_args(); - array_unshift($args, $this->id); - return call_user_func_array(["Seminar", "getNumberOfParticipantsBySeminarId"], $args); - } - - /** - * class method - * - * returns number of participants for each usergroup in given seminar, - * total, lecturers, tutors, authors, users - * - * @param string seminar_id - * - * @param string (optional) return count only for given usergroup - * - * @return array <description> - */ - - public function getNumberOfParticipantsBySeminarId($sem_id) - { - $db = DBManager::get(); - $stmt1 = $db->prepare("SELECT - COUNT(Seminar_id) AS anzahl, - COUNT(IF(status='dozent',Seminar_id,NULL)) AS anz_dozent, - COUNT(IF(status='tutor',Seminar_id,NULL)) AS anz_tutor, - COUNT(IF(status='autor',Seminar_id,NULL)) AS anz_autor, - COUNT(IF(status='user',Seminar_id,NULL)) AS anz_user - FROM seminar_user - WHERE Seminar_id = ? - GROUP BY Seminar_id"); - $stmt1->execute([$sem_id]); - $numbers = $stmt1->fetch(PDO::FETCH_ASSOC); - - $stmt2 = $db->prepare("SELECT COUNT(*) as anzahl - FROM admission_seminar_user - WHERE seminar_id = ? - AND status = 'accepted'"); - $stmt2->execute([$sem_id]); - $acceptedUsers = $stmt2->fetch(PDO::FETCH_ASSOC); - - - $count = 0; - if ($numbers["anzahl"]) { - $count += $numbers["anzahl"]; - } - if ($acceptedUsers["anzahl"]) { - $count += $acceptedUsers["anzahl"]; - } - - $participant_count = []; - $participant_count['total'] = $count; - $participant_count['lecturers'] = $numbers['anz_dozent'] ? (int) $numbers['anz_dozent'] : 0; - $participant_count['tutors'] = $numbers['anz_tutor'] ? (int) $numbers['anz_tutor'] : 0; - $participant_count['authors'] = $numbers['anz_autor'] ? (int) $numbers['anz_autor'] : 0; - $participant_count['users'] = $numbers['anz_user'] ? (int) $numbers['anz_user'] : 0; - - // return specific parameter if - $params = func_get_args(); - if (sizeof($params) > 1) { - if (in_array($params[1], array_keys($participant_count))) { - return $participant_count[$params[1]]; - } else { - trigger_error(get_class($this)."::__getParticipantInfos - unknown parameter requested"); - } - } - - return $participant_count; - } - - - /** - * Returns the IDs of this course's study areas. - * - * @return array an array of IDs - */ - public function getStudyAreas() - { - $stmt = DBManager::get()->prepare("SELECT DISTINCT sem_tree_id ". - "FROM seminar_sem_tree ". - "WHERE seminar_id=?"); - - $stmt->execute([$this->id]); - return $stmt->fetchAll(PDO::FETCH_COLUMN, 0); - } - - /** - * Sets the study areas of this course. - * - * @param array an array of IDs - * - * @return void - */ - public function setStudyAreas($selected) - { - $old = $this->getStudyAreas(); - $sem_tree = TreeAbstract::GetInstance("StudipSemTree"); - $removed = array_diff($old, $selected); - $added = array_diff($selected, $old); - $count_removed = 0; - $count_added = 0; - foreach($removed as $one){ - $count_removed += $sem_tree->DeleteSemEntries($one, $this->getId()); - } - foreach($added as $one){ - $count_added += $sem_tree->InsertSemEntry($one, $this->getId()); - } - if ($count_added || $count_removed) { - NotificationCenter::postNotification("CourseDidChangeStudyArea", $this); - } - return count($old) + $count_added - $count_removed; - } - - /** - * @return boolean returns TRUE if this course is publicly visible, - * FALSE otherwise - */ - public function isPublic() - { - return Config::get()->ENABLE_FREE_ACCESS && $this->read_level == 0; - } - - /** - * @return boolean returns TRUE if this course is a studygroup, - * FALSE otherwise - */ - public function isStudygroup() - { - global $SEM_CLASS, $SEM_TYPE; - return $SEM_CLASS[$SEM_TYPE[$this->status]["class"]]["studygroup_mode"]; - } - - /** - * @return int returns default colour group for new members (shown in meine_seminare.php) - * - **/ - public function getDefaultGroup() - { - if ($this->isStudygroup()) { - return 8; - } else { - return select_group ($this->semester_start_time); - } - } - - - /** - * Deletes the current seminar - * - * @return void returns success-message if seminar could be deleted - * otherwise an error-message - */ - - public function delete() - { - $s_id = $this->id; - - // Delete that Seminar. - - // Alle Benutzer aus dem Seminar rauswerfen. - $db_ar = CourseMember::deleteBySQL('Seminar_id = ?', [$s_id]); - if ($db_ar > 0) { - $this->createMessage(sprintf(_("%s Teilnehmende und Lehrende archiviert."), $db_ar)); - } - - // Alle Benutzer aus Wartelisten rauswerfen - AdmissionApplication::deleteBySQL('seminar_id = ?', [$s_id]); - - // Alle beteiligten Institute rauswerfen - $query = "DELETE FROM seminar_inst WHERE Seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - if (($db_ar = $statement->rowCount()) > 0) { - $this->createMessage(sprintf(_("%s Zuordnungen zu Einrichtungen archiviert."), $db_ar)); - } - - // user aus den Statusgruppen rauswerfen - $count = Statusgruppen::deleteBySQL('range_id = ?', [$s_id]); - if ($count > 0) { - $this->createMessage(sprintf(_('%s Funktionen/Gruppen gelöscht.'), $count)); - } - - // seminar_sem_tree entries are deleted automatically on deletion of the Course object. - - // Alle Termine mit allem was dranhaengt zu diesem Seminar loeschen. - if (($db_ar = SingleDateDB::deleteAllDates($s_id)) > 0) { - $this->createMessage(sprintf(_("%s Veranstaltungstermine archiviert."), $db_ar)); - } - - //Themen - IssueDB::deleteAllIssues($s_id); - - //Cycles - SeminarCycleDate::deleteBySQL('seminar_id = ' . DBManager::get()->quote($s_id)); - - // Alle weiteren Postings zu diesem Seminar in den Forums-Modulen löschen - foreach (PluginEngine::getPlugins(ForumModule::class) as $plugin) { - $plugin->deleteContents($s_id); // delete content irrespective of plugin-activation in the seminar - - if ($plugin->isActivated($s_id)) { // only show a message, if the plugin is activated, to not confuse the user - $this->createMessage(sprintf(_('Einträge in %s archiviert.'), $plugin->getPluginName())); - } - } - - // Alle Pluginzuordnungen entfernen - PluginManager::getInstance()->deactivateAllPluginsForRange('sem', $s_id); - - // Alle Dokumente zu diesem Seminar loeschen. - $folder = Folder::findTopFolder($s_id); - if($folder) { - if($folder->delete()) { - $this->createMessage(_("Dokumente und Ordner archiviert.")); - } - } - - - // Freie Seite zu diesem Seminar löschen - $db_ar = StudipScmEntry::deleteBySQL('range_id = ?', [$s_id]); - if ($db_ar > 0) { - $this->createMessage(_("Freie Seite der Veranstaltung archiviert.")); - } - - // Alle News-Verweise auf dieses Seminar löschen - if ( ($db_ar = StudipNews::DeleteNewsRanges($s_id)) ) { - $this->createMessage(sprintf(_("%s Ankündigungen gelöscht."), $db_ar)); - } - //delete entry in news_rss_range - StudipNews::UnsetRssId($s_id); - - //kill the datafields - DataFieldEntry::removeAll($s_id); - - //kill all wiki-pages - $db_wiki = WikiPage::deleteBySQL('range_id = ?', [$s_id]); - if ($db_wiki > 0) { - $this->createMessage(sprintf(_("%s Wiki-Seiten archiviert."), $db_wiki)); - } - - $query = "DELETE FROM wiki_links WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - - // delete course config values - ConfigValue::deleteBySQL('range_id = ?', [$s_id]); - - // kill all the ressources that are assigned to the Veranstaltung (and all the linked or subordinated stuff!) - if (Config::get()->RESOURCES_ENABLE) { - ResourceBooking::deleteBySql( - 'range_id = :course_id', - [ - 'course_id' => $s_id - ] - ); - if ($rr = RoomRequest::existsByCourse($s_id)) { - RoomRequest::find($rr)->delete(); - } - } - - // kill virtual seminar-entries in calendar - $query = "DELETE FROM schedule_seminare WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - - if(Config::get()->ELEARNING_INTERFACE_ENABLE){ - global $connected_cms; - $del_cms = 0; - $cms_types = ObjectConnections::GetConnectedSystems($s_id); - if(count($cms_types)){ - foreach($cms_types as $system){ - ELearningUtils::loadClass($system); - $del_cms += $connected_cms[$system]->deleteConnectedModules($s_id); - } - $this->createMessage(sprintf(_("%s Verknüpfungen zu externen Systemen gelöscht."), $del_cms )); - } - } - - //kill the object_user_vists for this seminar - object_kill_visits(null, $s_id); - - // Logging... - $query = "SELECT CONCAT(seminare.VeranstaltungsNummer, ' ', seminare.name, '(', semester_data.name, ')') - FROM seminare - LEFT JOIN semester_data ON (seminare.start_time = semester_data.beginn) - WHERE seminare.Seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$s_id]); - $semlogname = $statement->fetchColumn() ?: sprintf('unknown sem_id: %s', $s_id); - - StudipLog::log("SEM_ARCHIVE",$s_id,NULL,$semlogname); - // ...logged - - // delete deputies if necessary - Deputy::deleteByRange_id($s_id); - - UserDomain::removeUserDomainsForSeminar($s_id); - - AutoInsert::deleteSeminar($s_id); - - //Anmeldeset Zordnung entfernen - $cs = $this->getCourseSet(); - if ($cs) { - CourseSet::removeCourseFromSet($cs->getId(), $this->getId()); - $cs->load(); - if (!count($cs->getCourses()) - && $cs->isGlobal() - && $cs->getUserid() != '') { - $cs->delete(); - } - } - AdmissionPriority::unsetAllPrioritiesForCourse($this->getId()); - // und das Seminar loeschen. - $this->course->delete(); - $this->restore(); - return true; - } - - /** - * returns a html representation of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the html-representation of the dates - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - public function getDatesHTML($params = []) - { - return $this->getDatesTemplate('dates/seminar_html.php', $params); - } - - /** - * returns a representation without html of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the representation of the dates without html - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - public function getDatesExport($params = []) - { - return $this->getDatesTemplate('dates/seminar_export.php', $params); - } - - /** - * returns a xml-representation of the seminar-dates - * - * @param array optional variables which are passed to the template - * @return string the xml-representation of the dates - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - public function getDatesXML($params = []) - { - return $this->getDatesTemplate('dates/seminar_xml.php', $params); - } - - /** - * returns a representation of the seminar-dates with a specifiable template - * - * @param mixed this can be a template-object or a string pointing to a template in path_to_studip/templates - * @param array optional parameters which are passed to the template - * @return string the template output of the dates - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - public function getDatesTemplate($template, $params = []) - { - if (!$template instanceof Flexi\Template && is_string($template)) { - $template = $GLOBALS['template_factory']->open($template); - } - - if (!empty($params['semester_id'])) { - $semester = Semester::find($params['semester_id']); - if ($semester) { - // apply filter - $this->applyTimeFilter($semester->beginn, $semester->ende); - } - } - - $template->dates = $this->getUndecoratedData(isset($params['semester_id'])); - $template->seminar_id = $this->getId(); - - $template->set_attributes($params); - return trim($template->render()); - } - - /** - * returns an asscociative array with the attributes of the seminar depending - * on the field-names in the database - * @return array - */ - public function getData() - { - $data = $this->course->toArray(); - foreach($this->alias as $a => $o) { - $data[$a] = $this->course->$o; - } - return $data; - } - - /** - * returns an array with all IDs of Institutes this seminar is related to - * @param sem_id string: optional ID of a seminar, when null, this ID will be used - * @return: array of IDs (not associative) - */ - public function getInstitutes($sem_id = null) - { - if (!$sem_id && $this) { - $sem_id = $this->id; - } - - $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = :sem_id - UNION - SELECT Institut_id FROM seminare WHERE Seminar_id = :sem_id"; - $statement = DBManager::get()->prepare($query); - $statement->execute(compact('sem_id')); - return $statement->fetchAll(PDO::FETCH_COLUMN); - } - - /** - * set the entries for seminar_inst table in database - * seminare.institut_id will always be added - * @param institutes array: array of Institut_id's - * @return bool: if something changed - */ - public function setInstitutes($institutes = []) - { - if (is_array($institutes)) { - $institutes[] = $this->institut_id; - $institutes = array_unique($institutes); - - $query = "SELECT institut_id FROM seminar_inst WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - $old_inst = $statement->fetchAll(PDO::FETCH_COLUMN); - - $todelete = array_diff($old_inst, $institutes); - - $query = "DELETE FROM seminar_inst WHERE seminar_id = ? AND institut_id = ?"; - $statement = DBManager::get()->prepare($query); - - foreach($todelete as $inst) { - $tmp_instname= get_object_name($inst, 'inst'); - StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde gelöscht.'); - $statement->execute([$this->id, $inst]); - NotificationCenter::postNotification('SeminarInstitutionDidDelete', $inst, $this->id); - - } - - $toinsert = array_diff($institutes, $old_inst); - - $query = "INSERT INTO seminar_inst (seminar_id, institut_id) VALUES (?, ?)"; - $statement = DBManager::get()->prepare($query); - - foreach($toinsert as $inst) { - $tmp_instname= get_object_name($inst, 'inst'); - StudipLog::log('CHANGE_INSTITUTE_DATA', $this->id, $inst, 'Die beteiligte Einrichtung "'. $tmp_instname['name'] .'" wurde hinzugefügt.'); - $statement->execute([$this->id, $inst]); - NotificationCenter::postNotification('SeminarInstitutionDidCreate', $inst, $this->id); - } - if ($todelete || $toinsert) { - NotificationCenter::postNotification("CourseDidChangeInstitutes", $this); - } - return $todelete || $toinsert; - } else { - $this->createError(_("Ungültige Eingabe der Institute. Es muss " . - "mindestens ein Institut angegeben werden.")); - return false; - } - } - - /** - * adds a user to the seminar with the given status - * @param user_id string: ID of the user - * @param status string: status of the user for the seminar "user", "autor", "tutor", "dozent" - * @param force bool: if false (default) the user will only be upgraded and not degraded in his/her status - */ - public function addMember($user_id, $status = 'autor', $force = false) - { - - if (in_array($GLOBALS['perm']->get_perm($user_id), ["admin", "root"])) { - $this->createError(_("Admin und Root dürfen nicht Mitglied einer Veranstaltung sein.")); - return false; - } - $db = DBManager::get(); - - $rangordnung = array_flip(['user', 'autor', 'tutor', 'dozent']); - if ($rangordnung[$status] > $rangordnung['autor'] && SeminarCategories::getByTypeId($this->status)->only_inst_user) { - //überprüfe, ob im richtigen Institut: - $user_institute_stmt = $db->prepare( - "SELECT Institut_id " . - "FROM user_inst " . - "WHERE user_id = :user_id " . - ""); - $user_institute_stmt->execute(['user_id' => $user_id]); - $user_institute = $user_institute_stmt->fetchAll(PDO::FETCH_COLUMN, 0); - - if (!in_array($this->institut_id, $user_institute) && !count(array_intersect($user_institute, $this->getInstitutes()))) { - $this->createError(_("Einzutragender Nutzer stammt nicht einem beteiligten Institut an.")); - - return false; - } - } - $course_member = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $this->id]); - $new_position = (int) DBManager::get()->fetchColumn( - "SELECT MAX(position) + 1 FROM seminar_user WHERE status = ? AND Seminar_id = ?", - [$status, $this->id] - ); - $numberOfTeachers = CourseMember::countBySql("Seminar_id = ? AND status = 'dozent'", [$this->id]); - - if (!$course_member && !$force) { - CourseMember::create([ - 'Seminar_id' => $this->id, - 'user_id' => $user_id, - 'status' => $status, - 'position' => $new_position?:0, - 'gruppe' => (int) select_group($this->getSemesterStartTime()), - 'visible' => in_array($status, ['tutor', 'dozent']) ? 'yes' : 'unknown', - ]); - // delete the entries, user is now in the seminar - if (AdmissionApplication::deleteBySQL('user_id = ? AND seminar_id = ?', [$user_id, $this->getId()])) { - //renumber the waiting/accepted/lot list, a user was deleted from it - AdmissionApplication::renumberAdmission($this->getId()); - } - $cs = $this->getCourseSet(); - if ($cs) { - AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); - } - - CalendarScheduleModel::deleteSeminarEntries($user_id, $this->getId()); - NotificationCenter::postNotification('CourseDidGetMember', $this, $user_id); - NotificationCenter::postNotification('UserDidEnterCourse', $this->id, $user_id); - StudipLog::log('SEM_USER_ADD', $this->id, $user_id, $status, 'Wurde in die Veranstaltung eingetragen'); - $this->course->resetRelation('members'); - $this->course->resetRelation('admission_applicants'); - - // Check if we need to add user to parent course as well. - if ($this->parent_course) { - $parent = new Seminar($this->parent); - $parent->addMember($user_id, $status, $force); - } - - return $this; - } elseif ( - ($force || $rangordnung[$course_member->status] < $rangordnung[$status]) - && ($course_member->status !== 'dozent' || $numberOfTeachers > 1) - ) { - $visibility = $course_member->visible; - if (in_array($status, ['tutor', 'dozent'])) { - $visibility = 'yes'; - } - $course_member->status = $status; - $course_member->visible = $visibility; - $course_member->position = $new_position; - $course_member->store(); - - if ($course_member->status === 'dozent') { - $termine = DBManager::get()->fetchFirst( - "SELECT termin_id FROM termine WHERE range_id = ?", - [$this->id] - ); - - DBManager::get()->execute( - "DELETE FROM termin_related_persons WHERE range_id IN (?) AND user_id = ?", - [$termine, $user_id] - ); - } - NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); - $this->course->resetRelation('members'); - $this->course->resetRelation('admission_applicants'); - return $this; - } else { - if ($course_member->status === 'dozent' && $numberOfTeachers <= 1) { - $this->createError(sprintf(_('Die Person kann nicht herabgestuft werden, ' . -'da mindestens ein/eine Veranstaltungsleiter/-in (%s) in die Veranstaltung eingetragen sein muss!'), - get_title_for_status('dozent', 1, $this->status)) . - ' ' . sprintf(_('Tragen Sie zunächst eine weitere Person als Veranstaltungsleiter/-in (%s) ein.'), -get_title_for_status('dozent', 1, $this->status))); - } - - return false; - } - } - - /** - * Cancels a subscription to an admission. - * - * @param array $users - * @param string $status - * @return array - * @throws NotificationVetoException - */ - public function cancelAdmissionSubscription(array $users, string $status): array - { - $msgs = []; - $messaging = new messaging; - $course_set = $this->getCourseSet(); - $users = User::findMany($users); - foreach ($users as $user) { - $prio_delete = false; - if ($course_set) { - $prio_delete = AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->getId()); - } - $result = AdmissionApplication::deleteBySQL( - 'seminar_id = ? AND user_id = ? AND status = ?', - [$this->getId(), $user->id, $status] - ); - if ($result || $prio_delete) { - setTempLanguage($user->id); - if ($status !== 'accepted') { - $message = sprintf( - _('Sie wurden von der Warteliste der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), - $this->getFullName() - ); - } else { - $message = sprintf( - _('Sie wurden aus der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'), - $this->getFullName() - ); - } - restoreLanguage(); - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _('nicht zugelassen in Veranstaltung')), - true - ); - StudipLog::log('SEM_USER_DEL', $this->getId(), $user->id, 'Wurde aus der Veranstaltung entfernt'); - NotificationCenter::postNotification('UserDidLeaveCourse', $this->getId(), $user->id); - - $msgs[] = $user->getFullName(); - } - } - return $msgs; - } - - /** - * Cancels a subscription to a course - * @param array $users - * @return array - * @throws Exception - */ - public function cancelSubscription(array $users): array - { - $msgs = []; - $messaging = new messaging; - $users = User::findMany($users); - foreach ($users as $user) { - // delete member from seminar - if ($this->deleteMember($user->id)) { - setTempLanguage($user->id); - $message = sprintf( - _('Ihre Anmeldung zur Veranstaltung **%s** wurde aufgehoben.'), - $this->getFullName() - ); - restoreLanguage(); - $messaging->insert_message( - $message, - $user->username, - '____%system%____', - false, - false, - '1', - false, - sprintf('%s %s', _('Systemnachricht:'), _("Anmeldung aufgehoben")), - true - ); - $msgs[] = $user->getFullName(); - } - } - - return $msgs; - } - - /** - * deletes a user from the seminar by respecting the rule that at least one - * user with status "dozent" must stay there - * @param string $user_id user_id of the user to delete - * @return boolean - */ - public function deleteMember($user_id): bool - { - $dozenten = $this->getMembers(); - if (count($dozenten) >= 2 || empty($dozenten[$user_id])) { - $result = CourseMember::deleteBySQL('Seminar_id = ? AND user_id = ?', [$this->id, $user_id]); - if ($result === 0) { - return true; - } - // If this course is a child of another course... - if ($this->parent_course) { - // ... check if user is member in another sibling ... - $other = CourseMember::countBySQL( - "`user_id` = :user AND `Seminar_id` IN (:courses) AND `Seminar_id` != :this", - ['user' => $user_id, 'courses' => $this->parent->children->pluck('seminar_id'), 'this' => $this->id] - ); - - // ... and delete from parent course if this was the only - // course membership in this family. - if ($other === 0) { - $s = new Seminar($this->parent); - $s->deleteMember($user_id); - } - } - - if ($this->children != null) { - foreach ($this->children as $child) { - $s = new Seminar($child); - $s->deleteMember($user_id); - } - } - - if (!empty($dozenten[$user_id])) { - $query = "SELECT termin_id FROM termine WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$this->id]); - $termine = $statement->fetchAll(PDO::FETCH_COLUMN); - - $query = "DELETE FROM termin_related_persons WHERE range_id = ? AND user_id = ?"; - $statement = DBManager::get()->prepare($query); - - foreach ($termine as $termin_id) { - $statement->execute([$termin_id, $user_id]); - } - if (Deputy::isActivated()) { - $other_dozenten = array_diff(array_keys($dozenten), [$user_id]); - foreach (Deputy::findByRange_id($user_id) as $default_deputy) { - if ($default_deputy->user_id != $GLOBALS['user']->id && - !Deputy::countBySql("range_id IN (?)", [$other_dozenten])) { - Deputy::deleteBySQL("range_id = ? AND user_id = ?", [$this->id, $default_deputy->user_id]); - } - } - } - } - - // Delete course related datafield entries - DatafieldEntryModel::deleteBySQL('range_id = ? AND sec_range_id = ?', [$user_id, $this->id]); - - // Remove from associated status groups - foreach (Statusgruppen::findBySeminar_id($this->id) as $group) { - $group->removeUser($user_id, true); - } - - $this->createMessage(sprintf( - _('Nutzer %s wurde aus der Veranstaltung entfernt.'), - '<i>' . htmlReady(get_fullname($user_id)) . '</i>' - )); - NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id); - NotificationCenter::postNotification('UserDidLeaveCourse', $this->id, $user_id); - StudipLog::log('SEM_USER_DEL', $this->id, $user_id, 'Wurde aus der Veranstaltung entfernt'); - $this->course->resetRelation('members'); - return true; - } else { - $this->createError( - sprintf( - _('Die Veranstaltung muss wenigstens <b>einen/eine</b> VeranstaltungsleiterIn (%s) eingetragen haben!'), - get_title_for_status('dozent', 1, $this->status) - ) - . ' ' . _('Tragen Sie zunächst einen anderen ein, um diesen zu löschen.') - ); - return false; - } - } - - /** - * sets the almost never used column position in the table seminar_user - * @param array $members members array: array of user_id's - wrong IDs will be ignored - * @return Seminar - */ - public function setMemberPriority($members): Seminar - { - CourseMember::findEachBySQL( - function (CourseMember $membership) use (&$members) { - $membership->position = array_search($membership->user_id, $members); - $membership->store(); - }, - "Seminar_id = ? AND user_id IN (?)", - [$this->id, $members] - ); - return $this; - } - - /** - * returns array with information about enrolment to this course for given user_id - * ['enrolment_allowed'] : true or false - * ['cause']: keyword to describe the cause - * ['description'] : readable description of the cause - * - * @param string $user_id - * @return array - */ - public function getEnrolmentInfo($user_id) - { - $info = []; - $user = User::find($user_id); - if ($this->getSemClass()->isGroup()) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'grouped'; - $info['description'] = _("Dies ist eine Veranstaltungsgruppe. Sie können sich nur in deren Unterveranstaltungen eintragen."); - return $info; - } - if ($this->read_level == 0 && Config::get()->ENABLE_FREE_ACCESS && !$GLOBALS['perm']->get_studip_perm($this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'free_access'; - $info['description'] = _("Für die Veranstaltung ist keine Anmeldung erforderlich."); - return $info; - } - if (!$user) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'nobody'; - $info['description'] = _("Sie sind nicht in Stud.IP angemeldet."); - return $info; - } - if ($GLOBALS['perm']->have_perm('root', $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'root'; - $info['description'] = _("Sie dürfen ALLES."); - return $info; - } - if ($GLOBALS['perm']->have_studip_perm('admin', $this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'courseadmin'; - $info['description'] = _("Sie sind Administrator_in der Veranstaltung."); - return $info; - } - if ($GLOBALS['perm']->have_perm('admin', $user_id)) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'admin'; - $info['description'] = _("Als Administrator_in können Sie sich nicht für eine Veranstaltung anmelden."); - return $info; - } - //Ist bereits Teilnehmer - if ($GLOBALS['perm']->have_studip_perm('user', $this->getId(), $user_id)) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'member'; - $info['description'] = _("Sie sind für die Veranstaltung angemeldet."); - return $info; - } - $admission_status = $user->admission_applications->findBy('seminar_id', $this->getId())->val('status'); - if ($admission_status == 'accepted') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'accepted'; - $info['description'] = _("Sie wurden für diese Veranstaltung vorläufig akzeptiert."); - return $info; - } - if ($admission_status == 'awaiting') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'awaiting'; - $info['description'] = _("Sie stehen auf der Warteliste für diese Veranstaltung."); - return $info; - } - if ($GLOBALS['perm']->get_perm($user_id) == 'user') { - $info['enrolment_allowed'] = false; - $info['cause'] = 'user'; - $info['description'] = _("Sie haben nicht die erforderliche Berechtigung sich für eine Veranstaltung anzumelden."); - return $info; - } - //falsche Nutzerdomäne - $same_domain = true; - $user_domains = UserDomain::getUserDomainsForUser($user_id); - if (count($user_domains) > 0) { - $seminar_domains = UserDomain::getUserDomainsForSeminar($this->getId()); - $same_domain = UserDomain::checkUserVisibility($seminar_domains, $user_domains);; - } - if (!$same_domain && !$this->isStudygroup()) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'domain'; - $info['description'] = _("Sie sind nicht in einer zugelassenenen Nutzerdomäne, Sie können sich nicht eintragen!"); - return $info; - } - //Teilnehmerverwaltung mit Sperregel belegt - if (LockRules::Check($this->getId(), 'participants')) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'locked'; - $lockdata = LockRules::getObjectRule($this->getId()); - $info['description'] = _("In diese Veranstaltung können Sie sich nicht eintragen!") . ($lockdata['description'] ? '<br>' . formatLinks($lockdata['description']) : ''); - return $info; - } - //Veranstaltung unsichtbar für aktuellen Nutzer - if (!$this->visible && !$this->isStudygroup() && !$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id)) { - $info['enrolment_allowed'] = false; - $info['cause'] = 'invisible'; - $info['description'] = _("Die Veranstaltung ist gesperrt, Sie können sich nicht eintragen!"); - return $info; - } - if ($courseset = $this->getCourseSet()) { - $info['enrolment_allowed'] = true; - $info['cause'] = 'courseset'; - $info['description'] = _("Die Anmeldung zu dieser Veranstaltung folgt speziellen Regeln. Lesen Sie den Hinweistext."); - $user_prio = AdmissionPriority::getPrioritiesByUser($courseset->getId(), $user_id); - if (isset($user_prio[$this->getId()])) { - if ($courseset->hasAdmissionRule('LimitedAdmission')) { - $info['description'] .= ' ' . sprintf(_("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung mit der Priorität %s.)"), $user_prio[$this->getId()]); - } else { - $info['description'] .= ' ' . _("(Sie stehen auf der Anmeldeliste für die automatische Platzverteilung.)"); - } - } - return $info; - } - $info['enrolment_allowed'] = true; - $info['cause'] = 'normal'; - $info['description'] = ''; - return $info; - } - - /** - * adds user with given id as preliminary member to course - * - * @param string $user_id - * @return integer 1 if successfull - */ - public function addPreliminaryMember($user_id, $comment = '') - { - $new_admission_member = new AdmissionApplication(); - $new_admission_member->user_id = $user_id; - $new_admission_member->position = 0; - $new_admission_member->status = 'accepted'; - $new_admission_member->comment = $comment; - $this->course->admission_applicants[] = $new_admission_member; - $ok = $new_admission_member->store(); - if ($ok && $this->isStudygroup()) { - StudygroupModel::applicationNotice($this->getId(), $user_id); - } - $cs = $this->getCourseSet(); - if ($cs) { - $prio_delete = AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId()); - } - // LOGGING - StudipLog::log('SEM_USER_ADD', $this->getId(), $user_id, 'accepted', 'Vorläufig akzeptiert'); - return $ok; - } - - /** - * returns courseset object for this course - * - * @return CourseSet courseset object or null - */ - public function getCourseSet() - { - if ($this->course_set === null) { - $this->course_set = CourseSet::getSetForCourse($this->id); - if ($this->course_set === null) { - $this->course_set = false; - } - } - return $this->course_set ?: null; - } - - /** - * returns true if the number of participants of this course is limited - * - * @return boolean - */ - public function isAdmissionEnabled() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->isSeatDistributionEnabled()); - } - - /** - * returns the number of free seats in the course or true if not limited - * - * @return integer - */ - public function getFreeAdmissionSeats() - { - if ($this->isAdmissionEnabled()) { - return $this->course->getFreeSeats(); - } else { - return true; - } - } - - /** - * returns true if the course is locked - * - * @return boolean - */ - public function isAdmissionLocked() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('LockedAdmission')); - } - - /** - * returns true if the course is password protected - * - * @return boolean - */ - public function isPasswordProtected() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('PasswordAdmission')); - } - - /** - * returns array with start and endtime of course enrolment timeframe - * return null if there is no timeframe - * - * @return array assoc array with start_time end_time as keys timestamps as values - */ - public function getAdmissionTimeFrame() - { - $cs = $this->getCourseSet(); - return ($cs && $cs->hasAdmissionRule('TimedAdmission')) ? - ['start_time' => $cs->getAdmissionRule('TimedAdmission')->getStartTime(), - 'end_time' => $cs->getAdmissionRule('TimedAdmission')->getEndTime()] : []; - } - - /** - * returns StudipModule object for given slot, null when deactivated or not available - * - * @param string $slot - * @return StudipModule|null - */ - public function getSlotModule($slot): ?StudipModule - { - $module = 'Core' . ucfirst($slot); - if ($this->course->isToolActive($module)) { - return PluginEngine::getPlugin($module); - } - return null; - } - - /** - * adds user with given id on waitinglist - * - * @param string $user_id - * @param string $which_end 'last' or 'first' - * @return integer|bool number on waitlist or false if not successful - */ - public function addToWaitlist($user_id, $which_end = 'last') - { - if (AdmissionApplication::exists([$user_id, $this->id]) || CourseMember::find([$this->id, $user_id])) { - return false; - } - switch ($which_end) { - // Append users to waitlist end. - case 'last': - $maxpos = DBManager::get()->fetchColumn("SELECT MAX(`position`) - FROM `admission_seminar_user` - WHERE `seminar_id`=? - AND `status`='awaiting'", [$this->id]); - $waitpos = $maxpos+1; - break; - // Prepend users to waitlist start. - case 'first': - default: - // Move all others on the waitlist up by the number of people to add. - AdmissionApplication::renumberAdmission($this->id); - $waitpos = 1; - } - $new_admission_member = new AdmissionApplication(); - $new_admission_member->user_id = $user_id; - $new_admission_member->position = $waitpos; - $new_admission_member->status = 'awaiting'; - $new_admission_member->seminar_id = $this->id; - if ($new_admission_member->store()) { - StudipLog::log('SEM_USER_ADD', $this->id, $user_id, 'awaiting', 'Auf Warteliste gesetzt, Position: ' . $waitpos); - $this->course->resetRelation('admission_applicants'); - return $waitpos; - } - return false; - } -} diff --git a/lib/classes/SeminarCategories.php b/lib/classes/SeminarCategories.php index 25eb4c10abe02a0b08bf1d1dd2b458401d1e032a..39122bc8ca4edc2878339a5b29d6267905508311 100644 --- a/lib/classes/SeminarCategories.php +++ b/lib/classes/SeminarCategories.php @@ -88,7 +88,7 @@ class SeminarCategories { * @return SeminarCategories */ public static function GetBySeminarId($seminar_id){ - return self::GetByTypeId(Seminar::GetInstance($seminar_id)->status); + return self::GetByTypeId(Course::find($seminar_id)->status); } /** diff --git a/lib/classes/StudipSemTreeViewAdmin.php b/lib/classes/StudipSemTreeViewAdmin.php index 59c926adfd064fcb7c68c4e25481dcb34e7d432f..28755c7117386a682a45127ec035fc5881aaf758 100644 --- a/lib/classes/StudipSemTreeViewAdmin.php +++ b/lib/classes/StudipSemTreeViewAdmin.php @@ -394,9 +394,9 @@ class StudipSemTreeViewAdmin extends TreeView if (($sem_aktion[0] == 'del' || $sem_aktion[1] == 'del') && count($marked_sem)){ $not_deleted = []; foreach($marked_sem as $key => $seminar_id){ - $seminar = new Seminar($seminar_id); - if(count($seminar->getStudyAreas()) == 1){ - $not_deleted[] = $seminar->getName(); + $course = Course::find($seminar_id); + if (count($course->getStudyAreas()) === 1) { + $not_deleted[] = $course->name; unset($marked_sem[$key]); } } diff --git a/lib/classes/StudygroupModel.php b/lib/classes/StudygroupModel.php index 051ada7b18d65080e75c116990a7bb96d9267780..9fd44b3eeab134d0f8b8e61239d7446494a69c6d 100644 --- a/lib/classes/StudygroupModel.php +++ b/lib/classes/StudygroupModel.php @@ -87,7 +87,7 @@ class StudygroupModel ); // Post equivalent notifications to a regular course - $seminar = Seminar::getInstance($sem_id); + $seminar = Course::find($sem_id); NotificationCenter::postNotification( 'CourseDidGetMember', $seminar, $accept_user_id ); @@ -154,7 +154,7 @@ class StudygroupModel ); // Post equivalent notifications to a regular course - $seminar = Seminar::getInstance($sem_id); + $seminar = Course::find($sem_id); NotificationCenter::postNotification('CourseDidChangeMember', $seminar, $user->id); NotificationCenter::postNotification('UserDidLeaveCourse', $sem_id, $user->id); } @@ -514,30 +514,37 @@ class StudygroupModel */ public static function applicationNotice($sem_id, $user_id) { - $sem = new Seminar($sem_id); - $dozenten = $sem->getMembers(); - $tutors = $sem->getMembers('tutor'); - $recipients = []; - $msging = new messaging(); - - foreach (array_merge($dozenten, $tutors) as $uid => $user) { - $recipients[] = $user['username']; - } + $course = Course::find($sem_id); + $msging = new messaging(); + + //Get all those with tutor and dozent status to inform them + //about the application: + $stmt = DBManager::get()->prepare( + "SELECT `username` + FROM `auth_user_md5` + JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_user`.`seminar_id` = :course_id + AND `seminar_user`.`status` IN ('dozent', 'tutor')" + ); + $stmt->execute(['course_id' => $course->id]); + $recipients = $stmt->fetchAll(); - if (mb_strlen($sem->getName()) > 32) //cut subject if to long - $subject = sprintf(_('[Studiengruppe: %s...]'), mb_substr($sem->getName(), 0, 30)); - else - $subject = sprintf(_('[Studiengruppe: %s]'), $sem->getName()); + //Limit the subject prefix size in case of a long course name: + if (mb_strlen($course->name) > 32) { + $subject = sprintf(_('[Studiengruppe: %s...]'), mb_substr($course->name, 0, 30)); + } else { + $subject = sprintf(_('[Studiengruppe: %s]'), $course->name); + } if (StudygroupModel::isInvited($user_id, $sem_id)) { $subject .= ' ' . _('Einladung akzeptiert'); $message = sprintf( _("%s hat die Einladung zur Studiengruppe %s akzeptiert. Klicken Sie auf den folgenden Link, um direkt zur Studiengruppe zu gelangen.\n\n [Direkt zur Studiengruppe]%s"), get_fullname($user_id), - $sem->getName(), + $course->name, URLHelper::getlink( - "{$GLOBALS['ABSOLUTE_URI_STUDIP']}dispatch.php/course/studygroup/members/?cid={$sem->id}", - ['cid' => $sem->id] + "{$GLOBALS['ABSOLUTE_URI_STUDIP']}dispatch.php/course/studygroup/members", + ['cid' => $course->id] ) ); } else { @@ -545,10 +552,10 @@ class StudygroupModel $message = sprintf( _("%s möchte der Studiengruppe %s beitreten. Klicken Sie auf den folgenden Link, um direkt zur Studiengruppe zu gelangen.\n\n [Direkt zur Studiengruppe]%s"), get_fullname($user_id), - $sem->getName(), + $course->name, URLHelper::getlink( - "{$GLOBALS['ABSOLUTE_URI_STUDIP']}dispatch.php/course/studygroup/members/?cid={$sem->id}", - ['cid' => $sem->id] + "{$GLOBALS['ABSOLUTE_URI_STUDIP']}dispatch.php/course/studygroup/members", + ['cid' => $course->id] ) ); } diff --git a/lib/classes/UserManagement.php b/lib/classes/UserManagement.php index 1901c9414ce4b710490ebd9512969156764764bb..ed349a2d31591d58f1e2394ec0b34cd506bb7af5 100644 --- a/lib/classes/UserManagement.php +++ b/lib/classes/UserManagement.php @@ -869,27 +869,26 @@ class UserManagement } foreach ($group_ids as $group_id) { - $sem = Seminar::GetInstance($group_id); + $course = Course::find($group_id); if (StudygroupModel::countMembers($group_id) > 1) { // check whether there are tutors or even autors that can be promoted - $tutors = $sem->getMembers('tutor'); - $autors = $sem->getMembers('autor'); + $tutors = CourseMember::findByCourseAndStatus($course->id, 'tutor'); + $autors = CourseMember::findByCourseAndStatus($course->id, 'autor'); if (count($tutors) > 0) { $new_founder = current($tutors); - StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + StudygroupModel::promote_user($new_founder['username'], $course->id, 'dozent'); continue; } // if not promote an autor elseif (count($autors) > 0) { $new_founder = current($autors); - StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + StudygroupModel::promote_user($new_founder['username'], $course->id, 'dozent'); continue; } // since no suitable successor was found, we are allowed to remove the studygroup } else { - $sem->delete(); + $course->delete(); } - unset($sem); } } diff --git a/lib/classes/admission/RandomAlgorithm.php b/lib/classes/admission/RandomAlgorithm.php index 4666f594d067eed1ba69ab025126349b5f229cc6..46ff7623944f2ab2a7d236134df20c0004c48025 100644 --- a/lib/classes/admission/RandomAlgorithm.php +++ b/lib/classes/admission/RandomAlgorithm.php @@ -374,27 +374,34 @@ class RandomAlgorithm extends AdmissionAlgorithm * @param Course $course course to add users to * @param int $prio user's priority for the given course */ - private function addUsersToCourse($user_list, $course, $prio = null) + private function addUsersToCourse($user_list, Course $course, $prio = null) { - $seminar = new Seminar($course); - foreach ($user_list as $chosen_one) { - setTempLanguage($chosen_one); - $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $seminar->getName()); - if ($seminar->admission_prelim) { - if ($seminar->addPreliminaryMember($chosen_one)) { - $message_body = sprintf (_('Sie haben bei der Platzvergabe der Veranstaltung **%s** einen vorläufigen Platz erhalten. Die endgültige Zulassung zu der Veranstaltung ist noch von weiteren Bedingungen abhängig, die Sie bitte der Veranstaltungsbeschreibung entnehmen.'), - $seminar->getName()); + foreach ($user_list as $user_id) { + $user = User::find($user_id); + setTempLanguage($user_id); + $message_title = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); + if ($course->admission_prelim) { + try { + $course->addPreliminaryMember($user); + } catch (\Studip\Exception $e) { + //Nothing here. } + $message_body = sprintf( + _('Sie haben bei der Platzvergabe der Veranstaltung **%s** einen vorläufigen Platz erhalten. Die endgültige Zulassung zu der Veranstaltung ist noch von weiteren Bedingungen abhängig, die Sie bitte der Veranstaltungsbeschreibung entnehmen.'), + $course->name + ); } else { - if ($seminar->addMember($chosen_one, 'autor')) { - $message_body = sprintf (_("Sie haben bei der Platzvergabe der Veranstaltung **%s** einen Platz erhalten. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen."), - $seminar->getName()); + if ($course->addMember($user_id, 'autor')) { + $message_body = sprintf( + _('Sie haben bei der Platzvergabe der Veranstaltung **%s** einen Platz erhalten. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), + $course->name + ); } } if ($prio) { - $message_body .= "\n" . sprintf(_("Sie hatten für diese Veranstaltung die Priorität %s gewählt."), $prio[$chosen_one]); + $message_body .= "\n" . sprintf(_('Sie hatten für diese Veranstaltung die Priorität %s gewählt.'), $prio[$user_id]); } - messaging::sendSystemMessage($chosen_one, $message_title, $message_body); + messaging::sendSystemMessage($user_id, $message_title, $message_body); restoreLanguage(); } } diff --git a/lib/classes/calendar/CalendarScheduleModel.php b/lib/classes/calendar/CalendarScheduleModel.php index 0aeadee036a02ecc5d47e8d585f3d0c30edf0000..bc884bbfc06b5239e8b8c448fbd82c06f211d0e7 100644 --- a/lib/classes/calendar/CalendarScheduleModel.php +++ b/lib/classes/calendar/CalendarScheduleModel.php @@ -163,30 +163,31 @@ class CalendarScheduleModel $filterEnd = $semester['vorles_ende']; } - $sem = new Seminar($seminar_id); - foreach ($sem->getCycles() as $cycle) { + $course = Course::find($seminar_id); + $regular_dates = SeminarCycleDate::findBySeminar($seminar_id); + foreach ($regular_dates as $cycle) { if (!$cycle_id || $cycle->getMetaDateID() == $cycle_id) { - $entry = []; + $entry = []; $entry['id'] = $seminar_id .'-'. $cycle->getMetaDateId(); $entry['cycle_id'] = $cycle->getMetaDateId(); - $entry['start_formatted'] = sprintf("%02d", $cycle->getStartStunde()) .':' - . sprintf("%02d", $cycle->getStartMinute()); - $entry['end_formatted'] = sprintf("%02d", $cycle->getEndStunde()) .':' - . sprintf("%02d", $cycle->getEndMinute()); + $entry['start_formatted'] = preg_replace('/\:00$/', '', $cycle->start_time); + $entry['end_formatted'] = preg_replace('/\:00$/', '', $cycle->end_time); - $entry['start'] = ((int)$cycle->getStartStunde() * 100) + ($cycle->getStartMinute()); - $entry['end'] = ((int)$cycle->getEndStunde() * 100) + ($cycle->getEndMinute()); - $entry['day'] = $cycle->getDay(); - $entry['content'] = $sem->getNumber() . ' ' . $sem->getName(); + $start_parts = explode(':', $cycle->start_time); + $end_parts = explode(':', $cycle->end_time); + $entry['start'] = $start_parts[0] * 100 + $start_parts[1]; + $entry['end'] = $end_parts[0] * 100 + $end_parts[1]; + $entry['day'] = $cycle->weekday; + $entry['content'] = $course->veranstaltungsnummer . ' ' . $course->name; - $entry['title'] = $cycle->getDescription(); + $entry['title'] = $cycle->description; // check, if the date is assigned to a room - if ($rooms = $cycle->getPredominantRoom($filterStart, $filterEnd)) { - $entry['title'] .= implode('', getPlainRooms(array_slice($rooms, 0, 1))) - . (sizeof($rooms) > 1 ? ', u.a.' : ''); + if ($room = $cycle->getMostBookedRoom()) { + $entry['title'] .= $room->getFullName(); } else if ($rooms = $cycle->getFreeTextPredominantRoom($filterStart, $filterEnd)) { + //TODO: replace unset($rooms['']); if (!empty($rooms)) { $entry['title'] .= '('. implode('), (', array_slice(array_keys($rooms), 0, 3)) .')'; @@ -194,14 +195,20 @@ class CalendarScheduleModel } // add the lecturer - $lecturers = []; - $members = $sem->getMembers('dozent'); + $db = DBManager::get(); + $stmt = $db->prepare( + "SELECT `Nachname` + FROM `auth_user_md5` + JOIN `seminar_user` USING (`user_id`) + WHERE `seminar_id` = :course_id + ORDER BY `Nachname` + LIMIT 4" + ); + $stmt->execute(['course_id' => $course->id]); + $lecturers = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); - foreach ($members as $member) { - $lecturers[] = $member['Nachname']; - } $entry['content'] .= " (". implode(', ', array_slice($lecturers, 0, 3)) - . (sizeof($members) > 3 ? ' et al.' : '').')'; + . (count($lecturers) > 3 ? ' et al.' : '').')'; $entry['url'] = URLHelper::getLink('dispatch.php/calendar/schedule/entry/' . $seminar_id @@ -213,11 +220,11 @@ class CalendarScheduleModel // check the settings for this entry - $member = CourseMember::find([$sem->getId(), $user_id]); + $member = CourseMember::find([$course->id, $user_id]); $entry['type'] = $member ? 'sem' : 'virtual'; - $stmt = DBManager::get()->prepare('SELECT * FROM schedule_seminare WHERE seminar_id = ? AND user_id = ? AND metadate_id = ?'); - $stmt->execute([$sem->getId(), $user_id, $cycle->getMetaDateId()]); + $stmt = $db->prepare('SELECT * FROM schedule_seminare WHERE seminar_id = ? AND user_id = ? AND metadate_id = ?'); + $stmt->execute([$course->id, $user_id, $cycle->getMetaDateId()]); $details = $stmt->fetch(); if ($entry['type'] === 'virtual') { @@ -418,21 +425,27 @@ class CalendarScheduleModel /** * Returns the ID of the cycle of a course specified by start and end. * - * @param Seminar $seminar an instance of a Seminar + * @param string $course_id The ID of a course. * @param string $start the start of the cycle * @param string $end the end of the cycle * @return string $day numeric day */ - static function getSeminarCycleId(Seminar $seminar, $start, $end, $day) + static function getSeminarCycleId($course_id, $start, $end, $day) { $ret = []; $day = ($day + 1) % 7; - foreach ($seminar->getCycles() as $cycle) { - if (leadingZero($cycle->getStartStunde()) . leadingZero($cycle->getStartMinute()) == $start - && leadingZero($cycle->getEndStunde()) . leadingZero($cycle->getEndMinute()) == $end - && $cycle->getDay() == $day) { + $regular_dates = SeminarCyCleDate::findBySeminar($course_id); + + foreach ($regular_dates as $cycle) { + $cycle_start = preg_replace('/\:00$/', '', $cycle->start_time); + $cycle_end = preg_replace('/\:00$/', '', $cycle->end_time); + if ( + leadingZero($cycle_start) == $start + && leadingZero($cycle_end) == $end + && $cycle->weekday == $day + ) { $ret[] = $cycle; } } diff --git a/lib/classes/coursewizardsteps/BasicDataWizardStep.php b/lib/classes/coursewizardsteps/BasicDataWizardStep.php index 18ae67191f5196488dc56f0777fb76b01bcce742..d9e79605c6a0211bd97632ef69d92d45ef41ecc1 100644 --- a/lib/classes/coursewizardsteps/BasicDataWizardStep.php +++ b/lib/classes/coursewizardsteps/BasicDataWizardStep.php @@ -402,7 +402,6 @@ class BasicDataWizardStep implements CourseWizardStep // We only need our own stored values here. $values = $values[__CLASS__]; - $seminar = new Seminar($course); if ($source_id && $copy_basic_data) { $this->copyBasicData( @@ -418,7 +417,7 @@ class BasicDataWizardStep implements CourseWizardStep $course->start_semester = Semester::findByTimestamp($values['start_time']); $course->institut_id = $values['institute']; - $semclass = $seminar->getSemClass(); + $semclass = $course->getSemClass(); $course->visible = $semclass['visible']; $course->admission_prelim = $semclass['admission_prelim_default']; $course->lesezugriff = $semclass['default_read_level'] ?: 1; @@ -447,19 +446,39 @@ class BasicDataWizardStep implements CourseWizardStep } StudipLog::log('SEM_CREATE', $course->id, null, 'Veranstaltung mit Assistent angelegt'); - $institutes = [$values['institute']]; + $institute_ids = [$values['institute']]; if (isset($values['participating']) && is_array($values['participating'])) { - $institutes = array_merge($institutes, array_keys($values['participating'])); + $institute_ids = array_merge($institute_ids, array_keys($values['participating'])); + } + $institutes = Institute::findMany($institute_ids); + $course->institutes = $institutes; + if ($course->isDirty()) { + $course->store(); } - $seminar->setInstitutes($institutes); if (isset($values['lecturers']) && is_array($values['lecturers'])) { foreach (array_keys($values['lecturers']) as $user_id) { - $seminar->addMember($user_id, 'dozent'); + $user = User::find($user_id); + if (!$user) { + continue; + } + try { + $course->addMember($user, 'dozent'); + } catch (\Studip\Exception $e) { + //Nothing here. + } } } if (isset($values['tutors']) && is_array($values['tutors'])) { foreach (array_keys($values['tutors']) as $user_id) { - $seminar->addMember($user_id, 'tutor'); + $user = User::find($user_id); + if (!$user) { + continue; + } + try { + $course->addMember($user, 'tutor'); + } catch (\Studip\Exception $e) { + //Nothing here. + } } } if (Config::get()->DEPUTIES_ENABLE && isset($values['deputies']) && is_array($values['deputies'])) { @@ -472,10 +491,6 @@ class BasicDataWizardStep implements CourseWizardStep CourseSet::addCourseToSet($course_set_id, $course->id); } - if ($source_id && ($copy_participants || $copy_groups || $copy_members)) { - self::copyParticipantsAndGroups($course, $source_id, $copy_participants, $copy_groups, $copy_members); - } - return $course; } @@ -484,7 +499,7 @@ class BasicDataWizardStep implements CourseWizardStep * to already given values. A good example are study areas which * are only needed for certain sem_classes. * - * @param array $values values specified from previous steps + * @param Array $values values specified from previous steps * @return bool Is the current step required for a new course? */ public function isRequired($values) @@ -495,7 +510,7 @@ class BasicDataWizardStep implements CourseWizardStep /** * Copy values for basic data wizard step from given course. * @param Course $course - * @param array $values + * @param Array $values */ public function copy($course, $values) { @@ -528,17 +543,17 @@ class BasicDataWizardStep implements CourseWizardStep * Fetches the default deputies for a given person if the necessary * config options are set. * @param $user_id user whose default deputies to get - * @return array Default deputy user_ids. + * @return Array Default deputy user_ids. */ public function getDefaultDeputies($user_id) { if (Config::get()->DEPUTIES_ENABLE && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) { - return Deputy::findDeputies($user_id)->map(function (Deputy $deputy): array { + return Deputy::findDeputies($user_id)->map(function($deputy) { return ['id' => $deputy->user_id, 'name' => $deputy->getDeputyFullname()]; }); + } else { + return []; } - - return []; } public function getSearch($course_type, $institute_ids, $exclude_lecturers = [],$exclude_tutors = []) @@ -627,121 +642,10 @@ class BasicDataWizardStep implements CourseWizardStep } } - } else { - foreach ($indices as $index) { - $values[$index] = $values[$index] ?? ''; - } + } return $values; } - private function copyBasicData( - Course $course, - string $source_id - ): void { - $source = Course::find($source_id); - $course->setData($source->toArray('untertitel ort sonstiges art teilnehmer vorrausetzungen lernorga leistungsnachweis ects admission_turnout modules')); - foreach ($source->datafields as $one) { - $df = $one->getTypedDatafield(); - if ($df->isEditable()) { - $course->datafields->findOneBy('datafield_id', $one->datafield_id)->content = $one->content; - } - } - } - - /** - * Copies participants and/or groups from one course to another. - */ - public static function copyParticipantsAndGroups( - Course $course, - string $source_id, - bool $with_participants = true, - bool $with_groups = true, - bool $with_members = true, - bool|array $group_ids = false - ): void { - $source = Course::find($source_id); - - if (!$with_participants && !$with_groups) { - return; - } - - if ($with_participants || ($with_groups && $with_members)) { - $member_ids = false; - if (!$with_participants && $with_members) { - $member_ids = []; - $source->statusgruppen->filter(function (Statusgruppen $group) use ($group_ids): bool { - return $group_ids === false - || in_array($group->id, $group_ids); - })->each(function (Statusgruppen $group) use (&$member_ids): void { - $group->members->each(function (StatusgruppeUser $member) use (&$member_ids): void { - if (!in_array($member->user_id, $member_ids)) { - $member_ids[] = $member->user_id; - } - }); - }); - } - - $source->getMembersWithStatus(['user', 'autor', 'tutor'], true) - ->filter(function (CourseMember $member) use ($course, $member_ids): bool { - return ($member_ids === false || in_array($member->user_id, $member_ids)) - && !CourseMember::exists([$course->id, $member->user_id]); - })->each(function (CourseMember $member) use ($course): void { - CourseMember::insertCourseMember( - $course->id, - $member->user_id, - $member->status, - ); - }); - } - - if (!$with_groups) { - return; - } - - $source->statusgruppen->filter(function (Statusgruppen $group) use ($group_ids): bool { - return $group_ids === false - || in_array($group->id, $group_ids); - })->each(function (Statusgruppen $group) use ($course, $with_members, $group_ids): void { - $g = Statusgruppen::findOneBySQL( - 'range_id = ? AND name = ?', - [$course->id, $group->name] - ); - - if (!$g) { - $g = Statusgruppen::createOrUpdate( - null, - $group->name, - $group->position, - $course->id, - $group->size, - $group->selfassign, - $group->selfassign_start, - $group->selfassign_end, - $group->hasFolder(), - null, - $group->hasBlubber() - ); - } - - if (!$with_members) { - return; - } - - $group->members->filter(function (StatusgruppeUser $member) use ($g): bool { - return !StatusgruppeUser::exists([$g->id, $member->user_id]); - })->each(function (StatusgruppeUser $member) use ($g): void { - StatusgruppeUser::create([ - 'statusgruppe_id' => $g->id, - ...$member->toArray([ - 'user_id', - 'position', - 'visible', - 'inherit', - ]) - ]); - }); - }); - } } diff --git a/lib/classes/globalsearch/GlobalSearchCourses.php b/lib/classes/globalsearch/GlobalSearchCourses.php index 853f8112c99bcc790156d37786ed02685e1fa971..1379f1a023ee92530f7368f3c033c27cf8d2abc4 100644 --- a/lib/classes/globalsearch/GlobalSearchCourses.php +++ b/lib/classes/globalsearch/GlobalSearchCourses.php @@ -149,11 +149,7 @@ class GlobalSearchCourses extends GlobalSearchModule implements GlobalSearchFull public static function filter($data, $search) { $course = Course::buildExisting($data); - $seminar = new Seminar($course); - $turnus_string = $seminar->getDatesExport([ - 'short' => true, - 'shrink' => true, - ]); + $turnus_string = implode(' ', $course->getAllDatesInSemester()->toStringArray()); //Shorten, if string too long (add link for details.php) if (mb_strlen($turnus_string) > 70) { $turnus_string = htmlReady(mb_substr($turnus_string, 0, mb_strpos(mb_substr($turnus_string, 70, mb_strlen($turnus_string)), ',') + 71)); diff --git a/lib/classes/globalsearch/GlobalSearchMyCourses.php b/lib/classes/globalsearch/GlobalSearchMyCourses.php new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/lib/dates.inc.php b/lib/dates.inc.php index 497a5d167a4ee4265de91c97c61467e171d67b64..acd5109902bd10c7bb3a3a842c1ad44362b04bda 100644 --- a/lib/dates.inc.php +++ b/lib/dates.inc.php @@ -19,7 +19,6 @@ **/ require_once 'lib/calendar_functions.inc.php'; -require_once 'lib/raumzeit/raumzeit_functions.inc.php'; /** * getWeekday liefert einen String mit einem Tagesnamen. @@ -199,28 +198,25 @@ function vorbesprechung(string $seminar_id, string $type = 'standard'): false|st return false; } - $termin = new SingleDate($termin_id); - $ret = $termin->toString(); - if ($termin->getResourceID()) { - $ret .= ', ' . _("Ort:") . ' '; + $termin = new CourseDate($termin_id); + $ret = (string) $termin; + if (!empty($termin->room_booking->resource)) { + $ret .= ', '._("Ort:").' '; switch ($type) { case 'export': - $room = Room::find($termin->getResourceID()); - $ret .= $room->name; + $ret .= $termin->room_booking->resource->name; break; case 'standard': default: - $resource = Resource::find($termin->getResourceID()); - $ret .= '<a href="' . $resource->getActionLink('show') . '" data-dialog="1">' - . htmlReady($resource->name) . '</a>'; + $ret .= '<a href="' . $termin->room_booking->resource->getActionLink('show') . '" data-dialog>' + . htmlReady($termin->room_booking->resource->name) . '</a>'; break; } } return $ret; } - /** * a small helper funktion to get the type query for "Sitzungstermine" * (this dates are important to get the regularly, presence dates @@ -294,3 +290,36 @@ function getPlainRooms(array $rooms): array return $room_list; } + + +/** + * @param string $comment + * @param array $dates SingleDate + */ +function raumzeit_send_cancel_message($comment, $dates) +{ + if (!is_array($dates)) { + $dates = [$dates]; + } + $course = Course::find($dates[0]->range_id); + if ($course) { + $subject = sprintf(_('[%s] Terminausfall'), $course->name); + $recipients = $course->members->pluck('username'); + $lecturers = $course->members->findBy('status', 'dozent')->pluck('nachname'); + $message = sprintf( + ngettext( + _('In der Veranstaltung %s fällt der folgende Termine aus:'), + _('In der Veranstaltung %s fallen die folgenden Termine aus:'), + count($dates) + ), + $course->name . ' (' . implode(',', $lecturers) . ') ' . $course->start_semester->name + ); + $message .= "\n\n- "; + $message .= implode("\n- " , array_map(fn($a) => (string) $a, $dates)); + if ($comment) { + $message .= "\n\n" . $comment; + } + $msg = new messaging(); + return $msg->insert_message($message, $recipients, '____%system%____', '', '', '', '', $subject, true); + } +} diff --git a/lib/elearning/Ilias4ConnectedCMS.php b/lib/elearning/Ilias4ConnectedCMS.php index d45168ff0076fb9807d166ff2f7dd06be5175065..8db483bb2173d7a148d836e4391c89b42c5ca51a 100644 --- a/lib/elearning/Ilias4ConnectedCMS.php +++ b/lib/elearning/Ilias4ConnectedCMS.php @@ -141,29 +141,28 @@ class Ilias4ConnectedCMS extends Ilias3ConnectedCMS $this->soap_client->clearCache(); if ($crs_id == false) { - $seminar = Seminar::getInstance($seminar_id); - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $ref_id = ObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->cms_type); + $course = Course::find($seminar_id); + if ($course->home_institut) { + $ref_id = ObjectConnections::getConnectionModuleId($course->institut_id, 'cat', $this->cms_type); } if ($ref_id < 1) { // Kategorie für Heimateinrichtung anlegen - $object_data["title"] = sprintf("%s", $home_institute->name); - $object_data["description"] = sprintf(_("Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung \"%s\"."), $home_institute->name); - $object_data["type"] = "cat"; - $object_data["owner"] = $this->soap_client->LookupUser($ELEARNING_INTERFACE_MODULES[$this->cms_type]["soap_data"]["username"]); + $object_data['title'] = $course->home_institut->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $course->home_institut->name); + $object_data['type'] = 'cat'; + $object_data['owner'] = $this->soap_client->LookupUser($ELEARNING_INTERFACE_MODULES[$this->cms_type]['soap_data']['username']); $ref_id = $this->soap_client->addObject($object_data, $this->main_category_node_id); - ObjectConnections::setConnection($home_institute->getId(), $ref_id, "cat", $this->cms_type); + ObjectConnections::setConnection($course->institut_id, $ref_id, 'cat', $this->cms_type); } if ($ref_id < 1) { $ref_id = $this->main_category_node_id; } // Kurs anlegen - $lang_array = explode("_", Config::get()->DEFAULT_LANGUAGE); - $course_data["language"] = $lang_array[0]; - $course_data["title"] = "Stud.IP-Kurs " . $seminar->getName(); - $course_data["description"] = ""; + $lang_array = explode('_', Config::get()->DEFAULT_LANGUAGE); + $course_data['language'] = $lang_array[0]; + $course_data['title'] = 'Stud.IP-Kurs ' . $course->name; + $course_data['description'] = ''; $crs_id = $this->soap_client->addCourse($course_data, $ref_id); if ($crs_id == false) { $messages["error"] .= _("Zuordnungs-Fehler: Kurs konnte nicht angelegt werden."); diff --git a/lib/exceptions/Exception.php b/lib/exceptions/Exception.php new file mode 100644 index 0000000000000000000000000000000000000000..606c03c30f7ba658caad93dff1bdc4bdf191771a --- /dev/null +++ b/lib/exceptions/Exception.php @@ -0,0 +1,58 @@ +<?php + +/** + * Exception.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2019-2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +namespace Studip; + +/** + * This class is a specialisation of the standard Exception class + * to distinguish Stud.IP exceptions from standard exceptions. + */ +class Exception extends \Exception +{ + /** + * GENERAL_ERROR means that an unspecified error has occurred. + */ + const GENERAL_ERROR = 0; + + /** + * END_BEFORE_BEGINNING means that a time range is specified + * where the end lies before the beginning. + */ + const END_BEFORE_BEGINNING = 1; + + protected ?\Range $range = null; + + public function __construct(string $message = '', int $code = 0, ?\Range $range = null) + { + parent::__construct($message, $code); + $this->range = $range; + } + + /** + * Converts the content of the exception into an Information object. + * + * @return Information An Information representation of the exception. + */ + public function getInformation() : Information + { + return new Information( + $this->getMessage(), + \Studip\Information::ERROR, + (string) $this->getCode(), + $this->range + ); + } +} diff --git a/lib/exceptions/InvalidValuesException.php b/lib/exceptions/InvalidValuesException.php index aa86e2f7f6a5bc2e61f4fef5bfdfdd3405e56bd7..595ba9f10f543ae7a8f481ed4c236ff9e891e8c9 100644 --- a/lib/exceptions/InvalidValuesException.php +++ b/lib/exceptions/InvalidValuesException.php @@ -7,7 +7,7 @@ * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. - * + * * @author Peter Thienel <thienel@data-quest.de> * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 * @category Stud.IP @@ -17,21 +17,21 @@ class InvalidValuesException extends Exception { private $checked = []; - + /** * Constructor - * + * * @param string $message The error message * @param array $checked Associative array */ - public function __construct($message, $checked) + public function __construct(string $message, array $checked = []) { $this->checked = $checked; parent::__construct($message); } - + public function getChecked() { return $this->checked; } -} \ No newline at end of file +} diff --git a/lib/exceptions/StudipException.php b/lib/exceptions/StudipException.php deleted file mode 100644 index 369ba6fce745d96e8644d979b41309d7cefe951b..0000000000000000000000000000000000000000 --- a/lib/exceptions/StudipException.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -/** - * StudipException.php - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Moritz Strohm <strohm@data-quest.de> - * @copyright 2019 - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ - -/** - * This class is a specialisation of the standard Exception class - * with a specialisation to display exception data in Message boxes. - */ -class StudipException extends Exception -{ - protected $data = []; - - - public function __construct($message = '', Array $data = [], $code = 0, Throwable $previous = null) - { - parent::__construct($message, $code, $previous); - $this->data = $data; - } - - public function getData() - { - return $this->data; - } -} diff --git a/lib/exceptions/course/EnrolmentException.php b/lib/exceptions/course/EnrolmentException.php new file mode 100644 index 0000000000000000000000000000000000000000..f430feb7e04bf28365f9f94471be6b71f5ff6609 --- /dev/null +++ b/lib/exceptions/course/EnrolmentException.php @@ -0,0 +1,60 @@ +<?php +/** + * EnrolmentException.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023-2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +namespace Studip; + +class EnrolmentException extends Exception +{ + /** + * ALREADY_MEMBER means that enrolment failed because the user + * is already a member of the course. + */ + const ALREADY_MEMBER = 1; + + /** + * INVALID_PERMISSION_LEVEL means that the permission level of the user + * in the course is invalid. + */ + const INVALID_PERMISSION_LEVEL = 2; + + /** + * PROMOTION_NOT_POSSIBLE means that the user cannot get a higher permission + * level in the course. + */ + const PROMOTION_NOT_POSSIBLE = 3; + + /** + * DEMOTION_NOT_POSSIBLE means that the user cannot get a lower permission + * level in the course. + */ + const DEMOTION_NOT_POSSIBLE = 4; + + /** + * NO_INSTITUTE_MEMBER means that enrolment failed because the user + * is not the member of an institute the course is assigned to. + */ + const NO_INSTITUTE_MEMBER = 5; + + /** + * COURSE_IS_FULL means that no free seat is available for enrolling + * another user. + */ + const COURSE_IS_FULL = 10; + + /** + * ADD_AWAITING_FAILED means that adding a user to the wait list failed. + */ + const ADD_AWAITING_FAILED = 11; +} diff --git a/lib/exceptions/course/MembershipException.php b/lib/exceptions/course/MembershipException.php new file mode 100644 index 0000000000000000000000000000000000000000..289b8a73e6a43570dd260efc205117101e91827c --- /dev/null +++ b/lib/exceptions/course/MembershipException.php @@ -0,0 +1,42 @@ +<?php +/** + * MembershipException.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023-2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +namespace Studip; + +class MembershipException extends Exception +{ + /** + * NOT_A_MEMBER means that the user is not a member of the course. + */ + const NOT_A_MEMBER = 1; + + /** + * REMOVAL_FAILED means that the removal of the user from the course + * was unsuccessful. + */ + const REMOVAL_FAILED = 2; + + /** + * USER_IS_SOLE_LECTURER means that the user that shall be removed + * from the course is the sole lecturer of the course. + */ + const USER_IS_SOLE_LECTURER = 2; + + /** + * MOVING_POSITION_FAILED means that moving a course member to + * another position was unsuccessful. + */ + const MOVING_POSITION_FAILED = 10; +} diff --git a/lib/exceptions/tools/ToolException.php b/lib/exceptions/tools/ToolException.php new file mode 100644 index 0000000000000000000000000000000000000000..fe1813ffb0fcab6ae6aa95ad5b2e1befdfa5afac --- /dev/null +++ b/lib/exceptions/tools/ToolException.php @@ -0,0 +1,29 @@ +<?php +/** + * ToolException.class.php + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @author Moritz Strohm <strohm@data-quest.de> + * @copyright 2023 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + */ + +namespace Studip; + +/** + * ToolException is for exceptions that occur in the plugin management + * or the core course tools (modules). + */ +class ToolException extends Exception +{ + /** + * TOOL_NOT_ACTIVATED means that a tool or plugin shall be loaded + * or used which is not activated. + */ + const TOOL_NOT_ACTIVATED = 1; +} diff --git a/lib/extern/ExternPageCourseDetails.php b/lib/extern/ExternPageCourseDetails.php index 06f009debc848fae16ec4cf96c22aa72f59edc26..8cbc7a12a1bd521f740c450cbe7331e87167e076 100644 --- a/lib/extern/ExternPageCourseDetails.php +++ b/lib/extern/ExternPageCourseDetails.php @@ -124,13 +124,12 @@ class ExternPageCourseDetails extends ExternPage */ protected function getContentCourse(Course $course): array { - $seminar = new Seminar($course); $content = [ 'TITLE' => $course->name, 'SUBTITLE' => $course->untertitel, 'FULLNAME' => $course->getFullName(), 'SEMESTER' => $course->getTextualSemester(), - 'CYCLE' => $seminar->getDatesExport(), + 'CYCLE' => $course->getAllDatesInSemester(), 'ROOM' => $course->ort, 'NUMBER' => $course->veranstaltungsnummer, 'PRELIM_DISCUSSION' => vorbesprechung($course->id, 'export'), @@ -146,7 +145,7 @@ class ExternPageCourseDetails extends ExternPage 'ORGA' => $course->lernorga, 'CERTIFICATE' => $course->leistungsnachweis, 'ECTS' => $course->ects, - 'FIRST_MEETING' => $seminar->getFirstDate('export'), + 'FIRST_MEETING' => $course->getFirstDate(), 'HOME_INST_NAME' => $course->home_institut->name, 'HOME_INST_ID' => $course->home_institut->id, 'COUNT_USER' => count($course->members), diff --git a/lib/extern/ExternPageCourses.php b/lib/extern/ExternPageCourses.php index e1868c9c4e0eab661e87dbab37b9e32a93e16d02..f9d0f47af468b484912b2ea67c67cac322d671ad 100644 --- a/lib/extern/ExternPageCourses.php +++ b/lib/extern/ExternPageCourses.php @@ -451,7 +451,7 @@ class ExternPageCourses extends ExternPage 'SEMESTER' => $course->getFullName('sem-duration-name'), 'FORM' => $course->art, 'ROOM' => $course->ort, - 'CYCLE' => Seminar::getInstance($course->id)->getDatesExport(['show_room' => true]), + 'CYCLE' => implode("\n", $course->getAllDatesInSemester()->toStringArray(true)), 'AVATAR_URL' => $course->getItemAvatarURL(), 'INFO_URL' => $course->getItemURL(), 'LECTURERS' => $this->getContentMembers($course, 'dozent'), diff --git a/lib/ilias_interface/ConnectedIlias.php b/lib/ilias_interface/ConnectedIlias.php index 5b9c1bf742997a960c91a430cc6a051a8d099c27..48eabb7d49ae6811981ba98050e96f14b5c8c023 100644 --- a/lib/ilias_interface/ConnectedIlias.php +++ b/lib/ilias_interface/ConnectedIlias.php @@ -925,21 +925,21 @@ class ConnectedIlias $this->soap_client->clearCache(); if (!$crs_id) { - $seminar = Seminar::getInstance($studip_course_id); + $course = Course::find($studip_course_id); // on error use root category $ref_id = $this->ilias_config['root_category']; if ($this->ilias_config['cat_semester'] == 'outer') { // category for semester above institute - $semester_ref_id = IliasObjectConnections::getConnectionModuleId($seminar->start_semester->getId(), 'cat', $this->index); + $semester_ref_id = IliasObjectConnections::getConnectionModuleId($course->start_semester->id, 'cat', $this->index); if (!$semester_ref_id) { - $object_data['title'] = $seminar->getStartSemesterName(); - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); + $object_data['title'] = $course->start_semester->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $course->start_semester->name); $object_data['type'] = 'cat'; $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); $semester_ref_id = $this->soap_client->addObject($object_data, $ref_id); if ($semester_ref_id) { // store institute category - IliasObjectConnections::setConnection($seminar->start_semester->getId(), $semester_ref_id, 'cat', $this->index); + IliasObjectConnections::setConnection($course->start_semester->id, $semester_ref_id, 'cat', $this->index); } else { $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data['title']); } @@ -947,19 +947,18 @@ class ConnectedIlias if ($semester_ref_id) { $ref_id = $semester_ref_id; // category for home institute below semester - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $institute_ref_id = IliasObjectConnections::getConnectionModuleId(md5($seminar->start_semester->getId().$home_institute->getId()), "cat", $this->index); + if ($course->home_institut) { + $institute_ref_id = IliasObjectConnections::getConnectionModuleId(md5($course->start_semester->getId() . $course->home_institut->id), 'cat', $this->index); } if (!$institute_ref_id) { - $object_data['title'] = $home_institute->name; - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); + $object_data['title'] = $course->home_institut->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $course->home_institut->name); $object_data['type'] = 'cat'; $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); if ($institute_ref_id) { // store institute category - IliasObjectConnections::setConnection(md5($seminar->start_semester->getId().$home_institute->getId()), $institute_ref_id, "cat", $this->index); + IliasObjectConnections::setConnection(md5($course->start_semester->getId() . $course->home_institut->id), $institute_ref_id, 'cat', $this->index); } } if ($institute_ref_id) { @@ -970,19 +969,18 @@ class ConnectedIlias } } elseif ($this->ilias_config['cat_semester'] === 'inner' || $this->ilias_config['cat_semester'] === 'none') { // category for home institute - $home_institute = Institute::find($seminar->getInstitutId()); - if ($home_institute) { - $institute_ref_id = IliasObjectConnections::getConnectionModuleId($home_institute->getId(), "cat", $this->index); + if ($course->home_institut) { + $institute_ref_id = IliasObjectConnections::getConnectionModuleId($course->home_institut->id, 'cat', $this->index); } if (!$institute_ref_id) { - $object_data['title'] = $home_institute->name; - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $home_institute->name); + $object_data['title'] = $course->home_institut->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zur Stud.IP-Einrichtung "%s".'), $course->home_institut->name); $object_data['type'] = 'cat'; $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); $institute_ref_id = $this->soap_client->addObject($object_data, $ref_id); if ($institute_ref_id) { // store institute category - IliasObjectConnections::setConnection($home_institute->getId(), $institute_ref_id, "cat", $this->index); + IliasObjectConnections::setConnection($course->home_institut->id, $institute_ref_id, 'cat', $this->index); } else { $this->error[] = sprintf(_('ILIAS-Kategorie %s konnte nicht angelegt werden.'), $object_data["title"]); } @@ -991,16 +989,16 @@ class ConnectedIlias $ref_id = $institute_ref_id; if ($this->ilias_config['cat_semester'] === 'inner') { // category for semester below institute - $institute_semester_ref_id = IliasObjectConnections::getConnectionModuleId(md5($home_institute->getId().$seminar->start_semester->getId()), 'cat', $this->index); + $institute_semester_ref_id = IliasObjectConnections::getConnectionModuleId(md5($course->home_institut->id . $course->start_semester->id), 'cat', $this->index); if (!$institute_semester_ref_id) { - $object_data['title'] = $seminar->getStartSemesterName(); - $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $seminar->getStartSemesterName()); + $object_data['title'] = $course->start_semester->name; + $object_data['description'] = sprintf(_('Hier befinden sich die Veranstaltungsdaten zum Semester "%s".'), $course->start_semester->name); $object_data['type'] = 'cat'; $object_data['owner'] = $this->soap_client->LookupUser($this->ilias_config['admin']); $institute_semester_ref_id= $this->soap_client->addObject($object_data, $ref_id); if ($institute_semester_ref_id) { // store institute category - IliasObjectConnections::setConnection(md5($home_institute->getId().$seminar->start_semester->getId()), $institute_semester_ref_id, 'cat', $this->index); + IliasObjectConnections::setConnection(md5($course->home_institut->id . $course->start_semester->id), $institute_semester_ref_id, 'cat', $this->index); } } if ($institute_semester_ref_id) { @@ -1016,17 +1014,17 @@ class ConnectedIlias $lang_array = explode('_', Config::get()->DEFAULT_LANGUAGE); $course_data['language'] = $lang_array[0]; if ($this->ilias_config['course_semester'] === 'old' || $this->ilias_config['course_semester'] === 'old_bracket') { - $course_data['title'] = sprintf(_('Stud.IP-Veranstaltung "%s"'), $seminar->getName()); + $course_data['title'] = sprintf(_('Stud.IP-Veranstaltung "%s"'), $course->name); } else { - $course_data['title'] = sprintf(_('%s'), $seminar->getName()); + $course_data['title'] = $course->name; } if ($this->ilias_config['course_semester'] === 'old_bracket' || $this->ilias_config['course_semester'] === 'bracket') { - $course_data['title'] .= ' ('.$seminar->getStartSemesterName().')'; + $course_data['title'] .= ' (' . $course->start_semester->name . ')'; } if ($this->ilias_config['course_veranstaltungsnummer']) { - $course_data['title'] .= ' '.$seminar->VeranstaltungsNummer; + $course_data['title'] .= ' '.$course->veranstaltungsnummer; } - $course_data['description'] = sprintf(_('Dieser Kurs enthält die Lernobjekte der Stud.IP-Veranstaltung "%s".'), $seminar->getName()); + $course_data['description'] = sprintf(_('Dieser Kurs enthält die Lernobjekte der Stud.IP-Veranstaltung "%s".'), $course->name); $crs_id = $this->soap_client->addCourse($course_data, $ref_id); if (!$crs_id) { $this->error[] = _('ILIAS-Kurs konnte nicht angelegt werden.'); diff --git a/lib/models/AdmissionApplication.php b/lib/models/AdmissionApplication.php index 7e8c3f5628617740b592fef05f069fba723463f2..538c9fd0d41357146468602729636f56b26bb825 100644 --- a/lib/models/AdmissionApplication.php +++ b/lib/models/AdmissionApplication.php @@ -185,14 +185,14 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject $messaging = new messaging; //Daten holen / Abfrage ob ueberhaupt begrenzt - $seminar = Seminar::GetInstance($seminar_id, true); + $course = Course::find($seminar_id, true); - if($seminar->isAdmissionEnabled()){ - $sem_preliminary = ($seminar->admission_prelim == 1); - $cs = $seminar->getCourseSet(); + if ($course->isAdmissionEnabled()) { + $sem_preliminary = $course->admission_prelim == 1; + $cs = $course->getCourseSet(); //Veranstaltung einfach auffuellen (nach Lostermin und Ende der Kontingentierung) - if (!$seminar->admission_disable_waitlist_move && $cs->hasAlgorithmRun()) { - $count = (int)$seminar->getFreeAdmissionSeats(); + if (!$course->admission_disable_waitlist_move && $cs->hasAlgorithmRun()) { + $count = $course->getFreeSeats(); $memberships = self::findBySQL( "seminar_id = ? AND status = 'awaiting' ORDER BY position LIMIT {$count}", [$seminar_id] @@ -204,18 +204,22 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject } else { $membership->status = 'accepted'; $affected = $membership->store(); - StudipLog::log('SEM_USER_ADD', $seminar->getId(), $membership->user_id,'accepted', $log_message); + StudipLog::log('SEM_USER_ADD', $course->id, $membership->user_id,'accepted', $log_message); } if ($affected) { //User benachrichtigen if ($send_message) { setTempLanguage($membership->user_id); if (!$sem_preliminary) { - $message = sprintf (_('Sie sind in die Veranstaltung **%s (%s)** eingetragen worden, da für Sie ein Platz frei geworden ist. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); + $message = sprintf (_('Sie sind in die Veranstaltung **%s (%s)** eingetragen worden, da für Sie ein Platz frei geworden ist. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), $course->getName(), $course->getFormattedTurnus(true)); } else { - $message = sprintf (_('Sie haben den Status vorläufig akzeptiert in der Veranstaltung **%s (%s)** erhalten, da für Sie ein Platz frei geworden ist.'), $seminar->getName(), $seminar->getFormattedTurnus(true)); + $message = sprintf( + _('Sie haben den Status "vorläufig akzeptiert" in der Veranstaltung **%s (%s)** erhalten, da für Sie ein Platz frei geworden ist.'), + $course->name, + implode(', ', $course->getAllDatesInSemester()->toStringArray(true)) + ); } - $subject = sprintf(_("Teilnahme an der Veranstaltung %s"), $seminar->getName()); + $subject = sprintf(_('Teilnahme an der Veranstaltung %s'), $course->name); restoreLanguage(); $messaging->insert_message($message, $membership->username, '____%system%____', false, false, '1', false, $subject, true); @@ -225,7 +229,7 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject //Warteposition der restlichen User neu eintragen AdmissionApplication::renumberAdmission($seminar_id, FALSE); } - $seminar->restore(); + $course->restore(); } } @@ -235,14 +239,14 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject * @param bool $send_message * @return void */ - public static function renumberAdmission (string $seminar_id, bool $send_message = true): void + public static function renumberAdmission(string $seminar_id, bool $send_message = true) : void { $messaging = new messaging; - $seminar = Seminar::GetInstance($seminar_id); - if ($seminar->isAdmissionEnabled()) { + $course = Course::find($seminar_id); + if ($course->isAdmissionEnabled()) { $admission_users = self::findBySQL( "seminar_id = ? AND status = 'awaiting' ORDER BY position", - [$seminar->id] + [$course->id] ); $position = 1; foreach ($admission_users as $admission) { @@ -251,10 +255,10 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject $username = $admission->user->username; setTempLanguage($admission->user_id); $message = sprintf(_('Sie sind auf der Warteliste der Veranstaltung **%s (%s)** hochgestuft worden. Sie stehen zur Zeit auf Position %s.'), - $seminar->name, - $seminar->getFormattedTurnus(), + $course->name, + implode(' ', $course->getAllDatesInSemester()->toStringArray()), $position); - $subject = sprintf(_('Ihre Position auf der Warteliste der Veranstaltung %s wurde verändert'), $seminar->name); + $subject = sprintf(_('Ihre Position auf der Warteliste der Veranstaltung %s wurde verändert'), $course->name); restoreLanguage(); $messaging->insert_message($message, $username, '____%system%____', FALSE, FALSE, '1', FALSE, $subject); diff --git a/lib/models/ContentTermsOfUse.php b/lib/models/ContentTermsOfUse.php index 1982dd6c56ab98efb7a286d84ab27accb0c3a455..02ff522997da955d78440b8d6e398e30bbaad523 100644 --- a/lib/models/ContentTermsOfUse.php +++ b/lib/models/ContentTermsOfUse.php @@ -202,12 +202,12 @@ class ContentTermsOfUse extends SimpleORMap //the group must also have a terminated signup deadline. if ($context_type === "course") { //check where this range_id comes from: - $seminar = Seminar::GetInstance($context_id); - $timed_admission = $seminar->getAdmissionTimeFrame(); + $course = Course::find($context_id); + $timed_admission = $course->getAdmissionTimeFrame(); - if ($seminar->admission_prelim - || $seminar->isPasswordProtected() - || $seminar->isAdmissionLocked() + if ($course->admission_prelim + || $course->isPasswordProtected() + || $course->isAdmissionLocked() || (is_array($timed_admission) && $timed_admission['end_time'] > 0 && $timed_admission['end_time'] < time()) ) { return true; diff --git a/lib/models/Course.php b/lib/models/Course.php index d8bb2f5779a40e9711502792a3fa964313e8bcd0..ea6676c14b6902e14e86542afacdcaef0e2d5862 100644 --- a/lib/models/Course.php +++ b/lib/models/Course.php @@ -144,6 +144,24 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe 'on_delete' => 'delete', 'on_store' => 'store', ]; + $config['has_many']['scm_entries'] = [ + 'class_name' => StudipScmEntry::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_many']['wiki_pages'] = [ + 'class_name' => WikiPage::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store' + ]; + $config['has_many']['news'] = [ + 'class_name' => StudipNews::class, + 'thru_table' => 'news_range', + 'thru_key' => 'range_id', + 'thru_assoc_key' => 'news_id', + ]; $config['has_many']['blubberthreads'] = [ 'class_name' => BlubberThread::class, 'assoc_func' => 'findBySeminar', @@ -182,10 +200,12 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe 'on_store' => 'store', ]; $config['has_and_belongs_to_many']['institutes'] = [ - 'class_name' => Institute::class, - 'thru_table' => 'seminar_inst', - 'on_delete' => 'delete', - 'on_store' => 'store', + 'class_name' => Institute::class, + 'thru_table' => 'seminar_inst', + 'thru_key' => 'seminar_id', + 'thru_assoc_key' => 'institut_id', + 'on_delete' => 'delete', + 'on_store' => 'store', ]; $config['has_and_belongs_to_many']['domains'] = [ @@ -201,6 +221,11 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe 'assoc_foreign_key' => 'course_id', 'on_delete' => 'delete', ]; + $config['has_many']['resource_bookings'] = [ + 'class_name' => ResourceBooking::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete' + ]; $config['belongs_to']['parent'] = [ 'class_name' => Course::class, 'foreign_key' => 'parent_course' @@ -221,6 +246,13 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe 'on_delete' => 'delete', ]; + $config['has_many']['config_values'] = [ + 'class_name' => ConfigValue::class, + 'assoc_foreign_key' => 'range_id', + 'on_store' => 'store', + 'on_delete' => 'delete' + ]; + $config['has_many']['courseware_units'] = [ 'class_name' => \Courseware\Unit::class, 'assoc_foreign_key' => 'range_id', @@ -278,10 +310,70 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe "UPDATE `seminare` SET `parent_course` = NULL WHERE `parent_course` = :course", ['course' => $course->id] ); - DBManager::get()->execute( - "DELETE FROM `forum_visits` WHERE `seminar_id` = ?", - [$course->id] - ); + + //Delete forum entries: + foreach (PluginEngine::getPlugins(ForumModule::class) as $forum_tool) { + $forum_tool->deleteContents($course->id); + } + + //Delete all files: + $folder = Folder::findTopFolder($course->id); + if ($folder) { + $folder->delete(); + } + + //Unlink all news and delete them in RSS feeds: + StudipNews::DeleteNewsRanges($course->id); + StudipNews::UnsetRssId($course->id); + + //Cleanup remaining wiki table entries: + $query = 'DELETE FROM `wiki_links` WHERE `range_id` = ?'; + $statement = DBManager::get()->execute($query, [$course->id]); + $query = 'DELETE FROM `wiki_locks` WHERE `range_id` = ?'; + $statement = DBManager::get()->execute($query, [$course->id]); + WikiPageConfig::deleteByRange_id($course->id); + + //Remove all entries of the course in calendars: + $query = 'DELETE FROM `schedule_seminare` WHERE `seminar_id` = ?'; + $statement = DBManager::get()->execute($query, [$course->id]); + + //Remove connections to other e-learning systems: + if (Config::get()->ELEARNING_INTERFACE_ENABLE) { + $cms_types = ObjectConnections::GetConnectedSystems($course->id); + foreach ($cms_types as $system) { + if (empty($GLOBALS['connected_cms'][$system])) { + continue; + } + ELearningUtils::loadClass($system); + $del_cms += $GLOBALS['connected_cms'][$system]->deleteConnectedModules($course->id); + } + } + + //Remove all entries in object_user_vists for the course: + object_kill_visits(null, $course->id); + + //Remove deputies: + Deputy::deleteByRange_id($course->id); + + //Remove user domains: + UserDomain::removeUserDomainsForSeminar($course->id); + + //Remove auto-insert entries: + AutoInsert::deleteSeminar($course->id); + + //Remove assignments to admission sets: + $cs = $this->getCourseSet(); + if ($cs) { + CourseSet::removeCourseFromSet($cs->getId(), $course->id); + $cs->load(); + if (!count($cs->getCourses()) && $cs->isGlobal() && $cs->getUserid() != '') { + $cs->delete(); + } + } + AdmissionPriority::unsetAllPrioritiesForCourse($course->id); + + //Create a log entry: + StudipLog::log('SEM_ARCHIVE', $course->id, NULL, $course->getFullName('number-name-semester')); }; parent::configure($config); @@ -487,7 +579,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe }); } - public function getFreeSeats() + public function getFreeSeats() : int { $free_seats = $this->admission_turnout - $this->getNumParticipants(); return max($free_seats, 0); @@ -506,6 +598,327 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return true; } + /** + * Determines whether the course has at least one course set attached to it. + * + * @return bool True, if the course has at least one course set, false otherwise. + */ + public function hasCourseSet() : bool + { + return CourseSet::countBySeminar_id($this->id) > 0; + } + + /** + * Retrieves the course set of th course, if the course is associated to a course set. + * + * @return CourseSet|null The course set of the course, if it is associated to one. + */ + public function getCourseSet() : ?CourseSet + { + return CourseSet::getSetForCourse($this->id); + } + + /** + * Determines whether the number of participants in this course is limited + * by a course set whose seat distribution is enabled. + * + * @return boolean True, if a course set exists and its seat distribution is enabled, + * false otherwise. + */ + public function isAdmissionEnabled() : bool + { + $cs = $this->getCourseSet(); + return $cs && $cs->isSeatDistributionEnabled(); + } + + /** + * Determines by the course set of the course (if any), whether the admission + * is locked or not. + * + * @return bool True, if the admission is locked, false otherwise. + */ + public function isAdmissionLocked() : bool + { + $cs = $this->getCourseSet(); + return $cs && $cs->hasAdmissionRule('LockedAdmission'); + } + + /** + * Determines by looking at the course set (if any), whether the course + * is password protected or not. + * + * @return bool True, fi the course is password protected, false otherwise. + */ + public function isPasswordProtected() : bool + { + $cs = $this->getCourseSet(); + return $cs && $cs->hasAdmissionRule('PasswordAdmission'); + } + + /** + * Determines if there is an admission time frame for this course by looking + * at the course set (if any). If such a time frame exists, it is returned + * as an associative array with the start and end timestamp. + * + * @returns array An associative array with the array keys "start_time" and "end_time" + * containing the start and end timestamp of the admission. In case no such time + * frame exists, an empty array is returned instead. + */ + public function getAdmissionTimeFrame() : array + { + $cs = $this->getCourseSet(); + if ($cs && $cs->hasAdmissionRule(TimedAdmission::class)) { + $rule = $cs->getAdmissionRule(TimedAdmission::class); + return [ + 'start_time' => $rule->getStartTime(), + 'end_time' => $rule->getEndTime() + ]; + } + return []; + } + + /** + * Adds a user as preliminary member to this course. + * + * @param User $user The user to be added as preliminary member. + * @param string $comment An optional comment for the preliminary membership. + * + * @return AdmissionApplication The AdmissionApplication object for the preliminary membership. + * + * @throws \Studip\Exception In case the user cannot be added as preliminary member. + */ + public function addPreliminaryMember(User $user, string $comment = '') : AdmissionApplication + { + $new_admission_member = new AdmissionApplication(); + $new_admission_member->user_id = $user->id; + $new_admission_member->position = 0; + $new_admission_member->status = 'accepted'; + $new_admission_member->comment = $comment; + + $this->admission_applicants[] = $new_admission_member; + if (!$new_admission_member->store()) { + throw new \Studip\Exception( + sprintf( + _('%1$s konnte nicht als vorläufig teilnehmende Person zur Veranstaltung %2$s hinzugefügt werden.'), + $user->getFullName(), + $this->name + ), + 'add_preliminary_failed' + ); + } + if ($this->isStudygroup()) { + StudygroupModel::applicationNotice($this->id, $user->id); + } + $course_set = $this->getCourseSet(); + if ($course_set) { + AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->id); + } + + //Create a log entry: + StudipLog::log('SEM_USER_ADD', $this->id, $user->id, 'accepted', 'Vorläufig akzeptiert'); + + return $new_admission_member; + } + + /** + * Removes a preliminary member from the course. + * + * @param User $user The member to be removed. + * + * @throws \Studip\Exception In case the user is not a preliminary member or in case they + * cannot be removed as preliminary member. + */ + public function removePreliminaryMember(User $user) : void + { + //Get the status of the user first: + $application = AdmissionApplication::findOneBySQL( + 'seminar_id = :course_id AND user_id = :user_id', + [ + 'course_id' => $this->id, + 'user_id' => $user->id + ] + ); + if (!$application) { + throw new \Studip\Exception( + sprintf( + _('%1$s ist nicht als vorläufig teilnehmende Person in der Veranstaltung %2$s eingetragen.'), + $user->getFullName(), + $this->name + ), + 'preliminary_member_not_found' + ); + } + + $deleted_from_course_set = false; + $course_set = $this->getCourseSet(); + if ($course_set) { + $deleted_from_course_set = AdmissionPriority::unsetPriority( + $course_set->getId(), + $user->id, + $this->id + ); + } + if ($application->delete() || $deleted_from_course_set) { + setTempLanguage($user->id); + $message = ''; + if ($application->status === 'accepted') { + $message = studip_interpolate( + _('Ihre vorläufige Anmeldung zur Veranstaltung %{name} wurde aufgehoben. Sie sind damit __nicht__ zugelassen worden.'), + ['name' => $this->getFullName()] + ); + } else { + $message = studip_interpolate( + _('Sie wurden von der Warteliste der Veranstaltung %{name} gestrichen. Sie sind damit __nicht__ zugelassen worden.'), + ['name' => $this->getFullName()] + ); + } + $messaging = new messaging(); + $messaging->insert_message( + $message, + $user->username, + '____%system%____', + false, + false, + '1', + false, + studip_interpolate( + _('%{course_name}: Sie wurden nicht zugelassen!'), + ['course_name' => $this->getFullName()] + ), + true + ); + restoreLanguage(); + StudipLog::log('SEM_USER_DEL', $this->id, $user->id, 'Wurde aus der Veranstaltung entfernt'); + } else { + throw new \Studip\Exception( + sprintf( + _('%1$s konnte nicht als vorläufig teilnehmende Person aus der Veranstaltung %2$s entfernt werden.'), + $user->getFullName(), + $this->name + ), + 'remove_preliminary_failed' + ); + } + } + + /** + * Adds a user to the waitlist of this course. + * + * @param User $user The user to be added onto the waitlist. + * + * @param int $position The position of the user on the waitlist. + * + * @param bool $send_mail Whether to send a mail to the user that has been added + * (true) or not (false). Defaults to true. + * + * @return AdmissionApplication The AdmissionApplication object for the added user. + * + * @throws \Studip\Exception In case the user cannot be added onto the waitlist. + */ + public function addMemberToWaitlist( + User $user, + int $position = PHP_INT_MAX, + bool $send_mail = true + ) : AdmissionApplication + { + $member_exists = AdmissionApplication::exists([$user->id, $this->id]) + || CourseMember::find([$this->id, $user->id]); + if ($member_exists) { + throw new \Studip\EnrolmentException( + sprintf( + _('%1$s ist bereits Mitglied der Veranstaltung %2$s.'), + $user->getFullName(), + $this->name + ), + \Studip\EnrolmentException::ALREADY_MEMBER + ); + } + if ($position === PHP_INT_MAX) { + //Append the user to the end of the waitlist. + //NOTE: If this method is called two times at the same time for the + //same course, there may be course members with the same position! + $position = DBManager::get()->fetchColumn( + "SELECT MAX(`position`) + FROM `admission_seminar_user` + WHERE `seminar_id` = :course_id + AND `status`='awaiting'", + ['course_id' => $this->id] + ); + if ($position === false) { + //No members on the waitlist. + $position = 0; + } + } + $new_admission_member = new AdmissionApplication(); + $new_admission_member->user_id = $user->id; + $new_admission_member->position = strval($position); + $new_admission_member->status = 'awaiting'; + $new_admission_member->seminar_id = $this->id; + if (!$new_admission_member->store()) { + throw new \Studip\EnrolmentException( + sprintf( + _('%1$s konnte nicht auf die Warteliste der Veranstaltung %2$s gesetzt werden.'), + $user->getFullName(), + $this->name + ), + \Studip\EnrolmentException::ADD_AWAITING_FAILED + ); + } + + //Reset the admission_applicants relation: + $this->resetRelation('admission_applicants'); + + //Renumber all members on the waitlist: + AdmissionApplication::renumberAdmission($this->id); + + //Create a log entry: + StudipLog::log( + 'SEM_USER_ADD', + $this->id, + $user->id, + 'awaiting', + sprintf('Auf Warteliste gesetzt, Position: %u', $position) + ); + + if ($send_mail) { + setTempLanguage($user->id); + $body = sprintf( + _('Sie wurden auf die Warteliste der Veranstaltung %s gesetzt.'), + $this->getFullName() + ); + $messaging = new messaging(); + $messaging->insert_message( + $body, + $user->username, + '____%system%____', + false, + false, + '1', + false, + _('Auf die Warteliste einer Veranstaltung eingetragen'), + true + ); + restoreLanguage(); + } + + //Everything went fine: Re-load the new admission member before returning it, + //since its position number may have changed during renumbering: + return AdmissionApplication::findOneBySQL( + '`user_id` = :user_id AND `seminar_id` = :course_id', + ['user_id' => $user->id, 'course_id' => $this->id] + ); + } + + /** + * Retrieves the course category for this course. + * + * @return SeminarCategories The category object of the course. + */ + public function getCourseCategory() : SeminarCategories + { + return SeminarCategories::GetByTypeId($this->status); + } + /** * Retrieves all members of a status * @@ -533,6 +946,579 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return CourseMember::countByCourseAndStatus($this->id, $status); } + /** + * Adds a user to this course. + * + * @param User $user The user to be added. + * @param string $permission_level The permission level the user shall get in the course. + * @param bool $regard_contingent Whether to regard the contingent of the course (true) + * or whether to ignore it (false). Defaults to true. + * @param bool $send_mail Whether to send a mail to the new participant (true) or not (false). + * Defaults to true. + * @param bool $renumber_admission Whether to call AdmissionApplication::renumberAdmission when + * the admission of the user has been removed (true) or whether not to renumber the admission + * entries (false). Defaults to true. + * Setting this parameter to false is useful when adding several users at once and then + * manually call AdmissionApplication::renumberAdmission so that the entries are renumbered + * only once after all the users have been added. + * + * @return CourseMember The CourseMember object for the user. + * + * @throws \Studip\EnrolmentException In case the user is already in the course but cannot get a higher permission level or + * they are the only lecturer and can therefore not get a lower permission level. + */ + public function addMember( + User $user, + string $permission_level = 'autor', + bool $regard_contingent = true, + bool $send_mail = true, + bool $renumber_admission = true + ) : CourseMember + { + //TODO: Put checks for entry into Course::getEnrolmentInformation. + //Checks regarding the promotion/demotion of users in courses shall be + //transferred to a new method. + + if (!in_array($permission_level, ['user', 'autor', 'tutor', 'dozent'])) { + throw new \Studip\EnrolmentException( + _('Die Rechtestufe ist für die Eintragung in eine Veranstaltung unpassend.'), + \Studip\EnrolmentException::INVALID_PERMISSION_LEVEL + ); + } + + $db = DBManager::get(); + + //In case the course only allows users of the institute to be members, + //we must check if the user is a member of the institute: + $course_category = $this->getCourseCategory(); + if ($course_category->only_inst_user) { + //Only institute members are allowed: + $stmt = $db->prepare( + "SELECT 1 + FROM `user_inst` + JOIN `seminar_inst` USING (`institute_id`) + WHERE `user_inst`.`user_id` = :user_id + AND `seminar_inst`.`seminar_id` = :course_id" + ); + $stmt->execute([ + 'course_id' => $this->id, + 'user_id' => $user->id, + ]); + $user_in_institute = $stmt->fetchColumn(); + if (!$user_in_institute) { + throw new \Studip\EnrolmentException( + _('Die einzutragende Person ist kein Mitglied einer Einrichtung, zu der die Veranstaltung zugeordnet ist.'), + \Studip\EnrolmentException::NO_INSTITUTE_MEMBER + ); + } + } + + //Load the course member object: + $course_member = CourseMember::findOneBySQL( + '`seminar_id` = :course_id AND `user_id` = :user_id', + ['course_id' => $this->id, 'user_id' => $user->id] + ); + $new_member_position = $db->fetchColumn( + 'SELECT MAX(`position`) + 1 + FROM `seminar_user` + WHERE `status` = :status + AND `seminar_id` = :course_id', + ['status' => $permission_level, 'course_id' => $this->id] + ) ?? 0; + $number_of_lecturers = CourseMember::countByCourseAndStatus($this->id, 'dozent'); + + if (!$course_member) { + $course_member = new CourseMember(); + $course_member->seminar_id = $this->id; + $course_member->user_id = $user->id; + $course_member->status = $permission_level; + } + $course_member->position = $new_member_position; + if (in_array($permission_level, ['tutor', 'dozent'])) { + //Tutors and lecturers are always visible in the course: + $course_member->visible = 'yes'; + } else { + //All others may decide for themselves: + $course_member->visible = 'unknown'; + } + + $ranks = array_flip(['user', 'autor', 'tutor', 'dozent']); + + if ($course_member->isNew()) { + //The user shall be added to the course. Before storing, we must check + //if the contingent shall be regarded and if there is a free seat + //for the user: + + //TODO: Move the following check back to controllers. + //Background: Lecturers may enforce the entry of a student, but the latter must not + //override the checks. + if ( + $permission_level === 'autor' + && $regard_contingent + && $this->isAdmissionEnabled() + && $this->getFreeSeats() < 1 + ) { + //There is no free seat to add another member. + throw new \Studip\EnrolmentException( + sprintf( + _('Für %s ist kein Platz mehr in der Veranstaltung frei.'), + $user->getFullName() + ), + \Studip\EnrolmentException::COURSE_IS_FULL + ); + } + + $course_member->store(); + + //Delete the user from admission applications: + $application_removed = AdmissionApplication::deleteBySQL( + '`user_id` = :user_id AND `seminar_id` = :course_id', + ['user_id' => $user->id, 'course_id' => $this->id] + ); + if ($application_removed && $renumber_admission) { + //Renumber the waitlist or the other admission list: + AdmissionApplication::renumberAdmission($this->id); + } + + //Remove the user from the course set, if any: + $course_set = $this->getCourseSet(); + $removed_from_course_set = 0; + if ($course_set) { + $removed_from_course_set = AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->id); + } + + if ($permission_level === 'dozent' && Config::get()->DEPUTIES_ENABLE) { + //Delete a possible deputy entry for the lecturer: + $deputy = Deputy::find([$this->id, $user->id]); + if ($deputy) { + $deputy->delete(); + } + + //Assign all default deputies of the lecturer to the course + //if they are not already a lecturer of the course: + $unassigned_deputies = Deputy::findBySQL( + "`range_id` = :lecturer_id + AND `user_id` NOT IN ( + SELECT `user_id` FROM `seminar_user` + WHERE `seminar_id` = :course_id + AND `status` = 'dozent' + )", + [ + 'lecturer_id' => $user->id, + 'course_id' => $this->id + ] + ); + foreach ($unassigned_deputies as $deputy) { + Deputy::addDeputy($deputy->user_id, $this->id); + } + } + + //Delete course entries in the schedule: + CalendarScheduleModel::deleteSeminarEntries($user->id, $this->id); + + //Log the event: + StudipLog::log('SEM_USER_ADD', $this->id, $user->id, $permission_level, 'Wurde in die Veranstaltung eingetragen'); + + if ($this->parent instanceof Course) { + $this->parent->addMember($user, $permission_level, false); + } + + if ($send_mail) { + setTempLanguage($user->id); + $body = ''; + $subject = ''; + if ($application_removed) { + //Enrolment after being on the wait list: + $subject = _('Zulassung zur Veranstaltung'); + $body = sprintf( + _('Sie wurden für die Veranstaltung %s zugelassen. Ihr Eintrag auf der Warteliste wurde daher entfernt.'), + $this->getFullName() + ); + } elseif ($removed_from_course_set) { + //Enrolment after being in a course set: + $subject = _('Zulassung zur Veranstaltung'); + $body = sprintf( + _('Sie wurden für die Veranstaltung %s endgültig zugelassen.'), + $this->getFullName() + ); + } else { + //Direct enrolment without waitlist or course set: + $subject = _('Eintragung in Veranstaltung'); + $body = sprintf( + _('Sie wurden in die Veranstaltung %s eingetragen.'), + $this->getFullName() + ); + } + $messaging = new messaging(); + $messaging->insert_message( + $body, + $user->username, + '____%system%____', + false, + false, + '1', + false, + $subject, + true + ); + restoreLanguage(); + } + } elseif ($ranks[$course_member->status] < $ranks[$permission_level] + && $course_member->status !== 'dozent' || $number_of_lecturers > 1) { + //The user is already a member of the course. They shall either be promoted + //or they are not a lecturer or there is more than one lecturer in the course + //(please read this multiple times in case you are unsure about these conditions). + + $course_member->status = $permission_level; + $course_member->position = $new_member_position; + + $success = !$course_member->isDirty() || $course_member->store(); + + if (!$success) { + throw new \Studip\EnrolmentException( + _('Die Person kann nicht hochgestuft werden.'), + \Studip\EnrolmentException::PROMOTION_NOT_POSSIBLE + ); + } + } elseif ($course_member->status === 'dozent' && $number_of_lecturers <= 1) { + throw new \Studip\EnrolmentException( + sprintf( + _('Die Person kann nicht herabgestuft werden, da mindestens eine lehrende Person (%1$s) in die Veranstaltung eingetragen sein muss! Tragen Sie deshalb zuerst eine weitere Person als lehrende Person (%1$s) ein und versuchen Sie es dann erneut!'), + get_title_for_status('dozent', 1, $this->status) + ), + \Studip\EnrolmentException::DEMOTION_NOT_POSSIBLE + ); + } + $this->resetRelation('members'); + + return $course_member; + } + + /** + * Removes a user from this course. + * + * @param User $user The user to be removed. + * @param bool $send_mail Whether to send a mail after the membership deletion + * (true) or not (false). Defaults to false. + * + * @return void If this method does not throw, everything went fine. + * + * @throws \Studip\MembershipException If the user cannot be removed from the course. + */ + public function deleteMember(User $user, bool $send_mail = false) : void + { + $membership = CourseMember::findOneBySQL( + 'seminar_id = :course_id AND user_id = :user_id', + ['course_id' => $this->id, 'user_id' => $user->id] + ); + if (!$membership) { + //The user is not a member of the course. + throw new \Studip\MembershipException( + sprintf( + _('%1$s ist kein Mitglied der Veranstaltung %2$s.'), + $user->getFullName(), + $this->name + ), + \Studip\MembershipException::NOT_A_MEMBER, + $user + ); + } + + if ($membership->status === 'dozent') { + //Check if there are enough lecturers left: + $lecturer_amount = CourseMember::countByCourseAndStatus($this->id, 'dozent'); + if ($lecturer_amount < 2) { + //Not enough lecturers left. + throw new \Studip\MembershipException( + sprintf( + _('In die Veranstaltung muss mindestens eine lehrende Person (%s) eingetragen sein. Um diese Person aus der Veranstaltung zu entfernen, muss zunächst eine weitere lehrende Person eingetragen werden.'), + get_title_for_status('dozent', 1, $this->status) + ), + \Studip\MembershipException::USER_IS_SOLE_LECTURER, + $user + ); + } + } + + //At this point, the user may be removed. + $success = $membership->delete(); + if (!$success) { + throw new \Studip\MembershipException( + sprintf( + _('Es trat ein Fehler auf beim Austragen von %1$s aus der Veranstaltung %2$s.'), + $user->getFullName(), + $this->getFullname() + ), + \Studip\MembershipException::REMOVAL_FAILED, + $user + ); + } + + $removed_from_parent = false; + $removed_from_children = false; + + if ($this->parent_course) { + //This course has a parent course. + //Delete the user from the parent course if they are not part of + //one of the other child courses. + $other_memberships = CourseMember::countBySql( + 'JOIN `seminare` USING (`seminar_id`) + WHERE `user_id` = :user_id + AND `parent_course` = :parent_course_id + AND `seminar_id` <> :this_course_id', + [ + 'user_id' => $user->id, + 'parent_course_id' => $this->parent_course->id, + 'this_course_id' => $this->id + ] + ); + if ($other_memberships === 0) { + //No other memberships. We can delete the user from the parent course. + $this->parent_course->deleteMember($user, false); + $removed_from_parent = true; + } + } + + if ($this->children) { + //The other way around: This course has child courses and because the user + //has been removed from this course, they shall also be removed from all + //child courses. + foreach ($this->children as $child) { + $child->deleteMember($user); + } + $removed_from_children = true; + } + + if ($send_mail) { + $messaging = new messaging(); + setTempLanguage($user->id); + $subject = sprintf(_('%s: Anmeldung aufgehoben'), $this->getFullName()); + $body = sprintf(_('Ihre Anmeldung für die Veranstaltung %s wurde aufgehoben.'), $this->getFullName()); + $messaging->insert_message( + $body, + $user->username, + '____%system%____', + false, + false, + '1', + false, + $subject, + true + ); + restoreLanguage(); + } + + if ($membership->status === 'dozent') { + //Special treatment for lecturers: + //Remove them from course dates and remove them as deputies. + + $db = DBManager::get(); + $stmt = $db->prepare( + 'DELETE FROM `termin_related_persons` + WHERE `user_id` = :user_id + AND `range_id` IN ( + SELECT `termin_id` FROM `termine` + WHERE `range_id` = :course_id + )' + ); + $stmt->execute(['course_id' => $this->id, 'user_id' => $user->id]); + + if (Deputy::isActivated()) { + //For all courses where the user is a deputy, they can be removed as deputy + //from the course, if the other lecturers are no deputies and the current user + //is not a deputy: + $all_user_deputy_duties = Deputy::findByRange_id($user->id); + foreach ($all_user_deputy_duties as $deputy_duty) { + $other_deputy_amount = Deputy::countBySql( + "JOIN `seminar_user` + ON `seminar_user`.`user_id` = `deputies`.`range_id` + WHERE `seminar_user`.`user_id` <> :deleted_user_id + AND `seminar_user`.`status` = 'dozent'", + ['deleted_user_id' => $user->id] + ); + if ($other_deputy_amount === 0 && $GLOBALS['user']->id != $deputy_duty->user_id) { + Deputy::deleteBySQL( + '`range_id` = :course_id AND `user_id` = :deputy_id', + ['course_id' => $this->id, $deputy_duty->user_id] + ); + } + } + } + } + + //Delete data field entries that are related to the user and the course: + DatafieldEntryModel::deleteBySQL( + '`range_id` = :user_id AND `sec_range_id` = :course_id', + ['user_id' => $user->id, 'course_id' => $this->id] + ); + + //Remove the user from course groups: + if ($this->statusgruppen) { + foreach ($this->statusgruppen as $group) { + $group->removeUser($user->id, true); + } + } + + StudipLog::log('SEM_USER_DEL', $this->id, $user->id, 'Wurde aus der Veranstaltung entfernt'); + + $this->resetRelation('members'); + + //At this point, removal is complete. + } + + /** + * Moves a regular course member back onto the waitlist. + * + * @param User $user The course member to be moved back to the waitlist. + * @param bool $send_mail Whether to send a mail to inform the user of them + * being moved back to the waitlist (true) or not (false). Defaults to false. + * + * @return void + * + * @throws \Studip\Exception In case the former course member cannot be moved to the waitlist. + * + * @throws \Studip\MembershipException In case the membership cannot be terminated. + */ + public function moveMemberToWaitlist(User $user, bool $send_mail = false): void + { + $this->deleteMember($user); + $this->addMemberToWaitlist($user, PHP_INT_MAX, false); + + if ($send_mail) { + setTempLanguage($user->id); + $subject = studip_interpolate( + _('%{course}: Anmeldung aufgehoben, auf Warteliste gesetzt'), + ['course' => $this->getFullName()] + ); + $message = studip_interpolate( + _('Sie wurden aus der Veranstaltung %{course} abgemeldet und auf die zugehörige Warteliste gesetzt.'), + ['course' => $this->getFullName()] + ); + messaging::sendSystemMessage($user->id, $subject, $message); + restoreLanguage(); + } + } + + /** + * Swaps the course member position with another member. This is done by specifying a course member + * and the new position where they shall be placed in the course. + * + * @param CourseMember $membership The course member to move to another position. + * + * @return int The new position of the course member. + * + * @throws \Studip\MembershipException In case when moving the member position was unsuccessful. + */ + public function swapMemberPosition(CourseMember $membership, int $new_position): int + { + //At this point, the user is not at the highest position. + //Load the member with the position $position + 1 and swap the positions. + + $next_member = CourseMember::findOneBySQL( + '`seminar_id` = :course_id AND `status` = :permission_level AND `position` = :new_position', + [ + 'course_id' => $this->id, + 'permission_level' => $membership->status, + 'new_position' => strval($new_position) + ] + ); + $success = false; + if ($next_member) { + $swapped_position = $next_member->position; + $next_member->position = $membership->position; + $membership->position = $swapped_position; + + $next_member->store(); + $success = !$membership->isDirty() || $membership->store(); + } else { + //There is a gap in the position numbers. The user can just be placed to the new position: + $membership->position = $new_position; + $success = !$membership->isDirty() || $membership->store(); + } + + if (!$success) { + //Something went wrong. + throw new \Studip\MembershipException( + sprintf( + _('%1$s konnte nicht an die Position %2$u verschoben werden.'), + $membership->user->getFullName(), + $new_position + ), + \Studip\MembershipException::MOVING_POSITION_FAILED, + $membership->user + ); + } + return (int) $membership->position; + } + + /** + * Moves a course member one position up. + * + * @param User $user The user to move up. + * + * @return int The new position of the user. + */ + public function moveMemberUp(User $user) : int + { + $membership = CourseMember::findOneBySQL( + '`seminar_id` = :course_id AND `user_id` = :user_id', + ['course_id' => $this->id, 'user_id' => $user->id] + ); + if (!$membership) { + //The user is not a member. + return -1; + } + + if ($membership->position == 0) { + //The user is already at the highest position. + return 0; + } + return $this->swapMemberPosition($membership, intval($membership->position - 1)); + } + + /** + * Moves a course member one position down. + * + * @param User $user The user to move down. + * + * @return int The new position of the user. + */ + public function moveMemberDown(User $user) : int + { + $membership = CourseMember::findOneBySQL( + '`seminar_id` = :course_id AND `user_id` = :user_id', + ['course_id' => $this->id, 'user_id' => $user->id] + ); + if (!$membership) { + //The user is not a member. + return -1; + } + + //Get the maximum number for the permission level in the course: + $stmt = DBManager::get()->prepare( + 'SELECT MAX(`position`) + FROM `seminar_user` + WHERE `seminar_id` = :course_id + AND `status` = :permission_level' + ); + $stmt->execute([ + 'course_id' => $this->id, + 'permission_level' => $membership->status, + ]); + $max_number = $stmt->fetchColumn(); + if ($max_number === false) { + //Nothing there to move. + return -1; + } + + if ($membership->position == $max_number) { + //The user is already at the lowest position. + return (int) $max_number; + } + + return $this->swapMemberPosition($membership, intval($membership->position + 1)); + } + public function getNumParticipants() { return $this->countMembersWithStatus('user autor') + $this->getNumPrelimParticipants(); @@ -563,6 +1549,188 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return $p_status; } + /** + * Determines the enrolment status of the user and their possibilities + * to join the course. + * + * @param string $user_id The ID of the user for which to get enrolment information. + * + * @return \Studip\EnrolmentInformation The enrolment information + * for the specified user. + */ + public function getEnrolmentInformation(string $user_id) : \Studip\EnrolmentInformation + { + //Check the course itself: + + if ($this->getSemClass()->isGroup()) { + return new \Studip\EnrolmentInformation( + _('Diese Veranstaltung ist die Hauptveranstaltung einer Veranstaltungsgruppe. Sie können sich nur in die zugehörigen Unterveranstaltungen eintragen.'), + \Studip\Information::INFO, + 'main_course', + false + ); + } + + //Check the course set and if the user is on an admission list: + + if ($course_set = $this->getCourseSet()) { + $info = new \Studip\EnrolmentInformation(''); + $info->setCodeword('course_set'); + $info->setEnrolmentAllowed(true); + $message = _('Die Anmeldung zu dieser Veranstaltung folgt bestimmten Regeln.'); + $priority = AdmissionPriority::getPrioritiesByUser($course_set->getId(), $user_id); + if (!empty($priority[$this->id])) { + if ($course_set->hasAdmissionRule('LimitedAdmission')) { + $message .= ' ' . sprintf( + _('Sie stehen auf der Anmeldeliste für die automatische Platzverteilung der Veranstaltung mit der Priorität %u.'), + $priority[$this->id] + ); + } else { + $message .= ' ' . _('Sie stehen auf der Anmeldeliste für die automatische Platzverteilung der Veranstaltung.'); + } + } + $info->setMessage($message); + return $info; + } + + if ($this->lesezugriff == '0' && Config::get()->ENABLE_FREE_ACCESS && !$GLOBALS['perm']->get_studip_perm($this->id, $user_id)) { + return new \Studip\EnrolmentInformation( + _('Für diese Veranstaltung ist keine Anmeldung erforderlich.'), + \Studip\Information::INFO, + 'free_access', + true + ); + } + + //Check the visibility of the course for the user: + if ( + !$this->visible + && !$this->isStudygroup() + && !$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id) + ) { + return new \Studip\EnrolmentInformation( + _('Sie dürfen sich in diese Veranstaltung nicht eintragen.'), + \Studip\Information::INFO, + 'invisible', + false + ); + } + + //Check the lock rule for participants: + if (LockRules::Check($this->id, 'participants')) { + return new \Studip\EnrolmentInformation( + _('Sie dürfen sich in diese Veranstaltung nicht selbst eintragen.'), + \Studip\Information::INFO, + 'locked', + false + ); + } + + //Check the permissions of the user: + + $user = User::find($user_id); + + if (!$user) { + return new \Studip\EnrolmentInformation( + _('Sie sind nicht in Stud.IP angemeldet und können sich daher nicht in die Veranstaltung eintragen.'), + \Studip\Information::WARNING, + 'nobody', + false + ); + } + if (!$GLOBALS['perm']->have_perm('user', $user_id)) { + return new \Studip\EnrolmentInformation( + _('Sie haben keine ausreichende Berechtigung, um sich in die Veranstaltung einzutragen.'), + \Studip\Information::INFO, + 'user', + false + ); + } + if ($GLOBALS['perm']->have_perm('root', $user_id)) { + return new \Studip\EnrolmentInformation( + _('Sie haben root-Rechte und dürfen damit alles in Stud.IP.'), + \Studip\Information::INFO, + 'root', + true + ); + } + if ($GLOBALS['perm']->have_studip_perm('admin', $this->id, $user_id)) { + return new \Studip\EnrolmentInformation( + _('Sie verwalten diese Veranstaltung.'), + \Studip\Information::INFO, + 'course_admin', + true + ); + } + if ($GLOBALS['perm']->have_perm('admin', $user_id)) { + return new \Studip\EnrolmentInformation( + _('Als administrierende Person dürfen Sie sich nicht in eine Veranstaltung eintragen.'), + \Studip\Information::INFO, + 'admin', + false + ); + } + + //Check the course membership: + + if ($GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id)) { + return new \Studip\EnrolmentInformation( + _('Sie sind bereits in der Veranstaltung eingetragen.'), + \Studip\Information::INFO, + 'already_member', + true + ); + } + + //Check the admission status: + + $admission_status = $user->admission_applications->findBy('seminar_id', $this->id)->val('status'); + if ($admission_status === 'accepted') { + return new \Studip\EnrolmentInformation( + _('Sie wurden für diese Veranstaltung vorläufig akzeptiert.'), + \Studip\Information::INFO, + 'preliminary_accepted', + false + ); + } elseif ($admission_status === 'awaiting') { + return new \Studip\EnrolmentInformation( + _('Sie sind auf der Warteliste für diese Veranstaltung.'), + \Studip\Information::INFO, + 'on_waitlist', + false + ); + } + + //Check the user domain: + $user_domains = UserDomain::getUserDomainsForUser($user_id); + if (count($user_domains) > 0) { + //The user is in at least one domain. Check if the course is in one of them. + $course_domains = UserDomain::getUserDomainsForSeminar($this->id); + if ( + !UserDomain::checkUserVisibility($course_domains, $user_domains) + && !$this->isStudygroup() + ) { + //The user is not in the same domain as the course and the course + //is not a studygroup. + return new \Studip\EnrolmentInformation( + _('Sie sind nicht in der gleichen Domäne wie die Veranstaltung und können sich daher nicht für die Veranstaltung eintragen.'), + \Studip\Information::INFO, + 'wrong_domain', + false + ); + } + } + + //In all other cases, enrolment is allowed. + return new \Studip\EnrolmentInformation( + _('Sie können sich zur Veranstaltung anmelden.'), + \Studip\Information::INFO, + 'allowed', + true + ); + } + + /** * Returns the semType object that is defined for the course * @@ -621,6 +1789,111 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return trim(vsprintf($template[$format], array_map('trim', $data))); } + /** + * Retrieves all dates (regular and irregular) that take place + * in a specified semester or a semester range. + * + * @param Semester|null $start_semester The semester for which to get all dates + * or the start semester of a semester range. + * @param Semester|null $end_semester The end semester for a semester range. + * This can also be null in case only dates for one semester + * shall be retrieved. + * + * @param bool $with_cancelled_dates Whether to include cancelled dates (true) or not (false). + * Defaults to false. + * + * @return CourseDateList A collection of irregular and regular course dates. + * + * @throws \Studip\Exception In case that the end semester is before the start semester. + */ + public function getAllDatesInSemester( + ?Semester $start_semester = null, + ?Semester $end_semester = null, + bool $with_cancelled_dates = false + ) : CourseDateList { + $all_dates_of_course = !$start_semester && !$end_semester; + + if ($all_dates_of_course) { + $collection = new CourseDateList(); + foreach ($this->cycles as $regular_date) { + $collection->addRegularDate($regular_date); + } + foreach ($this->dates as $date) { + if (!$date->metadate_id) { + $collection->addSingleDate($date); + } + } + if ($with_cancelled_dates) { + foreach ($this->ex_dates as $cancelled_date) { + $collection->addCancelledDate($cancelled_date); + } + } + return $collection; + } else { + if (!$start_semester) { + return new CourseDateList(); + } + $beginning = $start_semester->beginn; + $end = $start_semester->ende; + if ($end_semester) { + if ($end_semester->ende < $start_semester->beginn) { + throw new \Studip\Exception( + _('Das Endsemester darf nicht vor dem Startsemester liegen.'), + \Studip\Exception::END_BEFORE_BEGINNING + ); + } + $end = $end_semester->ende; + } + + $collection = new CourseDateList(); + + SeminarCycleDate::findEachBySQL( + function ($date) use ($collection) { + $collection->addCycleDate($date); + }, + "`start_time` >= :beginning AND `end_time` <= :end + AND `seminar_id` = :course_id", + [ + 'course_id' => $this->id, + 'beginning' => $beginning, + 'end' => $end + ] + ); + + CourseDate::findEachBySQL( + function ($date) use ($collection) { + $collection->addSingleDate($date); + }, + "`date` >= :beginning AND `end_time` <= :end + AND `range_id` = :course_id + AND (`metadate_id` IS NULL OR `metadate_id` = '')", + [ + 'course_id' => $this->id, + 'beginning' => $beginning, + 'end' => $end + ] + ); + + if ($with_cancelled_dates) { + CourseExDate::findEachBySQL( + function ($date) use ($collection) { + $collection->addCancelledDate($date); + }, + "`date` >= :beginning AND `end_time` <= :end + AND `range_id` = :course_id + AND (`metadate_id` IS NULL OR `metadate_id` = '')", + [ + 'course_id' => $this->id, + 'beginning' => $beginning, + 'end' => $end + ] + ); + } + + return $collection; + } + } + /** * Retrieves the course dates including cancelled dates ("ex-dates"). @@ -655,6 +1928,44 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return $dates; } + /** + * Retrieves the first date of the course that takes place. + * + * @return CourseDate|null Either the first date as CourseDate or null in case + * the course has no dates. + */ + public function getFirstDate() : ?CourseDate + { + return $this->dates->first(); + } + + /** + * Retrieves the next date for the course. If requested, the next cancelled + * date is retrieved if no date can be found that takes place. + * + * The date must start in the future or within the past hour to be regarded + * as next date. + * + * @param bool $include_cancelled Include cancelled dates (true) or not. + * Defaults to false. + * + * @return CourseDate|CourseExDate|null A CourseDate or CourseExDate representing + * the next date or null in case there is no next date. CourseExDate instances + * are only returned if $include_cancelled is set to true. + */ + public function getNextDate(bool $include_cancelled = false) + { + $sql = '`range_id` = :course_id AND `date` > UNIX_TIMESTAMP() - 3600 + ORDER BY `date`, `end_time`'; + + $date = CourseDate::findOneBySQL($sql, ['course_id' => $this->id]); + if (!$date && $include_cancelled) { + //Do the same with CourseExDate: + $date = CourseExDate::findOneBySQL($sql, ['course_id' => $this->id]); + } + return $date; + } + /** * Sets this courses study areas to the given values. * @@ -1073,6 +2384,34 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); } + + /** + * Returns the Plugin/Tool specified by its name in case it is + * activated in this course. + * + * @param string $name The name of the tool. + * + * @return StandardPlugin An instance for the tool. + * + * @throws \Studip\ToolException In case the tool is not activated. + */ + public function getTool(string $name) : StandardPlugin + { + if ($this->isToolActive($name)) { + $plugin = PluginEngine::getPlugin($name); + if ($plugin instanceof StandardPlugin) { + return $plugin; + } + } + throw new \Studip\ToolException( + sprintf( + _('Das Werkzeug %s ist nicht aktiviert.'), + $name + ), + \Studip\ToolException::TOOL_NOT_ACTIVATED + ); + } + /** * returns all activated plugins/modules for this course * @return StudipModule[] @@ -1177,5 +2516,4 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe return $result; } - } diff --git a/lib/models/CourseDate.php b/lib/models/CourseDate.php index eadeb0a020e3b0006bc74fc668aefd2236c0198c..3f139045effe3b1708da83a0996eca9260251585 100644 --- a/lib/models/CourseDate.php +++ b/lib/models/CourseDate.php @@ -245,6 +245,45 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event return null; } + /** + * Books a room for the course date. + * + * @param Room $room Room The room to be booked. + * @param int $preparation_time int The preparation time for the booking. + * @return bool True, if the booking succeeded, false otherwise. + */ + public function bookRoom(Room $room, int $preparation_time = 0) : bool + { + //Check the permissions: Is the current user allowed to book the room? + if (!$room->userHasBookingRights(User::findCurrent(), $this->date, $this->end_time)) { + return false; + } + + //Clear the free text room field before booking the room: + $this->raum = ''; + $this->store(); + + //If there is already a room assigned, "change" the booking. + //Otherwise, create a new one. + if ($this->room_booking instanceof ResourceBooking) { + $this->room_booking->resource_id = $room->id; + $this->room_booking->preparation_time = $preparation_time; + $this->room_booking->store(); + } else { + $room->createBooking( + User::findCurrent(), + $this->id, + [['begin' => $this->date, 'end' => $this->end_time]], + null, + 0, + null, + $preparation_time + ); + } + + return true; + } + /** * Returns the name of the type of this date. * @@ -258,26 +297,46 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event /** * Returns the full qualified name of this date. * - * @param String $format Optional format type (only 'default', 'include-room' and - * 'verbose' are supported by now) + * @param String $format Optional format type. Only 'default', 'include-room', 'long', + * 'long-include-room' and 'verbose' are supported by now. * @return String containing the full name of this date. */ public function getFullName($format = 'default') { - if (!$this->date || !in_array($format, ['default', 'verbose', 'include-room'])) { + if (!$this->date || !in_array($format, ['default', 'verbose', 'long', 'long-include-room', 'include-room'])) { return ''; } - $latter_template = $format === 'verbose' ? _('%R Uhr') : '%R'; + require_once('lib/dates.inc.php'); + + $day_of_week = ''; + if (in_array($format, ['long', 'long-include-room'])) { + $day_of_week = getWeekday(date('w', $this->date), false); + } else { + $day_of_week = getWeekday(date('w', $this->date)); + } if (($this->end_time - $this->date) / 60 / 60 > 23) { - $string = strftime('%a., %x (' . _('ganztägig') . ')' , $this->date); + $string = sprintf( + '%1$s, %2$s (%3$s)', + $day_of_week, + date('d.m.Y', $this->date), + _('ganztägig') + ); } else { - $string = strftime('%a., %x, %R', $this->date) . ' - ' - . strftime($latter_template, $this->end_time); + $formatted_end = sprintf( + in_array($format, ['verbose', 'long', 'long-include-room']) ? _('%s Uhr') : '%s', + date('H:i', $this->end_time) + ); + $string = sprintf( + '%1$s, %2$s - %3$s', + $day_of_week, + date('d.m.Y H:i', $this->date), + $formatted_end + ); } - if($format === 'include-room') { + if (in_array($format, ['include-room', 'long-include-room'])) { $room = $this->getRoom(); if($room) { $string = sprintf('%s <a href="%s" target="_blank">%s</a>', diff --git a/lib/models/CourseMember.php b/lib/models/CourseMember.php index ba3a82d54ae86430c8f09ea727799c5d2a721f9b..eb3b6ba2ee04e9119dea8e3b0f56f478a1f918f7 100644 --- a/lib/models/CourseMember.php +++ b/lib/models/CourseMember.php @@ -366,76 +366,30 @@ class CourseMember extends SimpleORMap implements PrivacyObject * @param string $contingent optional studiengang_id, if no id is given, no contingent is considered * @param string $log_message optional log-message. if no log-message is given a default one is used * @return bool + * + * @deprecated Use Course::addMember instead. */ public static function insertCourseMember($seminar_id, $user_id, $status, $copy_studycourse = false, $contingent = false, $log_message = false): bool { if (!$user_id) { return false; } - // get the seminar-object - $sem = Seminar::GetInstance($seminar_id); - - $admission_status = ''; - $admission_comment = ''; - $mkdate = time(); - - $admission_user = AdmissionApplication::find([$user_id, $seminar_id]); - if ($admission_user) { - $admission_status = $admission_user->status; - $admission_comment = $admission_user->comment ?? ''; - $mkdate = $admission_user->mkdate; - } - // check if there are places left in the submitted contingent (if any) - //ignore if preliminary - if ($admission_status !== 'accepted' && $contingent && $sem->isAdmissionEnabled() && !$sem->getFreeAdmissionSeats()) { + $user = User::find($user_id); + if (!$user) { return false; } - // get coloured group as used on meine_seminare - $colour_group = $sem->getDefaultGroup(); - - // LOGGING - // if no log message is submitted use a default one - if (!$log_message) { - $log_message = 'Wurde in die Veranstaltung eingetragen, admission_status: '. $admission_status . ' Kontingent: ' . $contingent; - } - StudipLog::log('SEM_USER_ADD', $seminar_id, $user_id, $status, $log_message); - $membership = new self([$seminar_id, $user_id]); - $membership->setData([ - 'Seminar_id' => $seminar_id, - 'user_id' => $user_id, - 'status' => $status, - 'comment' => $admission_comment, - 'gruppe' => $colour_group, - 'mkdate' => $mkdate, - ]); - $membership->store(); - - NotificationCenter::postNotification('UserDidEnterCourse', $seminar_id, $user_id); - - if ($admission_status) { - $admission_user->delete(); - - //renumber the waiting/accepted/lot list, a user was deleted from it - AdmissionApplication::renumberAdmission($seminar_id); - } - $cs = $sem->getCourseSet(); - if ($cs) { - AdmissionPriority::unsetPriority($cs->getId(), $user_id, $sem->getId()); + $course = Course::find($seminar_id); + if (!$course) { + return false; } - - CalendarScheduleModel::deleteSeminarEntries($user_id, $seminar_id); - - // reload the seminar, the contingents have changed - $sem->restore(); - - // Check if a parent course exists and insert user there. - if ($sem->parent_course) { - self::insertCourseMember($sem->parent_course, $user_id, $status, $copy_studycourse, $contingent, $log_message); + try { + $course->addMember($user, $status, $contingent); + return true; + } catch (\Studip\Exception $e) { + return false; } - - return true; } public function getExportData(): array diff --git a/lib/models/CourseTopic.php b/lib/models/CourseTopic.php index eb26efa5adff41b23a42e40a2a5a7bd81ed96d4c..0a0d75de250e3e7ef7b1e557410a537023187ee4 100644 --- a/lib/models/CourseTopic.php +++ b/lib/models/CourseTopic.php @@ -93,8 +93,8 @@ class CourseTopic extends SimpleORMap public function connectWithDocumentFolder() { if ($this->seminar_id) { - $document_module = Seminar::getInstance($this->seminar_id)->getSlotModule('documents'); - if ($document_module) { + $course = Course::find($this->seminar_id); + if ($course->isToolActive(CoreDocuments::class)) { if (!$this->folders->count()) { $folder = new Folder(); $folder['range_id'] = $this['seminar_id']; @@ -118,10 +118,15 @@ class CourseTopic extends SimpleORMap public function connectWithForumThread() { if ($this->seminar_id) { - $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); - if ($forum_module instanceOf ForumModule) { - $forum_module->setThreadForIssue($this->id, $this->title, $this->description); - return true; + $course = Course::find($this->seminar_id); + try { + $forum_module = $course->getTool(CoreForum::class); + if ($forum_module instanceof ForumModule) { + $forum_module->setThreadForIssue($this->id, $this->title, $this->description); + return true; + } + } catch (\Studip\Exception $e) { + return false; } } return false; @@ -130,9 +135,14 @@ class CourseTopic extends SimpleORMap public function getForumThreadURL() { if ($this->seminar_id) { - $forum_module = Seminar::getInstance($this->seminar_id)->getSlotModule('forum'); - if ($forum_module instanceOf ForumModule) { - return html_entity_decode($forum_module->getLinkToThread($this->id)); + $course = Course::find($this->seminar_id); + try { + $forum_module = $course->getTool(CoreForum::class); + if ($forum_module instanceof ForumModule) { + return html_entity_decode($forum_module->getLinkToThread($this->id)); + } + } catch (\Studip\Exception $e) { + return ''; } } return ''; diff --git a/lib/models/LogEvent.php b/lib/models/LogEvent.php index 782157a5841046033c4e3f23184dfe0e1b93a573..144d1a695cc948ff6279ccbb41cd1bd0290c769c 100644 --- a/lib/models/LogEvent.php +++ b/lib/models/LogEvent.php @@ -252,8 +252,8 @@ class LogEvent extends SimpleORMap implements PrivacyObject * @return string The singledate. */ protected function formatSingledate($field) { - $termin = new SingleDate($this->$field); - return '<em>' . $termin->toString() . '</em>'; + $termin = new CourseDate($this->$field); + return '<em>' . $termin . '</em>'; } /** diff --git a/lib/models/SeminarCycleDate.php b/lib/models/SeminarCycleDate.php index a63716e6d75fc5f1d80c16d0a41b97745956b648..ae851217178890fae9e976c1e858f39422dbbe32 100644 --- a/lib/models/SeminarCycleDate.php +++ b/lib/models/SeminarCycleDate.php @@ -45,6 +45,27 @@ require_once 'lib/dates.inc.php'; */ class SeminarCycleDate extends SimpleORMap { + /** + * The booking status of the regular date cannot be determined + */ + const BOOKING_STATUS_UNDEFINED = 0; + + /** + * None of the single dates of the regular date is booked. + */ + const BOOKING_STATUS_NOT_BOOKED = 1; + + /** + * Only a part (at least one) of the single dates of the regular dates is booked. + */ + const BOOKING_STATUS_PARTIALLY_BOOKED = 2; + + /** + * All single dates of the regular date are booked. + */ + const BOOKING_STATUS_ALL_BOOKED = 3; + + /** * Configures this model. * @@ -181,35 +202,113 @@ class SeminarCycleDate extends SimpleORMap } /** - * returns a string for a date like '3. 9:00s - 10:45' (short and long) - * or '3. 9:00s - 10:45, , ab der 7. Semesterwoche, (Vorlesung)' with the week of the semester - * @param format string: "short"|"long"|"full" - * @return formatted string + * Generates a string for a regular date. Depending on the selected format, more or less information + * are present in the generated string: + * - short: Only the weekday, beginning and end + * - long: Weekday, beginning, end and the repetition interval + * - long-start: Same as long but with the start date. + * - full: Same as long, but also with the start and end week of the regular date in the semester + * and the description of the regular date, if provided. + * + * @param string $format The format string: "short", "long" or "full". Defaults to "short". + * + * @returns string The formatted string. */ - public function toString($format = 'short') + public function toString(string $format = 'short') : string { - $template['short'] = '%s. %02s:%02s - %02s:%02s'; - $template['long'] = '%s: %02s:%02s - %02s:%02s, %s'; - $template['full'] = '%s: %02s:%02s - %02s:%02s, ' . _('%s, ab der %s. Semesterwoche'); - if ($this->end_offset) { - $template['full'] .= ' bis zur %s. Semesterwoche'; + if (!in_array($format, ['short', 'long', 'long-start', 'full'])) { + //Invalid format: + return ''; + } + + $parameters = [ + 'beginning' => sprintf('%02d:%02d', $this->start_hour, $this->start_minute), + 'end' => sprintf('%02d:%02d', $this->end_hour, $this->end_minute), + ]; + + if ($format === 'short') { + $parameters['weekday_short'] = getWeekday($this->weekday); + return studip_interpolate( + _('%{weekday_short}. %{beginning} - %{end}'), + $parameters + ); } else { - $template['full'] .= '%s'; - } - $template['full'] .= '%s'; - $cycles = [_('wöchentlich'), _('zweiwöchentlich'), _('dreiwöchentlich')]; - $day = getWeekDay($this->weekday, $format == 'short'); - $result = sprintf($template[$format], - $day, - $this->start_hour, - $this->start_minute, - $this->end_hour, - $this->end_minute, - $cycles[(int)$this->cycle], - $this->week_offset + 1, - $this->end_offset ? $this->end_offset: '', - $this->description ? ' (' . $this->description . ')' : ''); - return $result; + $parameters['weekday'] = getWeekday($this->weekday, false); + $cycles = [_('wöchentlich'), _('zweiwöchentlich'), _('dreiwöchentlich')]; + $parameters['interval'] = $cycles[(int)$this->cycle]; + if ($format === 'long') { + return studip_interpolate( + _('%{weekday}, %{beginning} - %{end}, %{interval}'), + $parameters + ); + } elseif ($format === 'long-start') { + $text = _('%{weekday}, %{beginning} - %{end}, %{interval}'); + $room = $this->getMostBookedRoom(); + if ($room) { + $parameters['room_name'] = sprintf( + '<a href="%1$s" data-dialog="size=auto">%2$s</a>', + $room->getActionLink(), + htmlReady($room->name) + ); + } + $first_date = $this->getFirstDate(); + if ($first_date) { + $parameters['start_date'] = date('d.m.Y', $first_date->date); + } + if ($room && $first_date) { + $text = _('%{weekday}, %{beginning} - %{end}, %{interval} (ab dem %{start_date} im Raum %{room_name})'); + } elseif ($room) { + $text = _('%{weekday}, %{beginning} - %{end}, %{interval} (im Raum %{room_name})'); + } elseif ($first_date) { + $text = _('%{weekday}, %{beginning} - %{end}, %{interval} (ab dem %{start_date})'); + } + return studip_interpolate($text, $parameters); + } elseif ($format === 'full') { + $parameters['start_week'] = $this->week_offset + 1; + if ($this->description) { + $parameters['description'] = $this->description; + } + if ($this->end_offset) { + $parameters['end_week'] = $this->end_offset; + } + if ($this->description) { + if ($this->end_offset) { + return studip_interpolate( + _('%{weekday}, %{beginning} - %{end}, %{interval}, von der %{start_week}. bis zur %{end_week}. Semesterwoche (%{description})'), + $parameters + ); + } else { + return studip_interpolate( + _('%{weekday}, %{beginning} - %{end}, %{interval}, ab der %{start_week}. Semesterwoche (%{description})'), + $parameters + ); + } + } else { + if ($this->end_offset) { + return studip_interpolate( + _('%{weekday}, %{beginning} - %{end}, %{interval}, von der %{start_week}. bis zur %{end_week}. Semesterwoche'), + $parameters + ); + } else { + return studip_interpolate( + _('%{weekday}, %{beginning} - %{end}, %{interval}, ab der %{start_week}. Semesterwoche'), + $parameters + ); + } + } + } + } + return ''; + } + + /** + * Retrieves the first date of the regular date. + * + * @return CourseDate|null The first date or null if no such date exists. + */ + public function getFirstDate() : ?CourseDate + { + return $this->dates->first(); } /** @@ -233,6 +332,103 @@ class SeminarCycleDate extends SimpleORMap return $dates; } + /** + * Retrieves the most booked room that is booked for this regular date. + * + * @return Room[] Either the most booked rooms for this regular date or an empty array + * in case no such room exists. + */ + public function getMostBookedRooms(int $start_time = 0, int $end_time = 0) : array + { + $sql = "SELECT `resource_id`, COUNT(`resource_id`) AS resource_c + FROM `termine` + JOIN `resource_bookings` + ON (`termin_id` = `resource_bookings`.`range_id`) "; + if ($start_time && $end_time && $start_time < $end_time) { + $sql .= "JOIN `resource_booking_intervals` + ON `resource_booking_intervals`.`booking_id` = `resource_bookings`.`id` "; + } + $sql .= "WHERE "; + $sql_params = ['regular_date_id' => $this->id]; + if ($start_time && $end_time && $start_time < $end_time) { + $sql .= "`resource_booking_intervals`.`end` > :start AND `resource_booking_intervals`.`start` < :end AND "; + $sql_params['start'] = $start_time; + $sql_params['end'] = $end_time; + } + $sql .= "`termine`.`metadate_id` = :regular_date_id + AND `resource_id` <> '' + GROUP BY `resource_id` + ORDER BY resource_c DESC"; + $db = DBManager::get(); + $stmt = $db->prepare($sql); + $stmt->execute($sql_params); + $rooms = []; + while ($room_id = $stmt->fetchColumn() !== false) { + $room = Resource::find($room_id)?->getDerivedClassInstance(); + if ($room instanceof Room) { + $rooms[] = $room; + } + } + return $rooms; + } + + /** + * Retrieves the most booked room that is booked for this regular date. + * + * @return Room|null Either the most booked room for this regular date or null + * in case no such room exists. + */ + public function getMostBookedRoom() : ?Room + { + $rooms = $this->getMostBookedRooms(); + return array_shift($rooms); + } + + /** + * @param int $start_time + * + * @param int $end_time + * + * @return string[] A list of free text rooms ordered by the most used one. + * In case no such rooms exist, an empty array is returned. + */ + public function getMostUsedFreetextRoomNames(int $start_time = 0, int $end_time = 0) : array + { + $sql = "SELECT `raum`, COUNT(`raum`) AS room_name_c + FROM `termine` + WHERE "; + $sql_params = ['regular_date_id' => $this->id]; + if ($start_time && $end_time && $start_time < $end_time) { + $sql .= "`termine`.`date` BETWEEN :start AND :end AND "; + $sql_params['start'] = $start_time; + $sql_params['end'] = $end_time; + } + $sql .= "`termine`.`metadate_id` = :regular_date_id + AND `termine`.`termin_id` NOT IN (SELECT `range_id` FROM `resource_bookings`) + GROUP BY `raum` + ORDER BY room_name_c DESC"; + $db = DBManager::get(); + $stmt = $db->prepare($sql); + $stmt->execute($sql_params); + $rooms = []; + while ($room_name = $stmt->fetchColumn() !== false) { + $rooms[] = $room_name; + } + return $rooms; + } + + /** + * Retrieves the most booked free text room name that is used for this regular date. + * + * @return string|null Either the most used room name for this regular date or null + * in case no such room exists. + */ + public function getMostUsedFreetextRoomName() : ?string + { + $rooms = $this->getMostUsedFreetextRoomNames(); + return array_shift($rooms); + } + /** * Deletes the cycle. * @@ -690,7 +886,7 @@ class SeminarCycleDate extends SimpleORMap $ids = $statement->fetchAll(PDO::FETCH_COLUMN); foreach ($ids as $id) { - $termin = new SingleDate($id); + $termin = new CourseDate($id); $termin->delete(); unset($termin); } @@ -819,4 +1015,101 @@ class SeminarCycleDate extends SimpleORMap $data = $this->buildOpenRequestsForDatesQuery($include_metadate); return ResourceRequest::countBySql($data['sql'], $data['sql_params']); } + + /** + * Determines the booking status for the regular date and returns it as an integer that + * corresponds to defined class constants: + * + * - If the booking status cannot be determined, BOOKING_STATUS_UNDEFINED is returned. + * - If none of the single dates is booked, BOOKING_STATUS_NOT_BOOKED is returned. + * - If only a part of the single dates is booked, BOOKING_STATUS_PARTIALLY_BOOKED is returned. + * - If all single dates are booked, BOOKING_STATUS_ALL_BOOKED is returned. + * + * @returns int The booking status as integer. + */ + public function getBookingStatus() : int + { + if (!Config::get()->RESOURCES_ENABLE || !Config::get()->RESOURCES_ENABLE_BOOKINGSTATUS_COLORING) { + return self::BOOKING_STATUS_UNDEFINED; + } + + if (count($this->dates) === 0) { + //If there are no dates, the booking status cannot be determined. + return self::BOOKING_STATUS_UNDEFINED; + } + + //Count the course dates by their booking status: + $booked_c = 0; + + foreach ($this->dates as $course_date) { + if ($course_date->room_booking) { + $booked_c++; + } + } + + //Check which status is the dominant one (highest ratio): + if ($booked_c === 0) { + return self::BOOKING_STATUS_NOT_BOOKED; + } elseif (count($this->dates) === $booked_c) { + return self::BOOKING_STATUS_ALL_BOOKED; + } elseif ($booked_c > 0) { + return self::BOOKING_STATUS_PARTIALLY_BOOKED; + } + return self::BOOKING_STATUS_UNDEFINED; + } + + /** + * Generates the correct icon for the booking status of the regular date. + * + * @return Icon An icon representing the booking status of the regular date. + */ + public function getIconForBookingStatus() : Icon + { + return match ($this->getBookingStatus()) { + self::BOOKING_STATUS_ALL_BOOKED => + Icon::create('span-full', Icon::ROLE_STATUS_GREEN), + self::BOOKING_STATUS_PARTIALLY_BOOKED => + Icon::create('span-2quarter', Icon::ROLE_STATUS_YELLOW), + self::BOOKING_STATUS_NOT_BOOKED => + Icon::create('span-empty', Icon::ROLE_STATUS_RED), + default => + Icon::create('exclaim-circle', Icon::ROLE_INACTIVE), + }; + } + + /** + * Generates a human-readable HTML text for the booking status of the regular date. + * + * @return string A HTML text for the booking status of the regular date. + */ + public function getMessageForBookingStatus() : string + { + $booking_status = $this->getBookingStatus(); + if ($booking_status === self::BOOKING_STATUS_ALL_BOOKED) { + return _('Alle Termine haben Raumbuchungen.'); + } elseif ($booking_status === self::BOOKING_STATUS_NOT_BOOKED) { + return _('Alle Termine haben keine Raumbuchungen.'); + } elseif ($booking_status === self::BOOKING_STATUS_PARTIALLY_BOOKED) { + //List the dates that have no room booking: + $unbooked_dates = []; + foreach ($this->dates as $course_date) { + if (!$course_date->room_booking) { + $unbooked_dates[] = $course_date; + } + } + uasort($unbooked_dates, function(CourseDate $a, CourseDate $b) { + return $a->date - $b->date + ?: $a->end_time - $b->end_time; + }); + + $unbooked_dates_text = [ + _('Die folgenden Termine haben keine Raumbuchungen:') + ]; + foreach ($unbooked_dates as $date) { + $unbooked_dates_text[] = htmlReady($date->getFullName()); + } + return implode('<br>', $unbooked_dates_text); + } + return _('Es sind keine Informationen zu Buchungen verfügbar.'); + } } diff --git a/lib/models/StudipNews.php b/lib/models/StudipNews.php index ccb1828cdda146198717698fee0dc06f8fff1c9c..4166d8151c09bfad1fe29e4993d4721ce289d03d 100644 --- a/lib/models/StudipNews.php +++ b/lib/models/StudipNews.php @@ -500,7 +500,7 @@ class StudipNews extends SimpleORMap implements PrivacyObject if ($operation === 'view' && ($type !== 'sem' || $GLOBALS['perm']->have_studip_perm('user', $range_id) - || (Config::get()->ENABLE_FREE_ACCESS && Seminar::getInstance($range_id)->read_level == 0) + || (Config::get()->ENABLE_FREE_ACCESS && Course::find($range_id)->lesezugriff == 0) )) { return $news_range_perm_cache[$user_id.$range_id.$operation] = true; } diff --git a/lib/models/resources/ResourceRequest.php b/lib/models/resources/ResourceRequest.php index df77b19ff0d9514d5bff980e49ed8dd753679b34..fff7132adca4917d8bb27eb46206c3c95feb81b0 100644 --- a/lib/models/resources/ResourceRequest.php +++ b/lib/models/resources/ResourceRequest.php @@ -1465,14 +1465,7 @@ class ResourceRequest extends SimpleORMap implements PrivacyObject, Studip\Calen }); } } elseif ($this->course_id) { - $course = new Seminar($this->course_id); - $strings[] = $course->getDatesTemplate('dates/seminar_html_roomplanning', - [ - 'shrink' => false, - 'show_room' => true, - 'with_past_intervals' => $with_past_intervals - ] - ); + $strings = $this->course->getAllDatesInSemester()->toStringArray(); } elseif ($this->begin && $this->end) { $begin_date = date('Ymd', $this->begin); $end_date = date('Ymd', $this->end); diff --git a/lib/raumzeit/CycleData.php b/lib/raumzeit/CycleData.php deleted file mode 100644 index 8ba61bf2afb59594a59c8918d72d526bb72f5104..0000000000000000000000000000000000000000 --- a/lib/raumzeit/CycleData.php +++ /dev/null @@ -1,434 +0,0 @@ -<? -# Lifter002: TODO -# Lifter007: TODO -# Lifter003: TODO -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// CycleData.php -// -// Repräsentiert ein Turnusdatum eines MetaDates -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - -/** - * This class is subject to change, for now it wraps getter - * and setter to SeminarCycleDate. For compatibility reasons it has - * magic __get() __set() __isset, and it combines the old metadata_dates - * keys and the new fields from SeminarCycleDate (see CycleData::$alias) - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - */ -class CycleData -{ - /** - * list of aliases to translate old style metadata_dates keys to - * new fields of SeminarCycleDate - * - * @var array - */ - private $alias = [ - 'start_stunde' => 'start_hour', - 'end_stunde' => 'end_hour', - 'day' => 'weekday', - 'desc' => 'description', - 'is_visible' => 'is_visible' - ]; - - /** - * this is mostly filtered, see readSingleDates() - * should not be public - * - * @var array of SingleDate - */ - public $termine = NULL; // Array - - /** - * Enter description here ... - * @var SeminarCycleDate - */ - private $cycle_date = null; - - /** - * Constructor - * @param SeminarCycleDate|array - */ - function __construct($cycle_data = FALSE) - { - if ($cycle_data instanceof SeminarCycleDate) { - $this->cycle_date = $cycle_data; - } else { - if ($cycle_data['metadate_id']) { - $metadate_id = $cycle_data['metadate_id']; - } else { - $metadate_id = md5(uniqid('metadate_id')); - } - $this->cycle_date = new SeminarCycleDate($metadate_id); - $this->setStart($cycle_data['start_stunde'], $cycle_data['start_minute']); - $this->setEnd($cycle_data['end_stunde'], $cycle_data['end_minute']); - $this->setDay($cycle_data['day']); - $this->setDescription($cycle_data['desc']); - } - } - - function getDescription() - { - return $this->cycle_date->description; - } - - function getCycleDate() { - return $this->cycle_date; - } - function setDescription($description) - { - $this->cycle_date->description = $description; - } - - function setStart($start_stunde, $start_minute) - { - $this->cycle_date->start_hour = (int)$start_stunde; - $this->cycle_date->start_minute = (int)$start_minute; - } - - function setEnd($end_stunde, $end_minute) - { - $this->cycle_date->end_hour = (int)$end_stunde; - $this->cycle_date->end_minute = (int)$end_minute; - } - - function getStartStunde () - { - return $this->cycle_date->start_hour; - } - - function getStartMinute () - { - return $this->cycle_date->start_minute; - } - - function getEndStunde () - { - return $this->cycle_date->end_hour; - } - - function getEndMinute () - { - return $this->cycle_date->end_minute; - } - - function getMetaDateID() - { - return $this->cycle_date->getId(); - } - - function getDay() - { - return $this->cycle_date->weekday; - } - - function getStartTime() - { - return sprintf('%02d:%02d',$this->getStartStunde(), $this->getStartMinute()); - } - - function getEndTime() - { - return sprintf('%02d:%02d',$this->getEndStunde(), $this->getEndMinute()); - } - - function setDay($day) - { - $this->cycle_date->weekday = $day; - } - - /** - * Check if there is a least one not cancelled date for this cycle data - * - * @return bool true, if there is at least one not cancelled date - */ - function getIsVisible() - { - return $this->cycle_date->is_visible; - } - - function __get($field) - { - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return $this->cycle_date->$field; - } - - function __set($field, $value) - { - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return $this->cycle_date->$field = $value; - } - - function __isset($field) - { - if(isset($this->alias[$field])) { - $field = $this->alias[$field]; - } - return isset($this->cycle_date->$field); - } - - /** - * stores only the cycledate data - * - * @return boolean - */ - function storeCycleDate() - { - if (!$this->description) $this->description = ''; - return $this->cycle_date->store(); - } - - /** - * stores the single dates belonging to this cycledate, - * but only the ones which are currently loaded! - * (see readSingleDates()) - * should be private - * - * @return boolean - */ - function store() - { - foreach ($this->termine as $val) { - $val->store(); - } - - return TRUE; - } - - /** - * refreshes the currently loaded single dates from database, - * does not reload cycledate data! - * should be private - * - * @return boolean - */ - function restore() - { - foreach ($this->termine as $key => $val) { - $new_termine[$key] = $val->restore(); - } - $this->termine =& $new_termine; - return TRUE; - } - - /** - * deletes cycledate and corresponding single dates - * - * @param boolean $removeSingles - * @return boolean - */ - function delete($removeSingles = TRUE) - { - if ($removeSingles) { - if (!$this->termine) { - $this->readSingleDates(); - } - foreach ($this->termine as $termin) { - $termin->delete(); - } - } - return $this->cycle_date->delete(); - } - - /** - * this does not delete a single date, but set it to be marked - * as to not take place. do not use! - * - * @deprecated - * @param sting $date_id - * @param int $filterStart - * @param int $filterEnd - */ - function deleteSingleDate($date_id, $filterStart, $filterEnd) - { - if (!$this->termine) { - $this->readSingleDates($filterStart, $filterEnd); - } - - $this->termine[$date_id]->setExTermin(true); - $this->termine[$date_id]->store(); - } - - /** - * this should ressurect a single date whis is marked - * as to not take place. do not use! - * - * @deprecated - * @param sting $date_id - * @param int $filterStart - * @param int $filterEnd - */ - function unDeleteSingleDate($date_id, $filterStart, $filterEnd) - { - if (!$this->termine) { - $this->readSingleDates($filterStart, $filterEnd); - } - - if (!$this->termine[$date_id]->isExTermin()) { - return false; - } - - $this->termine[$date_id]->setExTermin(false); - $this->termine[$date_id]->store(); - return true; - } - - /** - * load corresponding single dates from database - * give timestamps as params to filter by time range - * - * @param int $start - * @param int $end - * @return boolean - */ - function readSingleDates($start = 0, $end = 0) - { - $this->termine = []; - $termin_data = CycleDataDB::getTermine($this->metadate_id, $start, $end); - if ($termin_data) { - foreach ($termin_data as $val) { - unset($termin); - $termin = new SingleDate(); - $termin->fillValuesFromArray($val); - $termin->setExTermin(!empty($val['ex_termin'])); - $this->termine[$val['termin_id']] = $termin; - } - return TRUE; - } - - return FALSE; - } - - /** - * get the currently loaded single dates, or all when no dates - * are loaded. you must use readSingleDates() before to be shure what to get! - * - * @return array of SingleDate - */ - function getSingleDates() - { - if (!$this->termine) { - $this->readSingleDates(); - } - return $this->termine; - } - - /** - * returns an assoc array, keys are room names values are number of dates for this room - * give timestamps as params to filter by time range - * - * @param int $filterStart - * @param int $filterEnd - * @return array|false - */ - function getFreeTextPredominantRoom($filterStart = 0, $filterEnd = 0) - { - return CycleDataDB::getFreeTextPredominantRoomDB($this->metadate_id, $filterStart, $filterEnd); - } - - /** - * returns an assoc array, keys are resource_id of rooms values are number of dates for this room - * give timestamps as params to filter by time range - * - * @param int $filterStart - * @param int $filterEnd - * @return array - */ - function getPredominantRoom($filterStart = 0, $filterEnd = 0) - { - if ($rooms = CycleDataDB::getPredominantRoomDB($this->metadate_id, $filterStart, $filterEnd)) { - return $rooms; - } - - return false; - } - - /** - * returns a formatted string for cycledate - * - * @see SeminarCycleDate::toString() - * @param boolean $short - * @return string - */ - function toString($short = false) - { - if($short === false) { - return $this->cycle_date->toString('long'); - } else if ($short === true) { - return $this->cycle_date->toString('short'); - } else { - return $this->cycle_date->toString($short); - } - } - - /** - * return all fields from SeminarCycleDate and old style - * metadata_dates, combined with info about rooms - * - * @return array - */ - function toArray() - { - $ret = $this->cycle_date->toArray(); - foreach($this->alias as $a => $o) { - $ret[$a] = $this->cycle_date->$o; - } - $ret['assigned_rooms'] = $this->getPredominantRoom(); - $ret['freetext_rooms'] = $this->getFreetextPredominantRoom(); - $ret['tostring'] = $this->toString(); - $ret['tostring_short'] = $this->toString(true); - - $ret['start_minute'] = leadingZero($ret['start_minute']); - $ret['end_minute'] = leadingZero($ret['end_minute']); - return $ret; - } - - /** - * assign single dates one by one to a list of issues - * seems not to be the right place for this method - * - * @deprecated - * @param array $themen - * @param int $filterStart - * @param int $filterEnd - */ - function autoAssignIssues($themen, $filterStart, $filterEnd) - { - $this->readSingleDates($filterStart, $filterEnd); - $z = 0; - foreach ($this->termine as $key => $val) { - if (sizeof($val->getIssueIDs()) == 0) { - if (!$themen[$z]) break; - if (!$val->isExTermin()) { - $this->termine[$key]->addIssueID($themen[$z++]); - } - } - } - $this->store(); - } -} diff --git a/lib/raumzeit/CycleDataDB.php b/lib/raumzeit/CycleDataDB.php deleted file mode 100644 index 715df0b3f08c3f6454bf860731323bdf090b2d05..0000000000000000000000000000000000000000 --- a/lib/raumzeit/CycleDataDB.php +++ /dev/null @@ -1,271 +0,0 @@ -<? -# Lifter002: TODO -# Lifter003: TEST -# Lifter007: TODO -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// CycleDataDB.php -// -// Datenbank-Abfragen für CycleData.php -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * CycleDataDB.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - */ -class CycleDataDB -{ - /** - * Returns sorted array of all dates belonging to the passed metadate, - * optionally filtered by start- and end-date - * - * @param string $metadate_id - * @param integer $start - * @param integer $end - * - * @return array - */ - public static function getTermine($metadate_id, $start = 0, $end = 0) - { - if (($start != 0) || ($end != 0)) { - $query = "SELECT termine.*, r.resource_id, GROUP_CONCAT(DISTINCT trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM termine - LEFT JOIN termin_related_persons AS trp ON (termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (termine.termin_id = trg.termin_id) - LEFT JOIN resource_bookings AS r ON (termine.termin_id = r.range_id) - WHERE metadate_id = ? AND termine.date BETWEEN ? AND ? - GROUP BY termine.termin_id - ORDER BY NULL"; - $parameters = [$metadate_id, $start, $end]; - } else { - $query = "SELECT termine.*, r.resource_id, GROUP_CONCAT(DISTINCT trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM termine - LEFT JOIN termin_related_persons AS trp ON (termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (termine.termin_id = trg.termin_id) - LEFT JOIN resource_bookings AS r ON (termine.termin_id = r.range_id) - WHERE metadate_id = ? - GROUP BY termine.termin_id - ORDER BY NULL"; - $parameters = [$metadate_id]; - } - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - $ret = []; - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $data = $row; - $data['related_persons'] = array_filter(explode(',', $data['related_persons'])); - $data['related_groups'] = array_filter(explode(',', $data['related_groups'])); - $ret[] = $data; - } - - if (($start != 0) || ($end != 0)) { - $query = "SELECT ex_termine.*, GROUP_CONCAT(DISTINCT trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM ex_termine - LEFT JOIN termin_related_persons AS trp ON (ex_termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (ex_termine.termin_id = trg.termin_id) - WHERE metadate_id = ? AND `date` BETWEEN ? AND ? - GROUP BY ex_termine.termin_id - ORDER BY NULL"; - $parameters = [$metadate_id, $start, $end]; - } else { - $query = "SELECT ex_termine.*, GROUP_CONCAT(DISTINCT trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM ex_termine - LEFT JOIN termin_related_persons AS trp ON (ex_termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (ex_termine.termin_id = trg.termin_id) - WHERE metadate_id = ? - GROUP BY ex_termine.termin_id - ORDER BY NULL"; - $parameters = [$metadate_id]; - } - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $zw = $row; - $zw['ex_termin'] = TRUE; - $zw['related_persons'] = array_filter(explode(',', $zw['related_persons'])); - $zw['related_groups'] = array_filter(explode(',', $zw['related_groups'])); - $ret[] = $zw; - } - - if ($ret) { - usort($ret, 'CycleDataDB::sort_dates'); - return $ret; - } - - return FALSE; - } - - public static function sort_dates($a, $b) - { - if ($a['date'] == $b['date']) return 0; - return ($a['date'] < $b['date']) ? -1 : 1; - } - - /** - * Deletes all dates that are newer then the passed date for metadate - * with the passed id - * - * @param string $metadate_id - * @param int $timestamp - * - * @return int number of deleted singledates - */ - public static function deleteNewerSingleDates($metadate_id, $timestamp) - { - $count = 0; - - $query = "SELECT termin_id - FROM termine - WHERE metadate_id = ? AND `date` > ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$metadate_id, $timestamp]); - while ($termin_id = $statement->fetchColumn()) { - $termin = new SingleDate($termin_id); - $termin->delete(); - unset($termin); - - $count += 1; - } - - $query = "DELETE FROM termine WHERE metadate_id = ? AND `date` > ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$metadate_id, $timestamp]); - - $query = "DELETE FROM ex_termine WHERE metadate_id = ? AND `date` > ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$metadate_id, $timestamp]); - - return $count; - } - - /** - * Returns the list of booked rooms ordered by number of appearance - * in the metadate with the passed id - * - * @param string $metadate_id - * @param integer $filterStart - * @param integer $filterEnd - * - * @return array [resource_id, number_of_appearances] - */ - public static function getPredominantRoomDB($metadate_id, $filterStart = 0, $filterEnd = 0) - { - if (($filterStart == 0) && ($filterEnd == 0)) { - $query = "SELECT resource_id, COUNT(resource_id) AS c - FROM termine - INNER JOIN resource_bookings ON (termin_id = resource_bookings.range_id) - WHERE termine.metadate_id = ? AND resource_id != '' - GROUP BY resource_id - ORDER BY c DESC"; - $parameters = [$metadate_id]; - } else { - $query = "SELECT resource_id, COUNT(resource_id) AS c - FROM termine - INNER JOIN resource_bookings ON (termin_id = resource_bookings.range_id) - WHERE termine.metadate_id = ? AND termine.date BETWEEN ? AND ? - GROUP BY resource_id - ORDER BY c DESC"; - $parameters = [$metadate_id, $filterStart, $filterEnd]; - } - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - return $statement->fetchGrouped(PDO::FETCH_COLUMN) ?: false; - } - - /** - * Returns the list of freetext rooms ordered by number of appearance - * in the metadate with the passed id - * - * @param [type] $metadate_id - * @param integer $filterStart - * @param integer $filterEnd - * - * @return array [freetex, number_of_appearances] - */ - public static function getFreeTextPredominantRoomDB($metadate_id, $filterStart = 0, $filterEnd = 0) - { - if (($filterStart == 0) && ($filterEnd == 0)) { - $query = "SELECT raum, COUNT(raum) AS c - FROM termine - LEFT JOIN resource_bookings ON (termin_id = resource_bookings.range_id) - WHERE termine.metadate_id = ? AND resource_bookings.range_id IS NULL - GROUP BY raum - ORDER BY c DESC"; - $parameters = [$metadate_id]; - } else { - $query = "SELECT raum, COUNT(raum) AS c - FROM termine - LEFT JOIN resource_bookings ON (termin_id = resource_bookings.range_id) - WHERE termine.metadate_id = ? AND resource_bookings.range_id IS NULL - AND termine.date BETWEEN ? AND ? - GROUP BY raum - ORDER BY c DESC"; - $parameters = [$metadate_id, $filterStart, $filterEnd]; - } - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - return $statement->fetchGrouped(PDO::FETCH_COLUMN) ?: false; - } - - /** - * returns the first date for a given metadate_id as array - * - * @param string $metadate_id - * - * @return array - */ - public static function getFirstDate($metadate_id) - { - $query = "SELECT * - FROM termine - WHERE metadate_id = ? - ORDER BY `date` ASC - LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$metadate_id]); - return $statement->fetch(PDO::FETCH_ASSOC); - } - - - /** - * returns the last date for a given metadate_id as array - * - * @param string $metadate_id - * - * @return array - */ - public static function getLastDate($metadate_id) - { - $query = "SELECT * - FROM termine - WHERE metadate_id = ? - ORDER BY `date` DESC - LIMIT 1"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$metadate_id]); - return $statement->fetch(PDO::FETCH_ASSOC); - } -} diff --git a/lib/raumzeit/Issue.php b/lib/raumzeit/Issue.php deleted file mode 100644 index 96222a56cbdc18cd7a6b8537d1b4b787ec36f2cc..0000000000000000000000000000000000000000 --- a/lib/raumzeit/Issue.php +++ /dev/null @@ -1,268 +0,0 @@ -<?php -# Lifter002: TODO -# Lifter007: TODO -# Lifter003: TODO -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// Issue.php -// -// Repräsentiert ein einzelnes Thema einer Veranstaltung -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * Issue.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - */ - -class Issue { - var $issue_id = ''; - var $seminar_id = ''; - var $author_id = ''; - var $title = ''; - var $description = ''; - var $mkdate = 0; - var $chdate = 0; - var $priority = 0; - var $file = FALSE; - var $folder_id = ''; - var $messages = []; - var $new = false; - var $hasForum = false; - - /** - * Constructor for class Issue - * - * $data is an Array with the following possible fields: - * issue_id: when set, the Issue with this id is restored - * seminar_id: when set and issue_id is not set, a new issue for this seminar is created - * - * returns NULL if both are unset - */ - function __construct($data = []) { - global $user; - - if (!empty($data['issue_id'])) { - $this->issue_id = $data['issue_id']; - $this->restore(); - } else if (!empty($data['seminar_id'])) { - $this->issue_id = md5(uniqid('Issue')); - $this->seminar_id = $data['seminar_id']; - $this->mkdate = time(); - $this->chdate = time(); - $this->author_id = $user->id; - $this->new = true; - } else { - return NULL; - } - } - - function getPriority() { - return $this->priority; - } - - function setPriority($priority) { - $this->priority = $priority; - } - - function getMkDate() { - return $this->mkdate; - } - - function getChDate() { - return $this->chdate; - } - - function getTitle() { - return $this->title; - } - - function setTitle($title) { - $this->title = $title; - } - - function getDescription() { - return $this->description; - } - - function setDescription($description) { - $this->description = $description; - } - - function getAuthorID() { - return $this->author_id; - } - - function getIssueID() { - return $this->issue_id; - } - - function setSeminarID($seminar_id) { - $this->seminar_id = $seminar_id; - } - - function getSeminarID() { - return $this->seminar_id; - } - - function readSingleDates() { - /*if ($termin_data = IssueDB::getTermine($this->issue_id)) { - foreach ($termin_data as $val) { - $this->singleDates[] = $val['termin_id']; - } - return TRUE; - }*/ - - return FALSE; - - } - - function store() { - $this->chdate = time(); - - if ($this->hasForum) { - $sem = Seminar::getInstance($this->seminar_id); - $forum_module = $sem->getSlotModule('forum'); - - if ($forum_module instanceof ForumModule) { - $forum_module->setThreadForIssue($this->issue_id, $this->title, $this->description); - } - } - - IssueDB::storeIssue($this); - $this->new = false; - } - - function restore() { - /* - * To avoid inconsistency, the restore function has been removed. - * The only way to load an Issue is via the Seminar.php, with the function fillValuesFromArray - */ - $this->fillValuesFromArray(IssueDB::restoreIssue($this->issue_id)); - } - - function delete() { - $dates = IssueDB::getDatesforIssue($this->issue_id); - - $titles = []; - $title = ''; - - foreach ($dates as $termin_id => $termin_data) { - $titles[] = date('d.m.y, H:i', $termin_data['date']).' - '.date('H:i', $termin_data['end_time']); - } - - if (sizeof($titles) > 0) { - $title = implode(', ', $titles).', '.$this->getTitle() . ' ' ._("(Thema gelöscht)"); - } else { - $title = $this->getTitle() . ' ' ._("(Thema gelöscht)"); - } - $description = _("Dateiordner bezieht sich auf ein nicht mehr vorhandenes Thema."); - - IssueDB::deleteIssue($this->issue_id, $this->seminar_id, $title, $description); - } - - function fillValuesFromArray($data) { - $this->issue_id = $data['issue_id']; - $this->seminar_id = $data['seminar_id']; - $this->author_id = $data['author_id']; - $this->title = $data['title']; - $this->description = $data['description']; - $this->mkdate = $data['mkdate']; - $this->chdate = $data['chdate']; - $this->priority = $data['priority']; - $this->file = !empty($data['range_id']); - if ($this->file) { - $this->folder_id = $data['folder_id']; - } - $this->new = false; - - // check, if there is a forums-connection - $sem = Seminar::getInstance($this->seminar_id); - $forum_module = $sem->getSlotModule('forum'); - - if ($forum_module instanceof ForumModule) { - $this->hasForum = $forum_module->getLinkToThread($this->issue_id) ? true : false; - } - - $this->readSingleDates(); - } - - function toString() { - return $this->title; - } - - function getFolderID() { - if ($this->file) { - return $this->folder_id; - } else { - return FALSE; - } - } - - function hasFile() { - return $this->file; - } - - function setFile($file) { - if ($file != $this->file) { - if ($file) { - $this->messages[] = sprintf(_("Dateiordner für das Thema \"%s\" angelegt."),$this->toString()); - } else { - //$this->messages[] = sprintf(_("Dateiordner für das Thema \"%s\" gelöscht!"),$this->toString()); - } - } - $this->file = $file; - } - - function setForum($newForumValue) { - // only do something, if we enable the link to a thread in a forum - if ($newForumValue) { - - // find the ForumModule which takes the role of the CoreForum in the current Seminar - $sem = Seminar::getInstance($this->seminar_id); - $forum_module = $sem->getSlotModule('forum'); - - if ($forum_module instanceof ForumModule) { - // only link if there is none yet - if (!$forum_module->getLinkToThread($this->issue_id)) { - $forum_module->setThreadForIssue($this->issue_id, $this->title, $this->description); - $this->messages[] = sprintf(_("Ordner im Forum für das Thema \"%s\" angelegt."), $this->toString()); - } - } - } - } - - function getMessages() { - $temp = $this->messages; - $this->messages = NULL; - return $temp; - } - - /* * * * * * * * * * * * * * * * * * * * - * * S T A T I C F U N C T I O N S * * - * * * * * * * * * * * * * * * * * * * */ - - function isIssue($issue_id) { - return IssueDB::isIssue($issue_id); - } -} diff --git a/lib/raumzeit/IssueDB.php b/lib/raumzeit/IssueDB.php deleted file mode 100644 index 17d8b95e00037e916ecd47af5d6c2ca4453a7e59..0000000000000000000000000000000000000000 --- a/lib/raumzeit/IssueDB.php +++ /dev/null @@ -1,153 +0,0 @@ -<? -# Lifter002: TODO -# Lifter003: TEST -# Lifter007: TODO -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// IssueDB.php -// -// Datenbank-Abfragen für Issue.php -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * IssueDB.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - * @deprecated - */ - -class IssueDB -{ - public static function restoreIssue($issue_id) - { - $query = "SELECT * - FROM themen - WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue_id]); - return $statement->fetch(PDO::FETCH_ASSOC); - } - - public static function storeIssue(&$issue) - { - if ($issue->new) { - $query = "INSERT INTO themen - (issue_id, seminar_id, author_id, title, description, mkdate, chdate, priority) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $issue->issue_id, - $issue->seminar_id, - $issue->author_id, - $issue->title, - $issue->description, - $issue->mkdate, - $issue->chdate, - $issue->priority - ]); - } else { - $query = "UPDATE themen - SET seminar_id = ?, author_id = ?, title = ?, description = ?, mkdate = ?, priority = ? - WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([ - $issue->seminar_id, - $issue->author_id, - $issue->title, - $issue->description, - $issue->mkdate, - $issue->priority, - $issue->issue_id - ]); - - if ($statement->rowCount()) { - $query = "UPDATE themen SET chdate = UNIX_TIMESTAMP() WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue->issue_id]); - - $query = "SELECT termin_id FROM themen_termine WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue->issue_id]); - $termin_ids = $statement->fetchAll(PDO::FETCH_COLUMN); - - if (count($termin_ids) > 0) { - $query = "UPDATE termine SET chdate = UNIX_TIMESTAMP() WHERE termin_id IN (?)"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_ids]); - } - } - - } - return TRUE; - } - - public static function deleteIssue($issue_id) - { - $query = "DELETE FROM themen WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue_id]); - - $query = "DELETE FROM themen_termine WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue_id]); - } - - public static function isIssue($issue_id) - { - $query = "SELECT 1 FROM themen WHERE issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue_id]); - return (bool)$statement->fetchColumn(); - } - - public static function getDatesforIssue($issue_id) - { - $query = "SELECT termine.* - FROM themen_termine - INNER JOIN termine USING (termin_id) - WHERE issue_id = ? - ORDER BY `date` ASC"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$issue_id]); - - $ret = []; - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $ret[$row['termin_id']] = $row; - } - return $ret; - } - - public static function deleteAllIssues($course_id) - { - $query = "SELECT issue_id FROM themen WHERE seminar_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$course_id]); - $themen = $statement->fetchAll(PDO::FETCH_COLUMN); - - foreach ($themen as $issue_id) { - self::deleteIssue($issue_id, $course_id); - } - - return count($themen); - } -} diff --git a/lib/raumzeit/MetaDate.php b/lib/raumzeit/MetaDate.php deleted file mode 100644 index 1348222880788618aaeacd4aaab1c33d46c2a9f4..0000000000000000000000000000000000000000 --- a/lib/raumzeit/MetaDate.php +++ /dev/null @@ -1,697 +0,0 @@ -<?php -# Lifter002: TODO -# Lifter007: TODO -# Lifter003: TODO -# Lifter010: TODO -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// MetaDate.php -// -// Repräsentiert die Zeit- und Turnusdaten einer Veranstaltung -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * MetaDate.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 28. Juni 2007 - * @access protected - * @package raumzeit - */ -class MetaDate -{ - var $seminar_id = ''; - var $seminarStartTime = 0; - var $seminarDurationTime = 0; - var $cycles = []; - var $hasDatesTmp = []; - - /** - * Constructor - * @param string $seminar_id - */ - function __construct($seminar_id = '') - { - if ($seminar_id != '') { - $this->seminar_id = $seminar_id; - $this->restore(); - } - - } - - /** - * returns start week (Semesterwoche) for a cycledate - * for compatibility the first cycledate is chosen if no one is specified - * - * @deprecated - * @param string id of cycledate - * @return int - */ - function getStartWoche($metadate_id = null) - { - if ($metadate_id) { - return $this->cycles[$metadate_id]->week_offset; - } else { - $first_metadate = $this->getFirstMetadate(); - return $first_metadate ? $first_metadate->week_offset : null; - } - } - - /** - * sets start week (Semesterwoche) for a cycledate - * for compatibility the first cycledate is chosen if no one is specified - * - * @deprecated - * @param int $start_woche - * @param string $metadate_id - * @return null|Ambigous <NULL, unknown> - */ - function setStartWoche($start_woche, $metadate_id = null) - { - if ($metadate_id) { - return $this->cycles[$metadate_id]->week_offset = $start_woche; - } else { - $first_metadate = $this->getFirstMetadate(); - return $first_metadate ? $first_metadate->week_offset = $start_woche : null; - } - } - - /** - * returns first cycledate - * - * @return CycleData - */ - function getFirstMetadate() - { - $cycles = array_keys($this->cycles); - $first_metadate_id = array_shift($cycles); - return $first_metadate_id ? $this->cycles[$first_metadate_id] : null; - } - - /** - * returns the cycle for a cycledate - * for compatibility the first cycledate is chosen if no one is specified - * - * @deprecated - * @param string $metadate_id - * @return int 0,1,2 for weekly, biweekly ... - */ - function getTurnus($metadate_id = null) - { - if ($metadate_id) { - return $this->cycles[$metadate_id]->cycle; - } else { - $first_metadate = $this->getFirstMetadate(); - return $first_metadate ? $first_metadate->cycle : null; - } - } - - /** - * set the cycle for a cycledate - * for compatibility the first cycledate is chosen if no one is specified - * - * @deprecated - * @param int 0,1,2 for weekly, biweekly ... - * @param string $metadate_id - * @return int - */ - function setTurnus($turnus, $metadate_id = null) - { - if ($metadate_id) { - return $this->cycles[$metadate_id]->cycle = $turnus; - } else { - $first_metadate = $this->getFirstMetadate(); - return $first_metadate ? $first_metadate->cycle = $turnus : null; - } - } - - function setSeminarStartTime($start) - { - $this->seminarStartTime = $start; - } - - function setSeminarDurationTime($duration) - { - $this->seminarDurationTime = $duration; - } - - function getSeminarID() - { - return $this->seminar_id; - } - - /** - * internal method to apply cycledate data from assoc array to a given - * CycleData object. checks the start and endtime and retruns false if wrong - * - * @deprecated - * @param array assoc, see CycleData, metadate_id must be in $data['cycle_id'] - * @param CycleData $cycle - * @return boolean - */ - function setCycleData($data, $cycle) - { - $cycle->seminar_id = $this->getSeminarId(); - $cycles = array_keys($this->cycles); - if ($last_one = array_pop($cycles)) { - $cycle->sorter = $this->cycles[$last_one]->sorter > 0 ? $this->cycles[$last_one]->sorter + 1 : 0; - } - if ($cycle->getDescription() != $data['description']) { - $cycle->setDescription($data['description']); - } - - if (isset($data['weekday'])) $cycle->weekday = (int)$data['weekday']; - if (isset($data['week_offset'])) $cycle->week_offset = (int)$data['week_offset']; - if (isset($data['cycle'])) $cycle->cycle = (int)$data['cycle']; - if (isset($data['sws'])) $cycle->sws = $data['sws']; - if (isset($data['endWeek'])) $cycle->end_offset = (int)$data['endWeek']; - if (isset($data['day']) && isset($data['start_stunde']) && isset($data['start_minute']) && isset($data['end_stunde']) && isset($data['end_minute'])) { - - if ( - ($data['start_stunde'] > 23) || ($data['start_stunde'] < 0) || - ($data['end_stunde'] > 23) || ($data['end_stunde'] < 0) || - ($data['start_minute'] > 59) || ($data['start_minute'] < 0) || - ($data['end_minute'] > 59) || ($data['end_minute'] < 0) - ) { - return FALSE; - } - - if (mktime((int)$data['start_stunde'], (int)$data['start_minute']) < mktime((int)$data['end_stunde'], (int)$data['end_minute'])) { - $cycle->setDay($data['day']); - $cycle->setStart($data['start_stunde'], $data['start_minute']); - $cycle->setEnd($data['end_stunde'], $data['end_minute']); - return TRUE; - } - } - - return FALSE; - } - - - /** - * adds a new cycledate, single dates are created if second param is true - * - * @param array assoc, see CycleData, metadate_id must be in $data['cycle_id'] - * @param bool $create_single_dates - * @return string|boolean metadate_id of created cycle - */ - function addCycle($data = [], $create_single_dates = true) - { - $data['day'] = (int)$data['day']; - $data['start_stunde'] = (int)$data['start_stunde']; - $data['start_minute'] = (int)$data['start_minute']; - $data['end_stunde'] = (int)$data['end_stunde']; - $data['end_minute'] = (int)$data['end_minute']; - - $cycle = new CycleData(); - - if ($this->setCycleData($data, $cycle)) { - $this->cycles[$cycle->getMetadateID()] = $cycle; - $this->sortCycleData(); - if ($create_single_dates) $this->createSingleDates($cycle->getMetadateID()); - return $cycle->getMetadateID(); - } - return FALSE; - } - - /** - * change existing cycledate, changes also corresponding single dates - * - * @param array assoc, see CycleData, metadate_id must be in $data['cycle_id'] - * @return number|boolean - */ - function editCycle($data = []) - { - $cycle = $this->cycles[$data['cycle_id']]; - $new_start = mktime((int)$data['start_stunde'], (int)$data['start_minute']); - $new_end = mktime((int)$data['end_stunde'], (int)$data['end_minute']); - $old_start = mktime($cycle->getStartStunde(), $cycle->getStartMinute()); - $old_end = mktime($cycle->getEndStunde(), $cycle->getEndMinute()); - - if (($new_start >= $old_start) && ($new_end <= $old_end) && ($data['day'] == $cycle->day) && ($data['endWeek'] == $cycle->end_offset)) { - // Zeitraum wurde verkuerzt, Raumbuchungen bleiben erhalten... - if ($this->setCycleData($data, $cycle)) { - $termine = $cycle->getSingleDates(); - foreach ($termine as $key => $val) { - $tos = $val->getStartTime(); - $toe = $val->getEndTime(); - if ($toe > time()) { - $t_start = mktime((int)$data['start_stunde'], (int)$data['start_minute'], 0, date('m', $tos), date('d', $tos), date('Y', $tos)); - $t_end = mktime((int)$data['end_stunde'], (int)$data['end_minute'], 0, date('m', $toe), date('d', $toe), date('Y', $toe)); - $termine[$key]->setTime($t_start, $t_end); - $termine[$key]->store(); - } else { - unset($termine[$key]); - } - } - $this->sortCycleData(); - } - return sizeof($termine); - } else { - if ($this->setCycleData($data, $cycle)) { - // collect all existing themes (issues) for this cycle: - $issues = []; - $issue_objects = []; - $singledate_count = 0; - - // loop through the single dates and look for themes (issues) - $termine = $cycle->getSingleDates(); - foreach ($termine as $key => $termin) { - // get all isues of this date ( zero, one, or more, if the expert view is activated) - // and store them at the relative position of this single date - $issues[$singledate_count] = $termin->getIssueIDs(); - $singledate_count++; - } - // remove all SingleDates in the future for this CycleData - $count = CycleDataDB::deleteNewerSingleDates($data['cycle_id'], time()); - // create new SingleDates - $this->createSingleDates(['metadate_id' => $cycle->getMetaDateId(), - 'startAfterTimeStamp' => time() - ]); - - // clear all loaded SingleDates so no odd ones remain. The Seminar-Class will load them fresh when needed - $cycle->termine = NULL; - - // read all new single dates - $termine = $cycle->getSingleDates(); - - // new dates counter - $new_singledate_count = 0; - - // loop through the single dates and add the themes (issues) - foreach ($termine as $key => $termin) { - // check, if there are issues for this single date - if ($issues[$new_singledate_count] != NULL) { - // add all issues: - foreach ($issues[$new_singledate_count] as $issue_key => $issue_id) { - $termin->addIssueID($issue_id); - $termin->store(); - } - } - unset($issues[$new_singledate_count]); - $new_singledate_count++; - } - - // delete issues, that are not assigned to a single date because of to few dates - // (only if the schedule expert view is off) - if (!Config::get()->RESOURCES_ENABLES_EXPERT_SCHEDULE_VIEW) { - if ($new_singledate_count < $singledate_count) { - for ($i = $new_singledate_count; $i < $singledate_count; $i++) { - if ($issues[$i] != NULL) { - foreach ($issues[$i] as $issue_id) { - // delete this issue - IssueDB::deleteIssue($issue_id); - } - } - } - } - } - $this->sortCycleData(); - return $count; - } - } - return FALSE; - } - - /** - * completey remove cycledate - * @see CycleData::delete() - * @param string $cycle_id - * @return boolean - */ - function deleteCycle($cycle_id) - { - $this->cycles[$cycle_id]->delete(); - unset ($this->cycles[$cycle_id]); - return TRUE; - } - - function deleteSingleDate($cycle_id, $date_id, $filterStart, $filterEnd) - { - $this->cycles[$cycle_id]->deleteSingleDate($date_id, $filterStart, $filterEnd); - } - - function unDeleteSingleDate($cycle_id, $date_id, $filterStart, $filterEnd) - { - return $this->cycles[$cycle_id]->unDeleteSingleDate($date_id, $filterStart, $filterEnd); - } - - /** - * store all changes to cycledates for the course, removed cycles are deleted from database - * @return int > 0 if changes where made - */ - function store() - { - $old_cycle_dates = []; - $changed = 0; - foreach (SeminarCycleDate::findBySeminar($this->seminar_id) as $c) { - $old_cycle_dates[$c->getId()] = $c; - } - $removed = array_diff(array_keys($old_cycle_dates), array_keys($this->cycles)); - foreach ($removed as $one) { - $changed += $old_cycle_dates[$one]->delete(); - } - foreach ($this->cycles as $one) { - $changed += $one->storeCycleDate(); - } - $this->sortCycleData(); - return $changed; - } - - - /** - * load all cycledates from database - */ - function restore() - { - $this->cycles = []; - foreach (SeminarCycleDate::findBySeminar($this->seminar_id) as $c) { - $this->cycles[$c->getId()] = new CycleData($c); - } - $this->sortCycleData(); - } - - function delete($removeSingleDates = TRUE) - { - //TODO: Löschen eines MetaDate-Eintrages (CycleData); - } - - /** - * sort cycledates by sorter column and date - */ - function sortCycleData() - { - uasort($this->cycles, function ($a, $b) { - return $a->sorter - $b->sorter - ?: $a->weekday - $b->weekday - ?: $a->start_hour - $b->start_hour; - }); - } - - /** - * returns cycledates as arrays - * - * @param bool $show_invisibles if cycles without dates should - * be in the array, defaults to false - * @return array assoc of cycledate data arrays - */ - function getCycleData($show_invisibles = false) - { - $ret = []; - - foreach ($this->cycles as $val) { - if ($val->is_visible || $show_invisibles) { - $ret[$val->getMetaDateID()] = $val->toArray(); - } - } - return $ret; - } - - /** - * returns the cycledate objects - * @return array of CycleData objects - */ - function getCycles() - { - return $this->cycles; - } - - /** - * returns an array of SingleDate objects corresponding to the given cycle id - * use the optional params to specify a timerange - * - * @param string a cycle id - * @param int unix timestamp - * @param int unix timestamp - * @return array of SingleDate objects - */ - function getSingleDates($metadate_id, $filterStart = 0, $filterEnd = 0) - { - if (!$this->cycles[$metadate_id]->termine) { - $this->readSingleDates($metadate_id, $filterStart, $filterEnd); - } - - return $this->cycles[$metadate_id]->termine; - } - - /** - * reload SingleDate objects for a given cycle id - * - * @param string $metadate_id - * @param int $start - * @param int $end - * @return bool - */ - function readSingleDates($metadate_id, $start = 0, $end = 0) - { - return $this->cycles[$metadate_id]->readSingleDates($start, $end); - } - - /** - * returns true if a given cycle has at least one date at all or in the given time range - * - * @param string $metadate_id - * @param int $filterStart - * @param int $filterEnd - * @return bool - */ - function hasDates($metadate_id, $filterStart = 0, $filterEnd = 0) - { - if (!isset($this->hasDatesTmp[$metadate_id])) { - $this->hasDatesTmp[$metadate_id] = MetaDateDB::has_dates($metadate_id, $this->getSeminarID(), $filterStart, $filterEnd); - } - - return $this->hasDatesTmp[$metadate_id]; - } - - /** - * create single dates for one cycle and all semester and store them in database, deleting obsolete ones - * - * @param mixed cycle id (string) or array with 'metadate_id' => string cycle id, 'startAfterTimeStamp' => int timestamp to override semester start - */ - function createSingleDates($data) - { - foreach ($this->getVirtualSingleDates($data) as $semester_id => $dates_for_semester) { - list($dates, $dates_to_delete) = array_values($dates_for_semester); - foreach ($dates_to_delete as $d) $d->delete(); - foreach ($dates as $d) { - if ($d->isUpdate()) continue; //vorhandene Termine nicht speichern wg. chdate - $d->store(); - } - } - //das sollte nicht nötig sein, muss aber erst genauer untersucht werden - $this->store(); - $this->restore(); - } - - /** - * generate single date objects for one cycle and all semester, existing dates are merged in - * - * @param mixed cycle id (string) or array with 'metadate_id' => string cycle id, 'startAfterTimeStamp' => int timestamp to override semester start - * @return array array of arrays, for each semester id an array of two arrays of SingleDate objects: 'dates' => all new and surviving dates, 'dates_to_delete' => obsolete dates - */ - function getVirtualSingleDates($data) - { - if (is_array($data)) { - $metadate_id = $data['metadate_id']; - $startAfterTimeStamp = $data['startAfterTimeStamp']; - } else { - $metadate_id = $data; - $startAfterTimeStamp = 0; - } - - $ret = []; - - $all_semester = Semester::findAllVisible(false); - $sem_begin = null; - $sem_end = null; - // get the starting-point for creating singleDates for the choosen cycleData - foreach ($all_semester as $val) { - if (($this->seminarStartTime >= $val["beginn"]) && ($this->seminarStartTime <= $val["ende"])) { - $sem_begin = mktime(0, 0, 0, date("n", $val["vorles_beginn"]), date("j", $val["vorles_beginn"]), date("Y", $val["vorles_beginn"])); - } - } - - // get the end-point - if ($this->seminarDurationTime == -1) { - foreach ($all_semester as $val) { - $sem_end = $val['vorles_ende']; - } - } else { - $i = 0; - foreach ($all_semester as $val) { - $i++; - $timestamp = $this->seminarDurationTime + $this->seminarStartTime; - if (($timestamp >= $val['beginn']) && ($timestamp <= $val['ende'])) { - $sem_end = $val["vorles_ende"]; - } - } - } - - $passed = false; - foreach ($all_semester as $val) { - if ($sem_begin <= $val['vorles_beginn']) { - $passed = true; - } - if ($passed && ($sem_end >= $val['vorles_ende']) && ($startAfterTimeStamp <= $val['ende'])) { - $ret[$val['semester_id']] = $this->getVirtualSingleDatesForSemester($metadate_id, $val['vorles_beginn'], $val['vorles_ende'], $startAfterTimeStamp); - } - } - - return $ret; - } - - /** - * generate single date objects for one cycle and one semester, existing dates are merged in - * - * @param string cycle id - * @param int timestamp of semester start - * @param int timestamp of semester end - * @param int alternative timestamp to start from - * @param int correction calculation (obsolete) - * @return array returns an array of two arrays of SingleDate objects: 'dates' => all new and surviving dates, 'dates_to_delete' => obsolete dates - */ - function getVirtualSingleDatesForSemester($metadate_id, $sem_begin, $sem_end, $startAfterTimeStamp, $corr = 0) - { - $dates = []; - $dates_to_delete = []; - - // loads the singledates of the by metadate_id denoted regular time-entry into the object - $this->readSingleDates($metadate_id); - - // The currently existing singledates for the by metadate_id denoted regular time-entry - $existingSingleDates =& $this->cycles[$metadate_id]->getSingleDates(); - - $start_woche = $this->cycles[$metadate_id]->week_offset; - $end_woche = $this->cycles[$metadate_id]->end_offset; - - $turnus = $this->cycles[$metadate_id]->cycle; - - // This variable is used to check if a given singledate shall be created in a bi-weekly seminar. - if ($start_woche == -1) $start_woche = 0; - - $week = 0; - - // get the first presence date after sem_begin - $day_of_week = date('l', strtotime('Sunday + ' . $this->cycles[$metadate_id]->day . ' days')); - $stamp = strtotime('this week ' . $day_of_week, $sem_begin); - - $start_time = mktime( - (int)$this->cycles[$metadate_id]->start_stunde, // Hour - (int)$this->cycles[$metadate_id]->start_minute, // Minute - 0, // Second - date("n", $stamp), // Month - date("j", $stamp), // Day - date("Y", $stamp)); // Year - - $end_time = mktime( - (int)$this->cycles[$metadate_id]->end_stunde, // Hour - (int)$this->cycles[$metadate_id]->end_minute, // Minute - 0, // Second - date("n", $stamp), // Month - date("j", $stamp), // Day - date("Y", $stamp)); // Year - - // loop through all possible singledates for this regular time-entry - do { - - // if dateExists is true, the singledate will not be created. Default is of course to create the singledate - $dateExists = false; - - // do not create singledates if they are earlier than the semester start - if ($end_time < $sem_begin) $dateExists = true; - - // do not create singledates, if they are earlier then the chosen start-week - if ($start_woche > $week || (isset($end_woche) && $week > $end_woche)) $dateExists = true; - - // bi-weekly check - if ($turnus > 0 && ($week - $start_woche) > 0 && (($week - $start_woche) % ($turnus + 1))) { - $dateExists = true; - } - - /* - * We only create dates, which do not already exist, so we do not overwrite existing dates. - * - * Additionally, we delete singledates which are not needed any more (bi-weekly, changed start-week, etc.) - */ - foreach ($existingSingleDates as $key => $val) { - // take only the singledate into account, that maps the current timepoint - if ($start_time > $startAfterTimeStamp && ($val->date == $start_time) && ($val->end_time == $end_time)) { - - // bi-weekly check - if ($turnus > 0 && ($week - $start_woche) > 0 && (($week - $start_woche) % ($turnus + 1))) { - $dates_to_delete[$key] = $val; - unset($existingSingleDates[$key]); - } - - // delete singledates if they are earlier than the chosen start-week - if ($start_woche > $week || (isset($end_woche) && $week > $end_woche)) { - $dates_to_delete[$key] = $val; - unset($existingSingleDates[$key]); - } - - $dateExists = true; - if (isset($existingSingleDates[$key])) { - $dates[$key] = $val; - } - } - } - - if ($start_time < $startAfterTimeStamp) { - $dateExists = true; - } - - if (!$dateExists) { - - $termin = new SingleDate(['seminar_id' => $this->seminar_id]); - - $all_holiday = SemesterHoliday::getAll(); // fetch all Holidays - foreach ($all_holiday as $val2) { - if (($val2["beginn"] <= $start_time) && ($start_time <= $val2["ende"])) { - $termin->setExTermin(true); - } - } - - //check for calculatable holidays - if (!$termin->isExTermin()) { - $holy_type = holiday($start_time); - if ($holy_type["col"] == 3) { - $termin->setExTermin(true); - } - } - - // fill the singleDate-Object with data - $termin->setMetaDateID($metadate_id); - $termin->setTime($start_time, $end_time); - $termin->setDateType(1); //best guess - - $dates[$termin->getTerminID()] = $termin; - } - - //inc the week, create timestamps for the next singledate - $start_time = strtotime('+1 week', $start_time); - $end_time = strtotime('+1 week', $end_time); - - $week++; - - } while ($end_time < $sem_end); - - return ['dates' => $dates, 'dates_to_delete' => $dates_to_delete]; - } -} diff --git a/lib/raumzeit/MetaDateDB.php b/lib/raumzeit/MetaDateDB.php deleted file mode 100644 index 29af00ec1c57a922a0bd8ccb711d478761708539..0000000000000000000000000000000000000000 --- a/lib/raumzeit/MetaDateDB.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -# Lifter002: DONE - not applicable -# Lifter007: TEST -# Lifter003: TEST -# Lifter010: DONE - not applicable - -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// MetaDateDB.php -// -// Datenbank-Abfragen für MetaDate.php -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * MetaDateDB.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - */ -class MetaDateDB -{ - public static function has_dates($metadate_id, $seminar_id, $filterStart = 0, $filterEnd = 0) - { - $query = "SELECT 1 FROM termine WHERE range_id = ? AND metadate_id = ?"; - $parameters = [$seminar_id, $metadate_id]; - - if ($filterStart != 0) { - $query .= " AND date >= ? AND end_time <= ?"; - array_push($parameters, $filterStart, $filterEnd); - } - - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - return (bool)$statement->fetchColumn(); - } -} diff --git a/lib/raumzeit/SeminarDB.php b/lib/raumzeit/SeminarDB.php deleted file mode 100644 index 600510bb68a44300e40baa7ef5eb3014a75f9e23..0000000000000000000000000000000000000000 --- a/lib/raumzeit/SeminarDB.php +++ /dev/null @@ -1,264 +0,0 @@ -<?php -# Lifter002: DONE -# Lifter003: TEST -# Lifter007: TODO -# Lifter010: DONE -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// SeminarDB.php -// -// Datenbank-Abfragen für Seminar -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * SeminarDB.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - * @deprecated - */ - -class SeminarDB -{ - public static function getIssues($seminar_id) - { - $query = "SELECT * - FROM themen - WHERE themen.seminar_id = ? - ORDER BY priority"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id]); - return $statement->fetchAll(PDO::FETCH_ASSOC); - } - - public static function getSingleDates($seminar_id, $start = 0, $end = 0) - { - $query = "SELECT termine.*, resource_bookings.resource_id, GROUP_CONCAT(DISTINCT trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM termine - LEFT JOIN termin_related_persons AS trp ON (termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (termine.termin_id = trg.termin_id) - LEFT JOIN resource_bookings ON (resource_bookings.range_id = termine.termin_id) - WHERE termine.range_id = ? - AND (metadate_id IS NULL OR metadate_id = '')"; - $parameters = [$seminar_id]; - - if ($start != 0 || $end != 0) { - $query .= " AND termine.date BETWEEN ? AND ?"; - array_push($parameters, $start, $end); - } - - $query .= " GROUP BY termine.termin_id ORDER BY date"; - - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - $ret = []; - while ($data = $statement->fetch(PDO::FETCH_ASSOC)) { - if ($data['related_persons']) { - $data['related_persons'] = explode(',', $data['related_persons']); - } - if ($data['related_groups']) { - $data['related_groups'] = explode(',', $data['related_groups']); - } - - $ret[] = $data; - } - - return $ret; - } - - public static function getStatOfNotBookedRooms($cycle_id, $seminar_id, $filterStart = 0, $filterEnd = 0) - { - $stat = [ - 'all' => 0, - 'booked' => 0, - 'open' => 0, - 'open_rooms' => [], - 'declined' => 0, - 'declined_dates' => [], - ]; - - $query = "SELECT termine.*, resource_bookings.resource_id - FROM termine - LEFT JOIN resource_bookings ON (resource_bookings.range_id = termin_id) - WHERE termine.range_id = ? AND metadate_id = ?"; - $parameters = [$seminar_id, $cycle_id]; - - if ($filterStart != 0 || $filterEnd != 0) { - $query .= " AND date >= ? AND end_time <= ?"; - array_push($parameters, $filterStart, $filterEnd); - } - $query .= " ORDER BY date"; - - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - $stat['all'] += 1; - if ($row['resource_id']) { - $stat['booked'] += 1; - } else { - $stat['open'] += 1; - $stat['open_rooms'][] = $row; - } - } - - // count how many singledates have a declined room-request - $query = "SELECT * - FROM termine t - LEFT JOIN resource_requests AS rr ON (t.termin_id = rr.termin_id) - WHERE range_id = ? AND t.metadate_id = ? AND closed = 3"; - $parameters = [$seminar_id, $cycle_id]; - - if ($filterStart != 0 && $filterEnd != 0) { - $query .= " AND date >= ? AND end_time <= ?"; - array_push($parameters, $filterStart, $filterEnd); - } - $query .= " ORDER BY date"; - - $stmt = DBManager::get()->prepare($query); - $stmt->execute($parameters); - - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - $stat['declined'] += 1; - $stat['declined_dates'][] = $data; - } - - return $stat; - } - - public static function hasDatesOutOfDuration($start, $end, $seminar_id) - { - $query = "SELECT COUNT(*) - FROM termine - WHERE range_id = ? AND `date` NOT BETWEEN ? AND ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id, $start, $end]); - return $statement->fetchColumn(); - } - - public static function getFirstDate($seminar_id) - { - $termine = []; - - $query = "SELECT termin_id, date, end_time - FROM termine - WHERE range_id = ? - ORDER BY date"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$seminar_id]); - - $start = 0; - $end = 0; - - while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { - if (($start == 0 && $end == 0) || ($start == $row['date'] && $end == $row['end_time'])) { - $termine[] = $row['termin_id']; - $start = $row['date']; - $end = $row['end_time']; - } - } - - return $termine ?: false; - } - - public static function getNextDate($seminar_id) - { - $termin = []; - - $query = "SELECT termin_id, date, end_time - FROM termine - WHERE range_id = ? AND date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR) - ORDER BY date, end_time"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$seminar_id]); - - $start = 0; - while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) { - if ($start == 0 || $start == $data['date']) { - $termin[] = $data['termin_id']; - $start = $data['date']; - } - } - - $ex_termin = []; - - $query = "SELECT termin_id - FROM ex_termine - WHERE range_id = ? AND date > UNIX_TIMESTAMP(NOW() - INTERVAL 1 HOUR) - AND content != '' AND content IS NOT NULL - ORDER BY date - LIMIT 1"; - $stmt = DBManager::get()->prepare($query); - $stmt->execute([$seminar_id]); - - while ($termin_id = $stmt->fetchColumn()) { - $ex_termin[] = $termin_id; - } - - return compact('termin', 'ex_termin'); - } - - - public static function getDeletedSingleDates($seminar_id, $start = 0, $end = 0) - { - if (($start != 0) || ($end != 0)) { - $query = "SELECT ex_termine.*, GROUP_CONCAT(trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM ex_termine - LEFT JOIN termin_related_persons AS trp ON (ex_termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (ex_termine.termin_id = trg.termin_id) - WHERE ex_termine.range_id = ? - AND (metadate_id IS NULL OR metadate_id = '') - AND `date` BETWEEN ? AND ? - GROUP BY ex_termine.termin_id - ORDER BY date"; - $parameters = [$seminar_id, $start, $end]; - } else { - $query = "SELECT ex_termine.*, GROUP_CONCAT(trp.user_id) AS related_persons, GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM ex_termine - LEFT JOIN termin_related_persons AS trp ON (ex_termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (ex_termine.termin_id = trg.termin_id) - WHERE ex_termine.range_id = ? - AND (metadate_id IS NULL OR metadate_id = '') - GROUP BY ex_termine.termin_id - ORDER BY date"; - $parameters = [$seminar_id]; - } - $statement = DBManager::get()->prepare($query); - $statement->execute($parameters); - - $ret = []; - while ($data = $statement->fetch(PDO::FETCH_ASSOC)) { - $data['ex_termin'] = true; - - if ($data['related_persons']) { - $data['related_persons'] = explode(',', $data['related_persons']); - } - if ($data['related_groups']) { - $data['related_groups'] = explode(',', $data['related_groups']); - } - - $ret[] = $data; - } - return $ret; - } - -} diff --git a/lib/raumzeit/SingleDate.php b/lib/raumzeit/SingleDate.php deleted file mode 100644 index c63800f139ff4865903e16b82444de570f345aa3..0000000000000000000000000000000000000000 --- a/lib/raumzeit/SingleDate.php +++ /dev/null @@ -1,978 +0,0 @@ -<?php -# Lifter002: TODO -# Lifter007: TODO -# Lifter003: TODO -# Lifter010: TODO -/** - * SingelDate.php - Ein (Ex-)Termin - * - * Diese Klasse stellt einen einzelnen Eintrag in der Tabelle termine, bzw. ex_termine dar. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * @author Till Glöggler <tgloeggl@uos.de> - * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 - * @category Stud.IP - */ -require_once 'lib/dates.inc.php'; - -class SingleDate -{ - var $termin_id = ''; - var $date_typ = 1; - var $metadate_id = ''; - var $date = 0; - var $end_time = 0; - var $mkdate = 0; - var $chdate = 0; - var $orig_ex = false; - var $ex_termin = false; - var $range_id = ''; - var $author_id = ''; - var $resource_id = ''; - var $raum = ''; - var $request_id = NULL; - var $requestData = NULL; - var $update = false; - var $issues = NULL; - var $messages = NULL; - var $content = ''; - var $room_request = NULL; - var $related_persons = []; - var $related_groups = []; - - /** - * Return the SingleDate instance of the given id - * - * @param string the id of the instance - * @return SingleDate the SingleDate instance - */ - function getInstance($singledate_id) - { - static $singledate_object_pool; - - if ($singledate_id) { - if (is_object($singledate_object_pool[$singledate_id]) && $singledate_object_pool[$singledate_id]->getTerminId() == $singledate_id) { - return $singledate_object_pool[$singledate_id]; - } else { - $singledate_object_pool[$singledate_id] = new SingleDate($singledate_id); - - return $singledate_object_pool[$singledate_id]; - } - } else { - return new SingleDate(); - } - - } - - function __construct($data = '') - { - global $user, $id; - $termin_id = ''; - if ($data instanceOf CourseDate || $data instanceof CourseExDate) { - $single_date_data = $data->toArray(); - $single_date_data['ex_termin'] = $data instanceOf CourseDate ? 0 : 1; - $single_date_data['resource_id'] = $data->room_booking->resource_id ?? ''; - if ($data instanceOf CourseDate) { - $single_date_data['related_persons'] = $data->dozenten->pluck('user_id'); - $single_date_data['related_groups'] = $data->statusgruppen->pluck('statusgruppe_id'); - } - $this->fillValuesFromArray($single_date_data); - } else { - if (is_array($data)) { - if ($data['termin_id']) $termin_id = $data['termin_id']; - if ($data['seminar_id']) $id = $data['seminar_id']; - } else { - $termin_id = $data; - } - if ($termin_id !== '') { - $this->termin_id = $termin_id; - $this->update = true; - $this->restore(); - } else { - $this->termin_id = md5(uniqid('SingleDate', 1)); - $this->author_id = $user->id; - $this->range_id = $id; - $this->mkdate = time(); - $this->chdate = time(); - $this->update = false; - } - } - } - - public function __toString() - { - return $this->toString(); - } - - function getStartTime() - { - return $this->date; - } - - function setTime($start, $end) - { - if (($start == 0) || ($end == 0)) return false; - - if (($this->date != $start) || ($this->end_time != $end)) { - // validate the passed variables: they have to be ints and $start - // has to be smaller than $end - if ($this->validate($start, $end)) { - $before = $this->toString(); - - // if the time-span has been shortened, keep the room-booking, - // otherwise remove it. - if ($this->resource_id) { - if ($start >= $this->date && $end <= $this->end_time) { - //Shrink the booking. - if ($assign_id = SingleDateDB::getAssignID($this->termin_id)) { - $assign_object = new ResourceBooking($assign_id); - $assign_object->resource_id = $this->resource_id; - $assign_object->begin = $start; - $assign_object->end = $end; - $assign_object->repeat_end = $end; - $assign_object->store(); - } - } else { - $this->killAssign(); - } - } - - $this->date = $start; - $this->end_time = $end; - - $after = $this->toString(); - // logging - if ($before) { - StudipLog::log('SINGLEDATE_CHANGE_TIME', $this->range_id, $this->termin_id, $before . ' -> ' . $after); - } else { - StudipLog::log('SEM_ADD_SINGLEDATE', $this->range_id, $this->termin_id, $after); - } - - return true; - } - - return false; - } - - return false; - } - - function getEndTime() - { - return $this->end_time; - } - - function setComment($comment) - { - $this->content = $comment; - } - - function getComment() - { - if (!$this->isExTermin()) return ''; - - return $this->content; - } - - function getMetaDateID() - { - return $this->metadate_id; - } - - function setMetaDateID($id) - { - if ($id != '') { - $this->metadate_id = $id; - - return true; - } else { - $this->metadate_id = 0; - - return false; - } - } - - function getRangeID() - { - return $this->range_id; - } - - function setDateType($typ) - { - $this->date_typ = $typ; - - return true; - } - - function getDateType() - { - return $this->date_typ; - } - - function getTypeName() - { - global $TERMIN_TYP; - - return $TERMIN_TYP[$this->date_typ]['name']; - } - - function getAuthorID() - { - return $this->author_id; - } - - function getChDate() - { - return $this->chdate; - } - - function getMkDate() - { - return $this->mkdate; - } - - function setSeminarID($seminar_id) - { - $this->range_id = $seminar_id; - } - - function getSeminarID() - { - return $this->range_id; - } - - function getSingleDateID() - { - return $this->termin_id; - } - - function getResourceID() - { - return $this->resource_id; - } - - function getTerminID() - { - return $this->termin_id; - } - - function getFreeRoomText() - { - return $this->raum; - } - - function setFreeRoomText($freeRoomText) - { - $this->raum = $freeRoomText; - } - - function getCycleID() - { - return $this->metadate_id; - } - - /** - * @deprecated - */ - function killIssue() - { - $issue_ids = $this->getIssueIDs(); - if (count($issue_ids) > 0) { - CourseTopic::deleteBySQL("issue_id IN (?)", $issue_ids); - - foreach ($issue_ids as $issue_id) { - unset($this->issues[$issue_id]); - } - } - } - - function delete() - { - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/' . $this->range_id); - - $this->chdate = time(); - $this->killAssign(); - - return SingleDateDB::deleteSingleDate($this->termin_id, $this->ex_termin); - } - - function store() - { - - $cache = \Studip\Cache\Factory::getCache(); - $cache->expire('course/undecorated_data/' . $this->range_id); - - $this->chdate = time(); - if ($this->ex_termin) { - $this->killAssign(); - } - - // date_typ = 0 defaults to TERMIN_TYP[1] because there never exists one with zero - if (!$this->date_typ) $this->date_typ = 1; - - if ($this->orig_ex != $this->ex_termin) { - SingleDateDB::deleteSingleDate($this->termin_id, $this->orig_ex); - } - - return SingleDateDB::storeSingleDate($this); - } - - function restore() - { - if (!($data = SingleDateDB::restoreSingleDate($this->termin_id))) { - return false; - } - $this->fillValuesFromArray($data); - - return true; - } - - function setExTermin($ex) - { - if ($ex != $this->ex_termin) { - $this->update = false; - $this->ex_termin = $ex; - - return true; - } - - return false; - } - - function isExTermin() - { - return $this->ex_termin; - } - - function isPresence() - { - return $GLOBALS['TERMIN_TYP'][$this->date_typ]['sitzung'] ? true : false; - } - - function isUpdate() - { - return $this->update; - } - - function isHoliday() - { - $name = null; - foreach (SemesterHoliday::getAll() as $val) { - if (($val['beginn'] <= $this->date) && ($val['ende'] >= $this->end_time)) { - $name = $val['name']; - } - } - - if (!$name) { - $holy_type = holiday($this->date); - $name = $holy_type ? $holy_type['name'] : null; - } - - if ($name) { - return $name; - } else { - return false; - } - } - - function fillValuesFromArray($daten) - { - $this->metadate_id = $daten['metadate_id']; - $this->termin_id = $daten['termin_id']; - if ($daten['date_typ'] != 0) { - $this->date_typ = $daten['date_typ']; - } else { - // if no date_typ is specified it defaults to 1 - $this->date_typ = 1; - } - $this->date = $daten['date']; - $this->end_time = $daten['end_time']; - $this->mkdate = $daten['mkdate']; - $this->chdate = $daten ['chdate']; - $this->ex_termin = $daten['ex_termin'] ?? null; - $this->orig_ex = $daten['ex_termin'] ?? null; - $this->range_id = $daten['range_id']; - $this->author_id = $daten['autor_id']; - $this->resource_id = $daten['resource_id']; - $this->raum = $daten['raum']; - $this->content = $daten['content']; - $this->update = true; - $this->related_persons = is_array($daten['related_persons']) ? $daten['related_persons'] : []; - $this->related_groups = is_array($daten['related_groups']) ? $daten['related_groups'] : []; - - return true; - } - - function toString() - { - $end_hours = strtotime(strftime('%H:%M', $this->end_time)); - $start_hours = strtotime(strftime('%H:%M', $this->date)); - if (!$this->date) { - return null; - } elseif ((($end_hours - $start_hours) / 60 / 60) > 23) { - return sprintf('%s (%s)', strftime('%A, %d.%m.%Y', $this->date), - _('ganztägig')); - } else { - return sprintf('%s - %s', strftime('%A, %d.%m.%Y %H:%M', $this->date), - strftime('%H:%M', $this->end_time)); - } - } - - function bookRoom($room_id, $preparation_time = 0) - { - if ($this->ex_termin || !$room_id) { - return false; - } - - // create a resource-object of the passed room - $room = Room::find($room_id); - - // there is no room with the passed id - if (!$room) { - return false; - } - - // check permissions (is current user allowed to book the passed room?) - if (!$room->userHasBookingRights(User::findCurrent(), $this->date, $this->end_time)) { - return false; - } - - // clear the freetext-field, if we book a room - $this->setFreeRoomText(''); - $this->store(); - - //If there is already a room assigned, "change" the booking. - //Otherwise create a new one. - if ($this->resource_id != '') { - $this->changeAssign($room, $preparation_time); - } else { - $this->insertAssign($room, $preparation_time); - } - - return $room; - } - - - /** - * This method converts overlap data about an overlapping booking - * to a string that can be used to output overlap information to the user. - * Only one overlap is converted by this method. For multiple overlaps - * this method must be called multiple times. - * - * @param ResourceBooking $booking The overlapping booking. - * - * @return string A string representation of the overlap. - */ - protected function getOverlapMessage(ResourceBooking $booking) - { - $message = ''; - - if ($booking->booking_type == ResourceBooking::TYPE_LOCK) { - $message .= sprintf( - _('Vom %1$s, %2$s Uhr bis zum %3$s, %4$s Uhr (Sperrzeit)') . "\n", - date("d.m.Y", $booking->begin), - date("H:i", $booking->begin), - date("d.m.Y", $booking->end), - date("H:i", $booking->end) - ); - } else { - $course = Course::find($booking->course_id); - - if ($course) { - $user_has_permissions = $GLOBALS['perm']->have_studip_perm( - 'dozent', - $course->id, - $GLOBALS['user']->id - ); - if ($user_has_permissions) { - $course_link = URLHelper::getLink( - 'dispatch.php/course/timesrooms/index', - [ - 'cid' => $course->id - ] - ); - $message .= sprintf( - _('Am %1$s von %2$s bis %3$s Uhr durch Veranstaltung %4$s') . "\n", - date('d.m.Y', $booking->begin), - date('H:i', $booking->begin), - date('H:i', $booking->end), - sprintf( - '<a href="%1$s">%2$s</a>', - $course_link, - htmlReady($course->name) - ) - ); - } else { - $course_link = URLHelper::getLink( - 'dispatch.php/course/details', - [ - 'sem_id' => $course->id - ] - ); - $message .= sprintf( - _('Am %1$s von %2$s bis %3$s Uhr durch Veranstaltung %4$s') . "\n", - date('d.m.Y', $booking->begin), - date('H:i', $booking->begin), - date('H:i', $booking->end), - sprintf( - '<a href="%1$s">%2$s</a>', - $course_link, - htmlReady($course->name) - ) - ); - } - } else { - $message .= sprintf( - _('Am %1$s von %2$s bis %3$s Uhr belegt von "%4$s"') . "\n", - date("d.m.Y", $booking->begin), - date("H:i", $booking->begin), - date("H:i", $booking->end), - htmlReady($booking->description) - ); - } - } - - return $message; - } - - - private function insertAssign(Room $room, $preparation_time = 0) - { - $begin = new DateTime(); - $begin->setTimestamp($this->date); - $end = new DateTime(); - $end->setTimestamp($this->end_time); - - //If the following code is executed a new room booking can be created: - try { - $booking = $room->createBooking( - User::findCurrent(), - $this->termin_id, - [ - [ - 'begin' => $begin, - 'end' => $end - ] - ], - null, - 0, - null, - $preparation_time * 60 - ); - if ($booking instanceof ResourceBooking) { - $booking->deleteOverlappingReservations(); - $room_link_string = sprintf( - '<a href="%1$s" data-dialog="1">%2$s</a>', - $room->getActionLink(), - htmlReady($room->name) - ); - - SingleDateDB::storeSingleDate($this); - $msg = sprintf( - _('Für den Termin %1$s wurde der Raum %2$s gebucht.'), - $this->toString(), - $room_link_string - ); - $this->messages['success'][] = $msg; - } - } catch (ResourceBookingRangeException $e) { - $this->messages['error'][] = _('Fehler beim Verknüpfen der Raumbelegung mit dem Einzeltermin!'); - return false; - } catch (ResourceBookingOverlapException $e) { - $error_message = sprintf( - _('Für den Termin %1$s konnte der Raum %2$s nicht gebucht werden, da es Überschneidungen mit folgenden Terminen gibt:'), - $this->toString(), - htmlReady($room->name) - ) . '<br>'; - $overlapping_bookings = array_merge( - $room->getResourceBookings($begin, $end), - $room->getResourceLocks($begin, $end) - ); - foreach ($overlapping_bookings as $overlapping_booking) { - $course_link = null; - if ($overlapping_booking->course) { - $user_is_lecturer = $GLOBALS['perm']->have_studip_perm( - 'dozent', - $overlapping_booking->course->id, - $GLOBALS['user']->id - ); - if ($user_is_lecturer) { - $course_link = URLHelper::getLink( - 'dispatch.php/course/timesrooms/index', - [ - 'cid' => $overlapping_booking->course->id - ] - ); - } - } - - $error_message .= $this->getOverlapMessage( - $overlapping_booking - ); - } - $this->messages['error'][] = $error_message; - return false; - } catch (ResourcePermissionException $e) { - $this->messages['error'][] = $e->getMessage(); - return false; - } catch (ResourceBookingException $e) { - $this->messages['error'][] = $e->getMessage(); - return false; - } - - return true; - } - - - private function changeAssign(Room $room, $preparation_time = 0) - { - $max_preparation_time = (Config::get()->RESOURCES_MAX_PREPARATION_TIME); - if ($preparation_time > $max_preparation_time) { - $this->messages['error'][] = sprintf( - _('Die eingegebene Rüstzeit überschreitet das erlaubte Maximum von %d Minuten!'), - $max_preparation_time - ); - return false; - } - if ($assign_id = SingleDateDB::getAssignID($this->termin_id)) { - $changeAssign = new ResourceBooking($assign_id); - $changeAssign->resource_id = $room->id; - $changeAssign->range_id = $this->termin_id; - $changeAssign->begin = $this->date; - $changeAssign->end = $this->end_time; - $changeAssign->repeat_end = $this->end_time; - $changeAssign->repetition_interval = ''; - if ($preparation_time > 0) { - $changeAssign->preparation_time = $preparation_time * 60; - } - - $room_link_string = sprintf( - '<a href="%1$s" data-dialog="1">%2$s</a>', - $room->getActionLink(), - htmlReady($room->name) - ); - - $overlaps = $changeAssign->getOverlappingBookings(); - if (is_array($overlaps) && (sizeof($overlaps) > 0)) { - $msg = sprintf( - _('Für den Termin %1$s konnte der Raum %2$s nicht gebucht werden, da es Überschneidungen mit folgenden Terminen gibt:'), - $this->toString(), - $room_link_string - ) . '<br>'; - foreach ($overlaps as $overlap) { - $msg .= $this->getOverlapMessage($overlap); - } - $this->messages['error'][] = $msg; - - return false; - } - - $this->resource_id = $room->id; - try { - $changeAssign->store(); - } catch (ResourceBookingOverlapException $e) { - $room = $changeAssign->resource->getDerivedClassInstance(); - if($room instanceof Room) { - $room_text = $link = sprintf( - '<a href="%1$s" data-dialog="1">%2$s</a>', - $room->getActionLink(), - htmlReady($room->name) - ); - } else { - $room_text = $changeAssign->resource->name; - } - $this->messages['error'][] = sprintf( - _('%1$s: Die Buchung vom %2$s bis %3$s konnte wegen Überlappungen nicht gespeichert werden: %4$s'), - $room_text, - date('d.m.Y H:i', $changeAssign->begin), - date('H:i', $changeAssign->end), - htmlReady($e->getMessage()) - ); - } - - $msg = sprintf( - _('Für den Termin %1$s wurde der Raum %2$s gebucht.'), - $this->toString(), - $room_link_string - ); - $this->messages['success'][] = $msg; - - return true; - } - - return false; - } - - function killAssign() - { - $this->resource_id = ''; - if ($assign_id = SingleDateDB::getAssignID($this->termin_id)) { - $assign_object = new ResourceBooking($assign_id); - $assign_object->delete(); - } - } - - - /** - * Returns the room name for this SingleDate object. - * - * @returns string The room name. - */ - public function getRoom() - { - if (!$this->resource_id) { - return null; - } else { - $room = Room::find($this->resource_id); - - return $room->name; - } - } - - - function readIssueIDs() - { - if (!$this->issues) { - if ($data = SingleDateDB::getIssueIDs($this->termin_id)) { - foreach ($data as $val) { - $this->issues[$val['issue_id']] = $val['issue_id']; - } - } - } - - return true; - } - - function getIssueIDs() - { - $this->readIssueIDs(); - - return $this->issues; - } - - function addIssueID($issue_id) - { - $this->readIssueIDs(); - $this->issues[$issue_id] = $issue_id; - - return true; - } - - function deleteIssueID($issue_id) - { - $this->readIssueIDs(); - unset($this->issues[$issue_id]); - SingleDateDB::deleteIssueID($issue_id, $this->termin_id); - - return true; - } - - function getMessages() - { - $temp = $this->messages; - $this->messages = NULL; - - return $temp; - } - - // checks, if the single-date has plausible values - function validate($start = 0, $end = 0) - { - if ($start == 0) { - $start = $this->date; - } - if ($end == 0) { - $end = $this->end_time; - } - - if ($start < 100000) return false; - if ($end < 100000) return false; - if ($start > $end) { - $this->messages['error'][] = _("Die Endzeitpunkt darf nicht vor dem Anfangszeitpunkt liegen!"); - - return false; - } - - return true; - } - - - /** - * returns a html representation of the date - * - * @param array optional variables which are passed to the template - * @return string the html-representation of the date - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - function getDatesHTML($params = []) - { - $template = $GLOBALS['template_factory']->open('dates/date_html.php'); - $template->set_attributes($params); - - return $this->getDatesTemplate($template); - } - - /** - * returns a representation without html of the date - * - * @param array optional variables which are passed to the template - * @return string the representation of the date without html - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - function getDatesExport($params = []) - { - $template = $GLOBALS['template_factory']->open('dates/date_export.php'); - $params['link'] = false; - $template->set_attributes($params); - - return $this->getDatesTemplate($template); - } - - /** - * returns a xml-representation of the date - * - * @param array optional variables which are passed to the template - * @return string the xml-representation of the date - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - function getDatesXML($params = []) - { - $template = $GLOBALS['template_factory']->open('dates/date_xml.php'); - $template->set_attributes($params); - - return $this->getDatesTemplate($template); - } - - /** - * returns a representation of the date with a specifiable template - * - * @param mixed this can be a template-object or a string pointing to a template in path_to_studip/templates - * @return string the template output of the date - * - * @author Till Glöggler <tgloeggl@uos.de> - */ - function getDatesTemplate($template) - { - if (!$template instanceof Flexi\Template && is_string($template)) { - $template = $GLOBALS['template_factory']->open($template); - } - - $template->set_attribute('date', $this); - - return $template->render(); - } - - /** - * adds a given user_id as a related person to the date - * @param string $user_id user_id from auth_user_md5 of the person to be added - */ - public function addRelatedPerson($user_id) - { - $this->related_persons[] = $user_id; - $this->related_persons = array_unique($this->related_persons); - } - - /** - * unsets a given user_id from the array of related persons - * @param string $user_id user_id from auth_user_md5 of the person to be added - */ - public function deleteRelatedPerson($user_id) - { - if (!$this->related_persons) { - $sem = Seminar::getInstance($this->getSeminarID()); - $this->related_persons = array_keys($sem->getMembers('dozent')); - } - foreach ($this->related_persons as $key => $related_person) { - if ($related_person === $user_id) { - unset($this->related_persons[$key]); - } - } - } - - /** - * gets all user_ids of related persons of this date - * @return array of user_ids - */ - public function getRelatedPersons() - { - if (count($this->related_persons)) { - return $this->related_persons; - } else { - $sem = Seminar::getInstance($this->getSeminarID()); - - return array_keys($sem->getMembers('dozent')); - } - } - - /** - * clears all related persons (in the interface this means that all dozents are - * marked as related to the date) - */ - public function clearRelatedPersons() - { - $this->related_persons = []; - } - - /** - * adds a given statusgruppe_id as a related group to the date - * @param string $statusgruppe_id statusgruppe_id from statusgruppen of the group to be added - */ - public function addRelatedGroup($statusgruppe_id) - { - $this->related_groups[] = $statusgruppe_id; - $this->related_groups = array_unique($this->related_groups); - } - - /** - * unsets a given statusgruppe_id from the array of related statusgruppen - * @param string $statusgruppe_id statusgruppe_id from statusgruppen of the group to be removed - */ - public function deleteRelatedGroup($statusgruppe_id) - { - if (!$this->related_groups) { - $groups = Statusgruppen::findBySeminar_id($this->getSeminarID()); - $this->related_groups = array_map(function ($g) { - return $g->getId(); - }, $groups); - } - foreach ($this->related_groups as $key => $related_group) { - if ($related_group === $statusgruppe_id) { - unset($this->related_groups[$key]); - } - } - } - - /** - * gets all statusgruppe_ids of related groups of this date - * @return array of statusgruppe_ids - */ - public function getRelatedGroups() - { - if (count($this->related_groups)) { - return $this->related_groups; - } else { - $groups = Statusgruppen::findBySeminar_id($this->getSeminarID()); - - return array_map(function ($g) { - return $g->getId(); - }, $groups); - } - } - - /** - * clears all related groups - */ - public function clearRelatedGroups() - { - $this->related_groups = []; - $this->messages['success'][] = _('Die beteiligten Gruppen wurden zurückgesetzt!'); - } -} diff --git a/lib/raumzeit/SingleDateDB.php b/lib/raumzeit/SingleDateDB.php deleted file mode 100644 index 04144a64b21ff77d38ac0bdb9d4cd305340e1586..0000000000000000000000000000000000000000 --- a/lib/raumzeit/SingleDateDB.php +++ /dev/null @@ -1,299 +0,0 @@ -<?php -# Lifter007: TODO -# Lifter003: TEST -# Lifter010: DONE -// +--------------------------------------------------------------------------+ -// This file is part of Stud.IP -// SingleDateDB.php -// -// Datenbank-Abfragen für SingleDate.php -// -// +--------------------------------------------------------------------------+ -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - - -/** - * SingleDateDB.php - * - * - * @author Till Glöggler <tgloeggl@uos.de> - * @version 19. Oktober 2005 - * @access protected - * @package raumzeit - */ - -class SingleDateDB -{ - static function storeSingleDate($termin) - { - //NOTE: If you modify this method make sure the changes - //are also inserted in CourseDate::cancelDate and - //CourseExDate::unCancelDate to keep the behavior consistent - //across Stud.IP! - - $table = 'termine'; - - if ($termin->isExTermin()) { - $table = 'ex_termine'; - - $booking = ResourceBooking::findByRange_id($termin->getTerminID()); - if ($booking) { - // delete resource-request, if any - ResourceRequest::deleteBySql( - 'termin_id = :termin_id', - [ - 'termin_id' => $termin->getTerminID() - ] - ); - - // delete resource booking, if any - $booking->delete(); - } - } - - $issueIDs = $termin->getIssueIDs(); - if (is_array($issueIDs)) { - $query = "REPLACE INTO themen_termine (termin_id, issue_id) - VALUES (?, ?)"; - $statement = DBManager::get()->prepare($query); - - foreach ($issueIDs as $val) { - $statement->execute([ - $termin->getTerminID(), - $val - ]); - } - } - - if ($termin->isUpdate()) { - $query = "UPDATE :table - SET metadate_id = :metadate_id, date_typ = :date_typ, - date = :date, end_time = :end_time, - range_id = :range_id, autor_id = :autor_id, - raum = :raum, content = :content - WHERE termin_id = :termin_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':table', $table, StudipPDO::PARAM_COLUMN); - $statement->bindValue(':metadate_id', $termin->getMetaDateID() ?: null); - $statement->bindValue(':date_typ', $termin->getDateType()); - $statement->bindValue(':date', $termin->getStartTime()); - $statement->bindValue(':end_time', $termin->getEndTime()); - $statement->bindValue(':range_id', $termin->getRangeID()); - $statement->bindValue(':autor_id', $termin->getAuthorID()); - $statement->bindValue(':raum', $termin->getFreeRoomText()); - $statement->bindValue(':content', $termin->getComment()); - $statement->bindValue(':termin_id',$termin->getTerminID()); - $statement->execute(); - - if ($statement->rowCount() > 0) { - $query = "UPDATE :table SET chdate = :chdate WHERE termin_id = :termin_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':table', $table, StudipPDO::PARAM_COLUMN); - $statement->bindValue(':chdate', $termin->getChDate()); - $statement->bindValue(':termin_id', $termin->getTerminID()); - $statement->execute(); - } - } else { - $query = "REPLACE INTO :table - (metadate_id, date_typ, date, end_time, mkdate, chdate, - termin_id, range_id, autor_id, raum, content) - VALUES - (:metadate_id, :date_typ, :date, :end_time, :mkdate, :chdate, - :termin_id, :range_id, :autor_id, :raum, :content)"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':table', $table, StudipPDO::PARAM_COLUMN); - $statement->bindValue(':metadate_id', $termin->getMetaDateID()); - $statement->bindValue(':date_typ', $termin->getDateType()); - $statement->bindValue(':date', $termin->getStartTime()); - $statement->bindValue(':end_time', $termin->getEndTime()); - $statement->bindValue(':mkdate', $termin->getMkDate()); - $statement->bindValue(':chdate', $termin->getChDate()); - $statement->bindValue(':termin_id', $termin->getTerminID()); - $statement->bindValue(':range_id', $termin->getRangeID()); - $statement->bindValue(':autor_id', $termin->getAuthorID()); - $statement->bindValue(':raum', $termin->getFreeRoomText()); - $statement->bindValue(':content', $termin->getComment()); - $statement->execute(); - } - - $query = "DELETE FROM termin_related_persons WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin->getTerminId()]); - - if (count($termin->related_persons) - && (count($termin->related_persons) < CourseMember::countBySQL("Seminar_id = ? AND status = 'dozent'", [$termin->range_id]))) { - $query = "INSERT IGNORE INTO termin_related_persons (range_id, user_id) VALUES (?, ?)"; - $statement = DBManager::get()->prepare($query); - - foreach ($termin->getRelatedPersons() as $user_id) { - $statement->execute([ - $termin->getTerminId(), - $user_id - ]); - } - } - - $query = "DELETE FROM termin_related_groups WHERE termin_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin->getTerminId()]); - if (count($termin->related_groups) - && (count($termin->related_groups) < Statusgruppen::countBySQL("range_id = ?", [$termin->range_id]))) { - $query = "INSERT IGNORE INTO termin_related_groups (termin_id, statusgruppe_id) VALUES (?, ?)"; - $statement = DBManager::get()->prepare($query); - foreach ($termin->getRelatedGroups() as $statusgruppe_id) { - $statement->execute([ - $termin->getTerminId(), - $statusgruppe_id - ]); - } - } - - return true; - } - - static function restoreSingleDate($termin_id) - { - $query = "SELECT termine.*, resource_id, 0 AS ex_termin, - GROUP_CONCAT(trp.user_id) AS related_persons, - GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM termine - LEFT JOIN termin_related_persons AS trp ON (termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (termine.termin_id = trg.termin_id) - LEFT JOIN resource_bookings ON (resource_bookings.range_id = termine.termin_id) - WHERE termine.termin_id = ? - GROUP BY termine.termin_id - ORDER BY NULL"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_id]); - if ($result = $statement->fetch(PDO::FETCH_ASSOC)) { - $result['related_persons'] = array_filter(explode(',', $result['related_persons'])); - $result['related_groups'] = array_filter(explode(',', $result['related_groups'])); - return $result; - } - - $query = "SELECT ex_termine.*, 1 AS ex_termin, - GROUP_CONCAT(trp.user_id) AS related_persons, - GROUP_CONCAT(DISTINCT trg.statusgruppe_id) AS related_groups - FROM ex_termine - LEFT JOIN termin_related_persons AS trp ON (ex_termine.termin_id = trp.range_id) - LEFT JOIN termin_related_groups AS trg ON (ex_termine.termin_id = trg.termin_id) - WHERE ex_termine.termin_id = ? - GROUP BY termin_id - ORDER BY NULL"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_id]); - if ($result = $statement->fetch(PDO::FETCH_ASSOC)) { - $result['related_persons'] = array_filter(explode(',', $result['related_persons'])); - $result['related_groups'] = array_filter(explode(',', $result['related_groups'])); - return $result; - } - - return false; - } - - static function deleteSingleDate($id, $ex_termin) - { - if (Config::get()->RESOURCES_ENABLE) { - // delete resource booking, if any - $killAssign = new ResourceBooking(self::getAssignID($id)); - $killAssign->delete(); - - //Delete resource requests: - ResourceRequest::deleteBySql( - 'termin_id = :termin_id', - [ - 'termin_id' => $id - ] - ); - } - - // Prepare query that deletes all entries for a given termin id - // from a given table - $query = "DELETE FROM :table WHERE termin_id = :termin_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':termin_id', $id); - - // Execute statement for the termin itself (ex_termin if neccessary) - $statement->bindValue(':table', $ex_termin ? 'ex_termine' : 'termine', StudipPDO::PARAM_COLUMN); - $statement->execute(); - - // Execute statement for themen_termine - $statement->bindValue(':table', 'themen_termine', StudipPDO::PARAM_COLUMN); - $statement->execute(); - - // Execute statement for termin_related_persons - $query = "DELETE FROM termin_related_persons WHERE range_id = :termin_id"; - $statement = DBManager::get()->prepare($query); - $statement->bindValue(':termin_id', $id); - $statement->execute(); - - return true; - } - - static function getAssignID($termin_id) - { - $query = "SELECT resource_bookings.id - FROM termine - LEFT JOIN resource_bookings ON (resource_bookings.range_id = termin_id) - WHERE termin_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_id]); - return $statement->fetchColumn() ?: false; - } - - - static function getIssueIDs($termin_id) - { - $query = "SELECT tt.* - FROM themen_termine AS tt - LEFT JOIN themen AS t USING (issue_id) - WHERE termin_id = ? - AND issue_id IS NOT NULL AND issue_id != '' ORDER BY t.priority, t.title"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_id]); - $result = $statement->fetchAll(PDO::FETCH_ASSOC); - - return $result ?: null; - } - - static function deleteIssueID($issue_id, $termin_id) - { - $query = "DELETE FROM themen_termine WHERE termin_id = ? AND issue_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$termin_id, $issue_id]); - - return true; - } - - - static function deleteAllDates($course_id) - { - $query = "DELETE FROM ex_termine WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$course_id]); - - $query = "SELECT termin_id FROM termine WHERE range_id = ?"; - $statement = DBManager::get()->prepare($query); - $statement->execute([$course_id]); - - $termine = 0; - while ($termin_id = $statement->fetchColumn()) { - self::deleteSingleDate($termin_id, false); - $termine += 1; - } - - return $termine; - } -} diff --git a/lib/raumzeit/raumzeit_functions.inc.php b/lib/raumzeit/raumzeit_functions.inc.php deleted file mode 100644 index a65f3bb0dd744032242c6f4c704eab2b4be659b6..0000000000000000000000000000000000000000 --- a/lib/raumzeit/raumzeit_functions.inc.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -# Lifter002: TODO -# Lifter007: TODO -# Lifter003: TODO -# Lifter010: TODO -/* -raumzeit_functions.inc.php -Helper functions for the "RaumZeit"-pages -Copyright (C) 2005-2007 Till Glöggler <tgloeggl@uos.de> - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -function getAllSortedSingleDates(&$sem) { - $turnus = $sem->getFormattedTurnusDates(); - - $termine = []; - foreach ($sem->metadate->cycles as $metadate_id => $val) { - $termine = array_merge($termine, $sem->getSingleDatesForCycle($metadate_id)); - } - - $termine = array_merge($termine, $sem->getSingleDates(true, false, true)); - uasort ($termine, function ($a, $b) { - if ($a->getStartTime() === $b->getStartTime()) { - return strnatcasecmp($a->getRoom(), $b->getRoom()); - } - return $a->getStartTime() - $b->getStartTime(); - }); - - return $termine; -} - -/** - * @param string $comment - * @param array $dates SingleDate - */ -function raumzeit_send_cancel_message($comment, $dates) -{ - if (!is_array($dates)) { - $dates = [$dates]; - } - $course = Course::find($dates[0]->range_id); - if ($course) { - $subject = sprintf(_("[%s] Terminausfall"), $course->name); - $recipients = $course->members->pluck('username'); - $lecturers = $course->members->findBy('status', 'dozent')->pluck('nachname'); - $message = sprintf(_("In der Veranstaltung %s fällt der/die folgende(n) Termine aus:"), - $course->name . ' ('. join(',', $lecturers) .') ' . $course->start_semester->name); - $message .= "\n\n- "; - $message .= join("\n- " , array_map(function($a) {return (string)$a; }, $dates)); - if ($comment) { - $message .= "\n\n" . $comment; - } - $msg = new messaging(); - return $msg->insert_message($message, $recipients, '____%system%____', '', '', '', '', $subject, true); - } - -} diff --git a/lib/showNews.inc.php b/lib/showNews.inc.php index c3734d432d425d11b57e9c351c94b9f3450634ed..1a52d630b3b4d0625f23b0c3125dcef9aeec714e 100644 --- a/lib/showNews.inc.php +++ b/lib/showNews.inc.php @@ -205,11 +205,11 @@ function show_rss_news($range_id, $type) break; case 'sem': $studip_url = $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/course/overview?cid=' . $range_id; - $sem_obj = Seminar::GetInstance($range_id); - if ($sem_obj->read_level > 0) { + $course = Course::find($range_id); + if ($course->lesezugriff > 0) { $studip_url .= '&again=yes'; } - $title = $sem_obj->getName() . ' (Stud.IP - ' . Config::get()->UNI_NAME_CLEAN . ')'; + $title = $course->name . ' (Stud.IP - ' . Config::get()->UNI_NAME_CLEAN . ')'; $description = _('Neuigkeiten der Veranstaltung') . ' ' . $title; break; diff --git a/lib/webservices/api/studip_seminar.php b/lib/webservices/api/studip_seminar.php index 92a683ba7446b8ccf2174e3727a7f0d9b12d36ac..27159ec9b2fd4acd8369831660406136f6e0676a 100644 --- a/lib/webservices/api/studip_seminar.php +++ b/lib/webservices/api/studip_seminar.php @@ -20,7 +20,7 @@ class StudipSeminarHelper { $seminar_id = preg_replace('/\W/', '', $seminar_id); - return Seminar::getInstance($seminar_id)->getName(); + return Course::find($seminar_id)->name; } public static function validate_seminar_permission($ticket, $seminar_id, $permission) diff --git a/lib/webservices/services/lecture_tree_webservice.php b/lib/webservices/services/lecture_tree_webservice.php index 5748a5124e92570a455101a259d9bee23c7edf8a..422d7fb6d097582d042f512d97e9624c1d3aa403 100644 --- a/lib/webservices/services/lecture_tree_webservice.php +++ b/lib/webservices/services/lecture_tree_webservice.php @@ -40,7 +40,7 @@ class LectureTreeService extends AccessControlledService foreach($seminar_ids as $seminar_id) { - $sem_obj = new Seminar($seminar_id['seminar_id']); + $course = Course::find($seminar_id['seminar_id']); $lecturers = StudipSeminarHelper::get_participants($seminar_id['seminar_id'], 'dozent'); @@ -50,10 +50,15 @@ class LectureTreeService extends AccessControlledService } $seminar_info = new Studip_Seminar_Info(); - $seminar_info->title = $sem_obj->getName(); + $seminar_info->title = $course->name; $seminar_info->lecturers = $lecturers; - $seminar_info->turnus = $sem_obj->getDatesTemplate('dates/seminar_export', ['semester_id' => $term_id]); - $seminar_info->lecture_number = $sem_obj->seminar_number; + $semester = Semester::find($term_id); + if ($semester) { + $seminar_info->turnus = $course->getAllDatesInSemester($semester, $semester)->toStringArray(true); + } else { + $seminar_info->turnus = ''; + } + $seminar_info->lecture_number = $course->veranstaltungsnummer; $seminar_infos [] = $seminar_info; } diff --git a/templates/dates/course_date_list.php b/templates/dates/course_date_list.php new file mode 100644 index 0000000000000000000000000000000000000000..4fbc34f025db5d84f0d8a3e977764fb49a822b6b --- /dev/null +++ b/templates/dates/course_date_list.php @@ -0,0 +1,26 @@ +<?php +/** + * @var CourseDateList $collection + * @var bool $with_room_names + * @var bool $with_cancelled_dates + * + * @var SeminarCycleDate $regular_date + * @var CourseDate $single_date + * @var CourseExDate $cancelled_date + */ +?> +<? if (!$collection->isEmpty()) : ?> + <ul> + <? foreach ($collection->getRegularDates() as $regular_date) : ?> + <li><?= $regular_date->toString('long-start') ?></li> + <? endforeach ?> + <? foreach ($collection->getSingleDates() as $single_date) : ?> + <li><?= $single_date->getFullName($with_room_names ? 'long-include-room' : 'long') ?></li> + <? endforeach ?> + <? if ($with_cancelled_dates) : ?> + <? foreach ($collection->getCancelledDates() as $cancelled_date) : ?> + <li><?= $cancelled_date->getFullName() ?></li> + <? endforeach ?> + <? endif ?> + </ul> +<? endif ?> diff --git a/templates/dates/room_grouped_course_date_list.php b/templates/dates/room_grouped_course_date_list.php new file mode 100644 index 0000000000000000000000000000000000000000..29e5c94da55258752958268a0833c0c0e88d751e --- /dev/null +++ b/templates/dates/room_grouped_course_date_list.php @@ -0,0 +1,24 @@ +<?php +/** + * @var CourseDateList[] $grouped_dates The grouped dates to be displayed. + * @var bool $with_cancelled_dates Whether to output cancelled dates (true) or not (false). + */ +?> +<? foreach ($grouped_dates as $room_name => $grouped_date) : ?> + <? + $room = Resource::findOneBySQL("name = :name", ['name' => $room_name]); + if ($room instanceof Resource) { + $room = $room->getDerivedClassInstance(); + } + ?> + <? if ($room instanceof Room) : ?> + <h4> + <a href="<?= $room->getActionLink() ?>" data-dialog> + <?= htmlReady($room->name) ?> + </a> + </h4> + <? else : ?> + <h4><?= htmlReady($room_name) ?></h4> + <? endif ?> + <?= $grouped_date->toHtml(false, $with_cancelled_dates) ?> +<? endforeach ?> diff --git a/templates/dates/seminar_export_location.php b/templates/dates/seminar_export_location.php deleted file mode 100644 index e17f6befa9cc450e73597cdf9461619657cda457..0000000000000000000000000000000000000000 --- a/templates/dates/seminar_export_location.php +++ /dev/null @@ -1,48 +0,0 @@ -<? -// condense regular dates by room -if (!empty($dates['regular']['turnus_data'])) foreach ($dates['regular']['turnus_data'] as $cycle) : - if (!empty($cycle['assigned_rooms'])) foreach ($cycle['assigned_rooms'] as $room_id => $count) : - $room_object = Room::find($room_id); - $output[$room_object->name][] = $cycle['tostring_short'] .' ('. $count .'x)'; - endforeach; - - if (!empty($cycle['freetext_rooms'])) foreach ($cycle['freetext_rooms'] as $room => $count) : - if ($room) : - $output['('. $room .')'][] = $cycle['tostring_short'] .' ('. $count .'x)'; - endif; - endforeach; - -endforeach; - - -// condense irregular dates by room -if (!empty($dates['irregular'])) foreach ($dates['irregular'] as $date) : - if ($date['resource_id']) : - $output_dates[$date['resource_id']][] = $date; - elseif ($date['raum']) : - $output_dates[$date['raum']][] = $date; - endif; -endforeach; - -// now shrink the dates for each room/freetext and add them to the output -if (!empty($output_dates)) foreach ($output_dates as $dates) : - if ($dates[0]['resource_id']) : - $room_object = Room::find($dates[0]['resource_id']); - $output[$room_object->name][] = implode(", ", shrink_dates($dates)); - elseif ($dates[0]['raum']) : - $output['('. $dates[0]['raum'] .')'][] = implode(", ", shrink_dates($dates)); - endif; -endforeach; - -if (!isset($output) || count($output) === 0) : - echo _('nicht angegeben'); -elseif (count($output) === 1) : - $keys = array_keys($output); - echo array_pop($keys); -else : - $pos = 1; - foreach ($output as $room => $dates) : - echo $room .': '. implode("\n", $dates) . (count($output) > $pos ? ', ' : '') . "\n"; - $pos++; - endforeach; -endif; diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php index ea99293b86b358239ee863b65edfaab541579680..468d050dc99222b768d2eb6f1adc2af18b18ae0c 100644 --- a/tests/jsonapi/_bootstrap.php +++ b/tests/jsonapi/_bootstrap.php @@ -27,8 +27,6 @@ $CACHING_ENABLE = false; date_default_timezone_set('Europe/Berlin'); -require_once __DIR__.'/../../composer/autoload.php'; - require 'config.inc.php'; require 'lib/helpers.php'; @@ -60,4 +58,6 @@ class DB_Seminar extends DB_Sql } } +require_once __DIR__.'/../../composer/autoload.php'; + session_id("test-session");