aboutsummaryrefslogtreecommitdiff
path: root/lib/models/Course.class.php
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:07:19 +0200
committerJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:19:12 +0200
commita3da1483a9e689846179159355badfec8073dbec (patch)
tree770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/models/Course.class.php
current code from svn, revision 62608
Diffstat (limited to 'lib/models/Course.class.php')
-rw-r--r--lib/models/Course.class.php983
1 files changed, 983 insertions, 0 deletions
diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php
new file mode 100644
index 0000000..6fda4bd
--- /dev/null
+++ b/lib/models/Course.class.php
@@ -0,0 +1,983 @@
+<?php
+/**
+ * Course.class.php
+ * model class for table seminare
+ *
+ * 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 André Noack <noack@data-quest.de>
+ * @copyright 2012 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ *
+ * @property string seminar_id database column
+ * @property string id alias column for seminar_id
+ * @property string veranstaltungsnummer database column
+ * @property string institut_id database column
+ * @property string name database column
+ * @property string untertitel database column
+ * @property string status database column
+ * @property string beschreibung database column
+ * @property string ort database column
+ * @property string sonstiges database column
+ * @property string lesezugriff database column
+ * @property string schreibzugriff database column
+ * @property string start_time database column
+ * @property string duration_time database column
+ * @property string art database column
+ * @property string teilnehmer database column
+ * @property string vorrausetzungen database column
+ * @property string lernorga database column
+ * @property string leistungsnachweis database column
+ * @property string mkdate database column
+ * @property string chdate database column
+ * @property string ects database column
+ * @property string admission_turnout database column
+ * @property string admission_binding database column
+ * @property string admission_prelim database column
+ * @property string admission_prelim_txt database column
+ * @property string admission_disable_waitlist database column
+ * @property string visible database column
+ * @property string showscore database column
+ * @property string modules database column
+ * @property string aux_lock_rule database column
+ * @property string aux_lock_rule_forced database column
+ * @property string lock_rule database column
+ * @property string admission_waitlist_max database column
+ * @property string admission_disable_waitlist_move database column
+ * @property string completion database column
+ * @property string parent_course database column
+ * @property string end_time computed column read/write
+ * @property SimpleORMapCollection topics has_many CourseTopic
+ * @property SimpleORMapCollection dates has_many CourseDate
+ * @property SimpleORMapCollection ex_dates has_many CourseExDate
+ * @property SimpleORMapCollection members has_many CourseMember
+ * @property SimpleORMapCollection deputies has_many Deputy
+ * @property SimpleORMapCollection statusgruppen has_many Statusgruppen
+ * @property SimpleORMapCollection admission_applicants has_many AdmissionApplication
+ * @property SimpleORMapCollection datafields has_many DatafieldEntryModel
+ * @property SimpleORMapCollection cycles has_many SeminarCycleDate
+ * @property Semester start_semester belongs_to Semester
+ * @property Semester end_semester belongs_to Semester
+ * @property Institute home_institut belongs_to Institute
+ * @property AuxLockRule aux belongs_to AuxLockRule
+ * @property SimpleORMapCollection study_areas has_and_belongs_to_many StudipStudyArea
+ * @property SimpleORMapCollection institutes has_and_belongs_to_many Institute
+ * @property Course parent belongs_to Course
+ * @property SimpleORMapCollection children has_many Course
+ * @property CourseConfig config additional field
+ */
+
+class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, FeedbackRange
+{
+
+ /**
+ * Returns the currently active course or false if none is active.
+ *
+ * @return Course object of currently active course, null otherwise
+ * @since 3.0
+ */
+ public static function findCurrent()
+ {
+ if (Context::isCourse()) {
+ return Context::get();
+ }
+
+ return null;
+ }
+
+ protected static function configure($config = [])
+ {
+ $config['db_table'] = 'seminare';
+ $config['has_many']['topics'] = [
+ 'class_name' => 'CourseTopic',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['dates'] = [
+ 'class_name' => 'CourseDate',
+ 'assoc_foreign_key' => 'range_id',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ 'order_by' => 'ORDER BY date'
+ ];
+ $config['has_many']['ex_dates'] = [
+ 'class_name' => 'CourseExDate',
+ 'assoc_foreign_key' => 'range_id',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['members'] = [
+ 'class_name' => 'CourseMember',
+ 'assoc_func' => 'findByCourse',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['deputies'] = [
+ 'class_name' => 'Deputy',
+ 'assoc_func' => 'findByRange_id',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['statusgruppen'] = [
+ 'class_name' => 'Statusgruppen',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['admission_applicants'] = [
+ 'class_name' => 'AdmissionApplication',
+ 'assoc_func' => 'findByCourse',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['datafields'] = [
+ 'class_name' => 'DatafieldEntryModel',
+ 'assoc_func' => 'findByModel',
+ 'assoc_foreign_key' => function ($model, $params) {
+ $model->setValue('range_id', $params[0]->id);
+ },
+ 'foreign_key' => function ($course) {
+ return [$course];
+ },
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['cycles'] = [
+ 'class_name' => 'SeminarCycleDate',
+ 'assoc_func' => 'findBySeminar',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_many']['blubberthreads'] = [
+ 'class_name' => 'BlubberThread',
+ 'assoc_func' => 'findBySeminar',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+
+ $config['has_and_belongs_to_many']['semesters'] = [
+ 'class_name' => 'Semester',
+ 'thru_table' => 'semester_courses',
+ 'thru_key' => 'course_id',
+ 'thru_assoc_key' => 'semester_id',
+ 'order_by' => 'ORDER BY beginn ASC',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+
+ $config['belongs_to']['home_institut'] = [
+ 'class_name' => 'Institute',
+ 'foreign_key' => 'institut_id',
+ 'assoc_func' => 'find',
+ ];
+ $config['belongs_to']['aux'] = [
+ 'class_name' => 'AuxLockRule',
+ 'foreign_key' => 'aux_lock_rule',
+ ];
+ $config['has_and_belongs_to_many']['study_areas'] = [
+ 'class_name' => 'StudipStudyArea',
+ 'thru_table' => 'seminar_sem_tree',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+ $config['has_and_belongs_to_many']['institutes'] = [
+ 'class_name' => 'Institute',
+ 'thru_table' => 'seminar_inst',
+ 'on_delete' => 'delete',
+ 'on_store' => 'store',
+ ];
+
+ $config['has_and_belongs_to_many']['domains'] = [
+ 'class_name' => 'UserDomain',
+ 'thru_table' => 'seminar_userdomains',
+ 'ondelete' => 'delete',
+ 'onstore' => 'store',
+ 'order_by' => 'ORDER BY name',
+ ];
+
+ $config['has_many']['room_requests'] = [
+ 'class_name' => 'RoomRequest',
+ 'assoc_foreign_key' => 'course_id',
+ 'on_delete' => 'delete',
+ ];
+ $config['belongs_to']['parent'] = [
+ 'class_name' => 'Course',
+ 'foreign_key' => 'parent_course'
+ ];
+ $config['has_many']['children'] = [
+ 'class_name' => 'Course',
+ 'assoc_foreign_key' => 'parent_course',
+ 'order_by' => 'GROUP BY seminar_id ORDER BY VeranstaltungsNummer, Name'
+ ];
+ $config['has_many']['tools'] = [
+ 'class_name' => 'ToolActivation',
+ 'assoc_foreign_key' => 'range_id',
+ 'order_by' => 'ORDER BY position',
+ 'on_delete' => 'delete',
+ ];
+ $config['has_many']['member_notifications'] = [
+ 'class_name' => CourseMemberNotification::class,
+ 'on_delete' => 'delete',
+ ];
+
+ $config['has_one']['courseware'] = [
+ 'class_name' => \Courseware\StructuralElement::class,
+ 'assoc_func' => 'getCoursewareCourse',
+ ];
+
+ $config['default_values']['lesezugriff'] = 1;
+ $config['default_values']['schreibzugriff'] = 1;
+ $config['default_values']['duration_time'] = 0;
+
+ $config['additional_fields']['end_time'] = true;
+
+ $config['additional_fields']['start_semester'] = [
+ 'get' => 'getStartSemester'
+ ];
+ $config['additional_fields']['end_semester'] = [
+ 'get' => 'getEndSemester'
+ ];
+ $config['additional_fields']['semester_text'] = [
+ 'get' => 'getTextualSemester'
+ ];
+
+ $config['additional_fields']['config'] = [
+ 'get' => function (Course $course) {
+ return $course->getConfiguration();
+ }
+ ];
+
+ $config['notification_map']['after_create'] = 'CourseDidCreateOrUpdate';
+ $config['notification_map']['after_store'] = 'CourseDidCreateOrUpdate';
+
+ $config['i18n_fields']['name'] = true;
+ $config['i18n_fields']['untertitel'] = true;
+ $config['i18n_fields']['beschreibung'] = true;
+ $config['i18n_fields']['art'] = true;
+ $config['i18n_fields']['teilnehmer'] = true;
+ $config['i18n_fields']['vorrausetzungen'] = true;
+ $config['i18n_fields']['lernorga'] = true;
+ $config['i18n_fields']['leistungsnachweis'] = true;
+ $config['i18n_fields']['ort'] = true;
+
+ $config['additional_fields']['config']['get'] = function ($course) {
+ return CourseConfig::get($course->id);
+ };
+
+ $config['registered_callbacks']['before_update'][] = 'logStore';
+ $config['registered_callbacks']['after_create'][] = 'setDefaultTools';
+ $config['registered_callbacks']['after_delete'][] = function ($course) {
+ CourseAvatar::getAvatar($course->id)->reset();
+ FeedbackElement::deleteBySQL('course_id = ?', [$course->id]);
+ // Remove subcourse relations, leaving subcourses intact.
+ DBManager::get()->execute(
+ "UPDATE `seminare` SET `parent_course` = NULL WHERE `parent_course` = :course",
+ ['course' => $course->id]
+ );
+ };
+
+ parent::configure($config);
+ }
+
+ public function getEnd_Time()
+ {
+ if (!$this->semesters) {
+ return -1;
+ }
+
+ return $this->semesters->last()->ende;
+ }
+
+ public function setEnd_Time($value)
+ {
+ throw new Exception("This function is unavailable.");
+ }
+
+ /**
+ * Sets the start semester of the course.
+ */
+ public function setStartSemester(Semester $semester)
+ {
+ $this->start_semester = $semester;
+ }
+
+ /**
+ * Sets the end semester of the course.
+ */
+ public function setEndSemester(Semester $semester)
+ {
+ $this->end_semester = $semester;
+ }
+
+ public function setSemesters($semesters)
+ {
+ $semester_ids = array_map(function ($s) {
+ return $s->id;
+ }, $semesters);
+
+ if (count($semester_ids) > 0) {
+ $delete = DBManager::get()->prepare("
+ DELETE FROM semester_courses
+ WHERE semester_id NOT IN (:semester_ids)
+ AND course_id = :course_id
+ ");
+ $delete->execute([
+ 'semester_ids' => $semester_ids,
+ 'course_id' => $this->id,
+ ]);
+ } else {
+ $delete = DBManager::get()->prepare("
+ DELETE FROM semester_courses
+ WHERE course_id = :course_id
+ ");
+ $delete->execute([
+ 'course_id' => $this->id,
+ ]);
+ }
+ $insert = DBManager::get()->prepare("
+ INSERT IGNORE INTO semester_courses
+ SET course_id = :course_id,
+ semester_id = :semester_id,
+ mkdate = UNIX_TIMESTAMP(),
+ chdate = UNIX_TIMESTAMP()
+ ");
+ foreach ($semesters as $semester) {
+ $insert->execute([
+ 'course_id' => $this->id,
+ 'semester_id' => $semester->id,
+ ]);
+ }
+ $this->resetRelation('semesters');
+ }
+
+ /**
+ * Retrieves the first semester of a course, if applicable.
+ *
+ * @returns Semester|null Either the first semester of the course
+ * or null, if no semester could be found.
+ */
+ public function getStartSemester()
+ {
+ if (count($this->semesters) > 0) {
+ return $this->semesters->first();
+ } else {
+ return Semester::findByTimestamp($this['start_time']);
+ }
+ }
+
+ /**
+ * Retrieves the last semester of a course, if applicable.
+ *
+ * @returns Semester|null Either the last semester of the course
+ * or null, if no semester could be found.
+ */
+ public function getEndSemester()
+ {
+ if (count($this->semesters) > 0) {
+ return $this->semesters->last();
+ }
+ return null;
+ }
+
+ /**
+ * Returns the readable semester duration as as string
+ * @return string : readable semester
+ */
+ public function getTextualSemester()
+ {
+ if (count($this->semesters) > 1) {
+ return $this->start_semester->name . ' - ' . $this->end_semester->name;
+ } elseif (count($this->semesters) === 1) {
+ return $this->start_semester->name;
+ } else {
+ return $this->start_semester->name .' - ' . _('unbegrenzt');
+ }
+ }
+
+ /**
+ * Returns true if this course has no end-semester. Else false.
+ * @return bool : true if there is no end-semester
+ */
+ public function isOpenEnded()
+ {
+ return count($this->semesters) === 0;
+ }
+
+ /**
+ * Returns if this course is in the given semester
+ * @param Semester $semester : instance of the given semester
+ * @return bool : true if this course is part of this semester
+ */
+ public function isInSemester(Semester $semester)
+ {
+ if (count($this->semesters) > 0) {
+ foreach ($this->semesters as $s) {
+ if ($s->id === $semester->id) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return $this->start_time <= $semester->beginn;
+ }
+ }
+
+ public function getFreeSeats()
+ {
+ $free_seats = $this->admission_turnout - $this->getNumParticipants();
+ return max($free_seats, 0);;
+ }
+
+ public function isWaitlistAvailable()
+ {
+ if ($this->admission_disable_waitlist) {
+ return false;
+ }
+
+ if ($this->admission_waitlist_max) {
+ return $this->admission_waitlist_max - $this->getNumWaiting() > 0;
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieves all members of a status
+ *
+ * @param String|Array $status the status to filter with
+ * @param bool $as_collection return collection instead of array?
+ * @return Array an array of all those members.
+ */
+ public function getMembersWithStatus($status, $as_collection = false)
+ {
+ $result = CourseMember::findByCourseAndStatus($this->id, $status);
+ return $as_collection
+ ? SimpleCollection::createFromArray($result)
+ : $result;
+ }
+
+ /**
+ * Retrieves the number of all members of a status
+ *
+ * @param String|Array $status the status to filter with
+ *
+ * @return int the number of all those members.
+ */
+ public function countMembersWithStatus($status)
+ {
+ return CourseMember::countByCourseAndStatus($this->id, $status);
+ }
+
+ public function getNumParticipants()
+ {
+ return $this->countMembersWithStatus('user autor') + $this->getNumPrelimParticipants();
+ }
+
+ public function getNumPrelimParticipants()
+ {
+ return AdmissionApplication::countBySql(
+ "seminar_id = ? AND status = 'accepted'",
+ [$this->id]
+ );
+ }
+
+ public function getNumWaiting()
+ {
+ return AdmissionApplication::countBySql(
+ "seminar_id = ? AND status = 'awaiting'",
+ [$this->id]
+ );
+ }
+
+ public function getParticipantStatus($user_id)
+ {
+ $p_status = $this->members->findBy('user_id', $user_id)->val('status');
+ if (!$p_status) {
+ $p_status = $this->admission_applicants->findBy('user_id', $user_id)->val('status');
+ }
+ return $p_status;
+ }
+
+ /**
+ * Returns the semType object that is defined for the course
+ *
+ * @return SemType The semTypeObject for the course
+ */
+ public function getSemType()
+ {
+ $semTypes = SemType::getTypes();
+ if (isset($semTypes[$this->status])) {
+ return $semTypes[$this->status];
+ }
+
+ Log::ERROR(sprintf('SemType not found id:%s status:%s', $this->id, $this->status));
+ return new SemType(['name' => 'Fehlerhafter Veranstaltungstyp']);
+ }
+
+ /**
+ * Returns the SemClass object that is defined for the course
+ *
+ * @return SemClass The SemClassObject for the course
+ */
+ public function getSemClass()
+ {
+ return $this->getSemType()->getClass();
+ }
+
+ /**
+ * Returns the full name of a course. If the important course numbers
+ * (IMPORTANT_SEMNUMBER) is set in global configs it will also display
+ * the coursenumber
+ *
+ * @param string formatting template name
+ * @return string Fullname
+ */
+ public function getFullname($format = 'default')
+ {
+ $template['type-name'] = '%2$s: %1$s';
+ $template['number-type-name'] = '%3$s %2$s: %1$s';
+ $template['type-number-name'] = '%2$s: %3$s %1$s';
+ $template['number-name'] = '%3$s %1$s';
+ $template['number-name-semester'] = '%3$s %1$s (%4$s)';
+ $template['sem-duration-name'] = '%4$s';
+ if ($format === 'default' || !isset($template[$format])) {
+ $format = Config::get()->IMPORTANT_SEMNUMBER ? 'type-number-name' : 'type-name';
+ }
+ $sem_type = $this->getSemType();
+ $data[0] = $this->name;
+ $data[1] = $sem_type['name'];
+ $data[2] = $this->veranstaltungsnummer;
+ $data[3] = $this->start_semester->name;
+ if ($this->start_semester !== $this->end_semester && !$this->isStudygroup()) {
+ $data[3] .= ' - ' . ($this->end_semester ? $this->end_semester->name : _('unbegrenzt'));
+ }
+ return trim(vsprintf($template[$format], array_map('trim', $data)));
+ }
+
+
+ /**
+ * Retrieves the course dates including cancelled dates ("ex-dates").
+ * The dates can be filtered by an optional time range. By default,
+ * all dates are retrieved.
+ *
+ * @param $range_begin The begin timestamp of the time range.
+ *
+ * @param $range_end The end timestamp of the time range.
+ *
+ * @returns SimpleCollection A collection of all retrieved dates and
+ * cancelled dates.
+ */
+ public function getDatesWithExdates($range_begin = 0, $range_end = 0)
+ {
+ $dates = [];
+ if (($range_begin > 0) && ($range_end > 0) && ($range_end > $range_begin)) {
+ $ex_dates = $this->ex_dates->findBy('content', '', '<>')
+ ->findBy('date', $range_begin, '>=')
+ ->findBy('end_time', $range_end, '<=');
+ $dates = $this->dates->findBy('date', $range_begin, '>=')
+ ->findBy('end_time', $range_end, '<=');
+ $dates->merge($ex_dates);
+ } else {
+ $dates = $this->ex_dates->findBy('content', '', '<>');
+ $dates->merge($this->dates);
+ }
+ $dates->uasort(function($a, $b) {
+ return $a->date - $b->date
+ ?: strnatcasecmp($a->getRoomName(), $b->getRoomName());
+ });
+ return $dates;
+ }
+
+ /**
+ * Sets this courses study areas to the given values.
+ *
+ * @param $ids the new study areas
+ * @return bool Changes successfully saved?
+ */
+ public function setStudyAreas($ids)
+ {
+ $old = $this->study_areas->pluck('sem_tree_id');
+ $added = array_diff($ids, $old);
+ $removed = array_diff($old, $ids);
+ $success = false;
+ if ($added || $removed) {
+
+ $this->study_areas = SimpleCollection::createFromArray(StudipStudyArea::findMany($ids));
+
+ if ($this->store()) {
+ NotificationCenter::postNotification('CourseDidChangeStudyArea', $this);
+ $success = true;
+
+ foreach ($added as $one) {
+ StudipLog::log('SEM_ADD_STUDYAREA', $this->id, $one);
+
+ $area = $this->study_areas->find($one);
+ if ($area->isModule()) {
+ NotificationCenter::postNotification(
+ 'CourseAddedToModule',
+ $area,
+ ['module_id' => $one, 'course_id' => $this->id]
+ );
+ }
+ }
+
+ foreach ($removed as $one) {
+ StudipLog::log('SEM_DELETE_STUDYAREA', $this->id, $one);
+
+ $area = StudipStudyArea::find($one);
+ if ($area->isModule()) {
+ NotificationCenter::postNotification(
+ 'CourseRemovedFromModule',
+ $area,
+ ['module_id' => $one, 'course_id' => $this->id]
+ );
+ }
+ }
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Is the current course visible for the current user?
+ * @param string $user_id
+ * @return bool Visible?
+ */
+ public function isVisibleForUser($user_id = null)
+ {
+ return $this->visible
+ || $GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM, $user_id)
+ || $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id);
+ }
+
+ /**
+ * Returns a descriptive text for the range type.
+ *
+ * @return string
+ */
+ public function describeRange()
+ {
+ return _('Veranstaltung');
+ }
+
+ /**
+ * Returns a unique identificator for the range type.
+ *
+ * @return string
+ */
+ public function getRangeType()
+ {
+ return 'course';
+ }
+
+ /**
+ * Returns the id of the current range
+ *
+ * @return string
+ */
+ public function getRangeId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfiguration()
+ {
+ return CourseConfig::get($this);
+ }
+
+ /**
+ * Decides whether the user may access the range.
+ *
+ * @param string|null $user_id Optional id of a user, defaults to current user
+ * @return bool
+ * @todo Check permissions
+ */
+ public function isAccessibleToUser($user_id = null)
+ {
+ if ($user_id === null) {
+ $user_id = $GLOBALS['user']->id;
+ }
+ return $GLOBALS['perm']->have_studip_perm('user', $this->id, $user_id);
+ }
+
+ /**
+ * Decides whether the user may edit/alter the range.
+ *
+ * @param string|null $user_id Optional id of a user, defaults to current user
+ * @return bool
+ * @todo Check permissions
+ */
+ public function isEditableByUser($user_id = null)
+ {
+ if ($user_id === null) {
+ $user_id = $GLOBALS['user']->id;
+ }
+ return $GLOBALS['perm']->have_studip_perm('tutor', $this->id, $user_id);
+ }
+
+ /**
+ * Returns the appropriate icon for the completion status.
+ *
+ * Mapping (completion -> icon role):
+ * - 0 => status-red
+ * - 1 => status-yellow
+ * - 2 => status-green
+ *
+ * @return Icon class
+ */
+ public function getCompletionIcon()
+ {
+ $role = Icon::ROLE_STATUS_RED;
+ if ($this->completion == 1) {
+ $role = Icon::ROLE_STATUS_YELLOW;
+ } elseif ($this->completion == 2) {
+ $role = Icon::ROLE_STATUS_GREEN;
+ }
+ return Icon::create('radiobutton-checked', $role);
+ }
+
+ /**
+ * Generates a general log entry if the course were changed.
+ * Furthermore, this method emits notifications when the
+ * start and/or the end semester has/have changed.
+ */
+ protected function logStore()
+ {
+ if ($this->isFieldDirty('start_semester')) {
+ //Log change of start semester:
+ StudipLog::log('SEM_SET_STARTSEMESTER', $this->id, $this->start_semester->beginn);
+ NotificationCenter::postNotification('CourseDidChangeSchedule', $this);
+ }
+
+ $log = [];
+ if ($this->isFieldDirty('admission_prelim')) {
+ $log[] = $this->admission_prelim ? _('Neuer Anmeldemodus: Vorläufiger Eintrag') : _('Neuer Anmeldemodus: Direkter Eintrag');
+ }
+
+ if ($this->isFieldDirty('admission_binding')) {
+ $log[] = $this->admission_binding? _('Anmeldung verbindlich') : _('Anmeldung unverbindlich');
+ }
+
+ if ($this->isFieldDirty('admission_turnout')) {
+ $log[] = sprintf(_('Neue Teilnehmerzahl: %s'), (int)$this->admission_turnout);
+ }
+
+ if ($this->isFieldDirty('admission_disable_waitlist')) {
+ $log[] = $this->admission_disable_waitlist ? _('Warteliste aktiviert') : _('Warteliste deaktiviert');
+ }
+
+ if ($this->isFieldDirty('admission_waitlist_max')) {
+ $log[] = sprintf(_('Plätze auf der Warteliste geändert: %u'), (int)$this->admission_waitlist_max);
+ }
+
+ if ($this->isFieldDirty('admission_disable_waitlist_move')) {
+ $log[] = $this->admission_disable_waitlist ? _('Nachrücken aktiviert') : _('Nachrücken deaktiviert');
+ }
+
+ if ($this->isFieldDirty('admission_prelim_txt')) {
+ if ($this->admission_prelim_txt) {
+ $log[] = sprintf(_('Neuer Hinweistext bei vorläufigen Eintragungen: %s'), strip_tags(kill_format($this->admission_prelim_txt)));
+ } else {
+ $log[] = _('Hinweistext bei vorläufigen Eintragungen wurde entfert');
+ }
+ }
+
+ if (!empty($log)) {
+ StudipLog::log(
+ 'SEM_CHANGED_ACCESS',
+ $this->id,
+ null,
+ '',
+ implode(' - ', $log)
+ );
+ }
+
+ if ($this->isFieldDirty('visible')) {
+ StudipLog::log($this->visible ? 'SEM_VISIBLE' : 'SEM_INVISIBLE', $this->id);
+ }
+ }
+
+
+ //StudipItem interface implementation:
+
+ public function getItemName($long_format = true)
+ {
+ if ($long_format) {
+ return $this->getFullName();
+ } else {
+ return $this->name;
+ }
+ }
+
+ public function getItemURL()
+ {
+ return URLHelper::getURL(
+ 'dispatch.php/course/details/index',
+ [
+ 'cid' => $this->id
+ ]
+ );
+ }
+
+ public function getItemAvatarURL()
+ {
+ $avatar = CourseAvatar::getAvatar($this->id);
+ if ($avatar) {
+ return $avatar->getURL(Avatar::NORMAL);
+ }
+ return '';
+ }
+
+
+ /**
+ * Export available data of a given user into a storage object
+ * (an instance of the StoredUserData class) for that user.
+ *
+ * @param StoredUserData $storage object to store data into
+ */
+ public static function exportUserData(StoredUserData $storage)
+ {
+ $sorm = self::findThru($storage->user_id, [
+ 'thru_table' => 'seminar_user',
+ 'thru_key' => 'user_id',
+ 'thru_assoc_key' => 'Seminar_id',
+ 'assoc_foreign_key' => 'Seminar_id',
+ ]);
+ if ($sorm) {
+ $field_data = [];
+ foreach ($sorm as $row) {
+ $field_data[] = $row->toRawArray();
+ }
+ if ($field_data) {
+ $storage->addTabularData(_('Seminare'), 'seminare', $field_data);
+ }
+ }
+ }
+ public function getRangeName()
+ {
+ return $this->name;
+ }
+
+ public function getRangeIcon($role)
+ {
+ return Icon::create('seminar', $role);
+ }
+
+ public function getRangeUrl()
+ {
+ return 'course/overview';
+ }
+
+ public function getRangeCourseId()
+ {
+ return $this->Seminar_id;
+ }
+
+ public function isRangeAccessible(string $user_id = null): bool
+ {
+ $user_id = $user_id ?? $GLOBALS['user']->id;
+ return $GLOBALS['perm']->have_studip_perm('autor', $this->Seminar_id, $user_id);
+ }
+
+
+ public function getLink() : StudipLink
+ {
+ return new StudipLink($this->getItemURL(), $this->name, Icon::create('seminar'));
+ }
+
+
+ /**
+ * Returns a list of courses for the specified user.
+ * Permission levels may be supplied to limit the course list.
+ *
+ * @param string $user_id The ID of the user whose courses shall be retrieved.
+ *
+ * @param string[] $perms The permission levels of the user that shall be
+ * regarded when retrieving courses.
+ *
+ * @param bool $with_deputies Whether to include courses where the user is
+ * a deputy (true) or not (false). Defaults to true.
+ *
+ * @returns Course[] A list of courses.
+ */
+ public static function findByUser($user_id, $perms = [], $with_deputies = true)
+ {
+ if (!$user_id) {
+ return [];
+ }
+
+ $db = DBManager::get();
+ $sql = "SELECT `seminar_id`
+ FROM `seminar_user`
+ WHERE `user_id` = :user_id";
+ $sql_params = ['user_id' => $user_id];
+ if (is_array($perms) && count($perms)) {
+ $sql .= ' AND `status` IN (:perms)';
+ $sql_params['perms'] = $perms;
+ }
+ $seminar_ids = $db->fetchFirst($sql, $sql_params);
+ if (Config::get()->DEPUTIES_ENABLE && $with_deputies) {
+ $sql = 'SELECT range_id FROM `deputies` WHERE `deputies`.`user_id` = :user_id';
+ $seminar_ids = array_merge($seminar_ids, $db->fetchFirst($sql, $sql_params));
+ }
+ return Course::findBySQL(
+ "LEFT JOIN semester_courses ON (semester_courses.course_id = seminare.Seminar_id)
+ WHERE Seminar_id IN (?)
+ ORDER BY IF(semester_courses.semester_id IS NULL, 1, 0) DESC, start_time DESC, Name ASC",
+ [$seminar_ids]
+ );
+ }
+
+ /**
+ * Returns whether this course is a studygroup
+ * @return bool
+ */
+ public function isStudygroup()
+ {
+ return in_array($this->status, studygroup_sem_types());
+ }
+
+ /**
+ *
+ */
+ public function setDefaultTools()
+ {
+ $this->tools = [];
+ foreach (array_values($this->getSemClass()->getActivatedModuleObjects()) as $pos => $module) {
+ $this->tools[] = ToolActivation::create(
+ [
+ 'plugin_id' => $module->getPluginId(),
+ 'range_type' => 'course',
+ 'range_id' => $this->id,
+ 'position' => $pos
+ ]
+ );
+ }
+ }
+
+ /**
+ * @param $name string name of tool / plugin
+ * @return bool
+ */
+ public function isToolActive($name)
+ {
+ $plugin = PluginEngine::getPlugin($name);
+ return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId());
+ }
+
+ /**
+ * returns all activated plugins/modules for this course
+ * @return StudipModule[]
+ */
+ public function getActivatedTools()
+ {
+ return array_filter($this->tools->getStudipModule());
+ }
+}