diff options
| author | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
|---|---|---|
| committer | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
| commit | 4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch) | |
| tree | 5c07151ae61276d334e88f6309c30d439a85c12e /lib/classes/Seminar.php | |
| parent | da0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff) | |
| parent | 97a188592c679890a25c37ab78463add76a52ff7 (diff) | |
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/classes/Seminar.php')
| -rw-r--r-- | lib/classes/Seminar.php | 2439 |
1 files changed, 2439 insertions, 0 deletions
diff --git a/lib/classes/Seminar.php b/lib/classes/Seminar.php new file mode 100644 index 0000000..0fccdbc --- /dev/null +++ b/lib/classes/Seminar.php @@ -0,0 +1,2439 @@ +<? +# 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; + } +} |
