Select Git revision
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
-
David Siegfried authored
Closes #3592 Merge request studip/studip!2485
David Siegfried authoredCloses #3592 Merge request studip/studip!2485
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CourseSet.class.php 37.86 KiB
<?php
/**
* CourseSet.class.php
*
* Represents groups of Stud.IP courses that have common rules for admission.
*
* 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 Thomas Hackl <thomas.hackl@uni-passau.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
* @category Stud.IP
*/
class CourseSet
{
// --- ATTRIBUTES ---
/**
* Admission rules that are applied to the courses belonging to this set.
*/
protected $admissionRules = [];
/**
* Seat distribution algorithm.
*/
protected $algorithm = null;
/**
* IDs of courses that are aggregated into this set. The array is in the
* form ($courseId1 => true, $courseId2 => true).
*/
protected $courses = [];
/**
* Has the seat distribution algorithm already been executed?
*/
protected $hasAlgorithmRun = false;
/**
* Unique identifier for this set.
*/
protected $id = '';
/**
* Some extensive descriptional text for informing confused students.
*/
protected $infoText = '';
/**
* Which Stud.IP institute does the course set belong to?
*/
protected $institutes = [];
/**
* Some display name for this course set.
*/
protected $name = '';
/**
* Is the course set only visible for the creator?
*/
protected $private = false;
/**
* Who owns this course set?
*/
protected $user_id = false;
/*
* Lists of users who are treated differently on seat distribution
*/
protected $userlists = [];
// --- OPERATIONS ---
public function __construct($setId='') {
$this->id = $setId;
$this->name = _("Anmeldeset");
$this->algorithm = new RandomAlgorithm();
// Autoload admission rules.
AdmissionRule::getAvailableAdmissionRules();
// Define autoload function for admission rules.
spl_autoload_register(['UserFilterField', 'getAvailableFilterFields']);
if ($setId) {
$this->load();
}
}
/**
* Adds the given admission rule to the list of rules for the course set.
*
* @param AdmissionRule rule
* @return CourseSet
*/
public function addAdmissionRule($rule)
{
$this->admissionRules[$rule->getId()] = $rule;
return $this;
}
/**
* Adds the course with the given ID to the course set.
*
* @param String courseId
* @return CourseSet
*/
public function addCourse($courseId)
{
$this->courses[$courseId] = true;
return $this;
}
/**
* Adds a new institute ID.
*
* @param String newId
* @return CourseSet
*/
public function addInstitute($newId) {
$this->institutes[$newId] = true;
return $this;
}
/**
* Adds several institute IDs to the existing institute assignments.
*
* @param Array newIds
* @return CourseSet
*/
public function addInstitutes($newIds) {
foreach ($newIds as $newId) {
$this->addInstitute($newId);
}
return $this;
}
/**
* Adds a UserList to the course set. The list contains several users and a
* factor that changes seat distribution chances for these users;
*
* @param String listId
* @return CourseSet
*/
public function addUserList($listId)
{
$this->userlists[$listId] = true;
return $this;
}
/**
* Is the given user allowed to register as participant in the given
* course according to the rules of this course set?
*
* @param String userId
* @param String courseId
* @return Array Optional error messages from rules if something went wrong.
*/
public function checkAdmission($userId, $courseId) {
$errors = [];
foreach ($this->admissionRules as $rule) {
// All rules must be fulfilled.
$ruleCheck = $rule->ruleApplies($userId, $courseId);
if ($ruleCheck) {
$errors = array_merge($errors, $ruleCheck);
}
}
return $errors;
}
/**
* Removes all admission rules at once.
*
* @return CourseSet
*/
public function clearAdmissionRules() {
$this->admissionRules = [];
return $this;
}
/**
* Deletes the course set and all associated data.
*/
public function delete() {
NotificationCenter::postNotification('CourseSetWillDelete', $this->id, $GLOBALS['user']->id);
// Delete institute associations.
$stmt = DBManager::get()->prepare("DELETE FROM `courseset_institute`
WHERE `set_id`=?");
$stmt->execute([$this->id]);
// Delete course associations.
$stmt = DBManager::get()->prepare("DELETE FROM `seminar_courseset`
WHERE `set_id`=?");
$stmt->execute([$this->id]);
// Delete all rules...
foreach ($this->admissionRules as $rule) {
$rule->delete();
}
// ... and their association to the current course set.
$stmt = DBManager::get()->prepare("DELETE FROM `courseset_rule`
WHERE `set_id`=?");
$stmt->execute([$this->id]);
// Delete associations to user lists.
$stmt = DBManager::get()->prepare("DELETE FROM `courseset_factorlist`
WHERE `set_id`=?");
$stmt->execute([$this->id]);
// Delete course set data.
$stmt = DBManager::get()->prepare("DELETE FROM `coursesets`
WHERE `set_id`=?");
$stmt->execute([$this->id]);
/*
* Delete waiting lists
*/
foreach ($this->courses as $id => $assigned) {
AdmissionApplication::deleteBySQL("status='awaiting' AND seminar_id=?", [$id]);
}
//Delete priorities
AdmissionPriority::unsetAllPriorities($this->getId());
NotificationCenter::postNotification('CourseSetDidDelete', $this->id, $GLOBALS['user']->id);
}
/**
* Starts the seat distribution algorithm.
*
* @return CourseSet
*/
public function distributeSeats() {
if ($this->algorithm) {
// Call pre-distribution hooks on all assigned rules.
foreach ($this->admissionRules as &$rule) {
$rule->beforeSeatDistribution($this);
}
$this->algorithm->run($this);
// Mark as "seats distributed".
$this->setAlgorithmRun(true);
// Call post-distribution hooks on all assigned rules.
foreach ($this->admissionRules as &$rule) {
$rule->afterSeatDistribution($this);
}
AdmissionPriority::unsetAllPriorities($this->getId());
}
}
public function setAlgorithmRun($state)
{
NotificationCenter::postNotification('CourseSetAlgorithmWillStart', $state, $this->getId());
$this->hasAlgorithmRun = (bool)$state;
$db = DbManager::get();
$ok = $db->execute("UPDATE coursesets SET algorithm_run = ? WHERE set_id = ?", [$this->hasAlgorithmRun, $this->getId()]);
if ($ok) {
NotificationCenter::postNotification('CourseSetAlgorithmDidStart', $state, $this->getId());
}
return $ok;
}
/**
* returns true if the set allows only a limited number of places
*
* @return boolean
*/
public function isSeatDistributionEnabled()
{
return $this->getSeatDistributionTime() !== null;
}
/**
* returns timestamp of distribution time or null if no distribution time available
* may return 0 if first-come-first-serve is enabled
*
* @return integer|null timestamp of distribution
*/
public function getSeatDistributionTime()
{
$pr_admission = $this->getAdmissionRule('ParticipantRestrictedAdmission');
if ($pr_admission) {
return $pr_admission->getDistributionTime();
}
}
/**
* Get all admission rules belonging to the course set.
*
* @return AdmissionRule[]
*/
public function getAdmissionRules()
{
return $this->admissionRules;
}
public function getAdmissionRule($class_name)
{
$result = array_filter($this->getAdmissionRules(), function($r) use ($class_name) {
return $r instanceof $class_name;}
);
return array_pop($result);
}
/**
* check if course set has given admission rule
*
* @param string $rule name of AdmissionRule class
* @return boolean
*/
public function hasAdmissionRule($rule)
{
return is_object($this->getAdmissionRule($rule));
}
/**
* Get the currently used distribution algorithm.
*
* @return AdmissionAlgorithm
*/
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* How many users will be allowed to register according to the defined
* rules? This can help in estimating whether the combination of the
* defined rules makes sense.
*
* @return int
*/
public function getAllowedUserCount()
{
$users = [];
foreach ($this->admissionRules as $rule) {
$users = array_merge($users, $rule->getAffectedUsers());
}
return sizeof($users);
}
/**
* Gets the course IDs belonging to the course set.
*
* @return Array
*/
public function getCourses()
{
return array_keys($this->courses);
}
/**
* Gets all courses belonging to the given course set ID.
*
* @param String $courseSetId
* @return Array
*/
public static function getCoursesByCourseSetId($courseSetId)
{
$query = "SELECT `seminar_id`
FROM `seminar_courseset`
WHERE `set_id` = ?";
$stmt = DBManager::get()->prepare($query);
$stmt->execute([$courseSetId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Gets all course sets belonging to the given institute ID.
*
* @param String $instituteId
* @return Array
*/
public static function getCoursesetsByInstituteId($instituteId, $filter = []) {
$query = "SELECT DISTINCT ci.*
FROM `courseset_institute` ci
JOIN `coursesets` c ON (ci.`set_id`=c.`set_id`)
LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id
LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id
LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id
WHERE ci.`institute_id`=?";
$parameters = [$instituteId];
if (!$GLOBALS['perm']->have_perm('admin')) {
$query .= " AND (c.`private`=0 OR c.`user_id`=?)";
$parameters[] = $GLOBALS['user']->id;
}
if ($filter['course_set_name']) {
$query .= " AND c.name LIKE ?";
$parameters[] = $filter['course_set_name'] . '%';
}
if (is_array($filter['rule_types']) && count($filter['rule_types'])) {
$query .= " AND cr.type IN (?)";
$parameters[] = $filter['rule_types'];
}
if ($filter['semester_id']) {
$query .= " AND s.start_time = ?";
$parameters[] = Semester::find($filter['semester_id'])->beginn;
}
if ($filter['course_set_chdate']) {
$query .= " AND c.chdate < ?";
$parameters[] = $filter['chdate'];
}
$query .= " ORDER BY c.name";
$stmt = DBManager::get()->prepare($query);
$stmt->execute($parameters);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Gets all global course sets
*
* @param array $filter
* @return Array
*/
public static function getGlobalCoursesets($filter = []) {
$query = "SELECT DISTINCT c.set_id
FROM coursesets c
LEFT JOIN courseset_institute ci ON ci.`set_id`=c.`set_id`
LEFT JOIN courseset_rule cr ON cr.set_id=ci.set_id
LEFT JOIN seminar_courseset sc ON c.set_id = sc.set_id
LEFT JOIN seminare s ON s.seminar_id = sc.seminar_id
WHERE ci.institute_id IS NULL";
$parameters = [];
$query .= " AND (c.`private`=0 OR c.`user_id`=?)";
$parameters[] = $GLOBALS['user']->id;
if ($filter['course_set_name']) {
$query .= " AND c.name LIKE ?";
$parameters[] = $filter['course_set_name'] . '%';
}
if (is_array($filter['rule_types']) && count($filter['rule_types'])) {
$query .= " AND cr.type IN (?)";
$parameters[] = $filter['rule_types'];
}
if ($filter['semester_id']) {
$query .= " AND s.start_time = ?";
$parameters[] = Semester::find($filter['semester_id'])->beginn;
}
if ($filter['course_set_chdate']) {
$query .= " AND c.chdate < ?";
$parameters[] = $filter['chdate'];
}
$query .= " ORDER BY c.name";
$stmt = DBManager::get()->prepare($query);
$stmt->execute($parameters);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get the identifier of the course set.
*
* @return String
*/
public function getId()
{
return $this->id;
}
/**
* Get the course set's info text.
*
* @return String
*/
public function getInfoText()
{
return $this->infoText;
}
/**
* Which institutes does the rule belong to?
*
* @return Array
*/
public function getInstituteIds() {
return $this->institutes;
}
public function isGlobal()
{
return count($this->institutes) == 0;
}
/**
* Gets this course set's display name.
*/
public function getName() {
return $this->name;
}
/**
* Returns the date of the last change.
* @return int
*/
public function getChdate()
{
return $this->chdate;
}
/**
* Retrieves the priorities given to the courses in this set.
*
* @return Array
*/
public function getPriorities()
{
return AdmissionPriority::getPriorities($this->id);
}
public function getNumApplicants()
{
return AdmissionPriority::getPrioritiesCount($this->id);
}
/**
* Is the current courseset private?
*
* @return bool
*/
public function getPrivate() {
return $this->private;
}
/**
* returns latest semester id from assigned courses
* if no courses are assigned current semester
*
* @return string id of semester
*/
public function getSemester() {
$db = DBManager::get();
$data = $db->fetchOne("
SELECT semester_data.*
FROM semester_data
INNER JOIN semester_courses ON (semester_data.semester_id = semester_courses.semester_id)
WHERE semester_courses.course_id IN (?)
ORDER BY semester_data.ende DESC
LIMIT 1
", [$this->getCourses()]);
if ($data) {
$semester = Semester::buildExisting($data);
} else {
$semester = $_SESSION['_default_sem'] ? Semester::find($_SESSION['_default_sem']) : Semester::findCurrent();
}
return $semester->id;
}
/**
* Gets the owner of this course set.
*/
public function getUserId() {
return $this->user_id;
}
public function setUserId($user_id) {
$this->user_id = $user_id;
return $this;
}
/**
* Gets the course sets the given course belongs to.
*
* @param String courseId
* @return CourseSet
*/
public static function getSetForCourse($courseId)
{
$stmt = DBManager::get()->prepare("SELECT `set_id`
FROM `seminar_courseset` WHERE `seminar_id`=?");
$stmt->execute([$courseId]);
$set_id = $stmt->fetchColumn();
if ($set_id) {
return new CourseSet($set_id);
}
return null;
}
/**
* Gets the course sets the given rule belongs to.
*
* @param String $rule_id
* @return CourseSet
*/
public static function getSetForRule($rule_id)
{
$set_id = DBManager::get()->fetchColumn("SELECT `set_id`
FROM `courseset_rule` WHERE `rule_id`=?", [$rule_id]);
if ($set_id) {
return new CourseSet($set_id);
}
return null;
}
/**
* Retrieves the lists of users that are considered specially in
* seat distribution.
*
* @return Array
*/
public function getUserLists()
{
return array_keys($this->userlists);
}
public function getUserFactorList()
{
$factored_users = [];
foreach ($this->getUserLists() as $ul_id) {
$user_list = new AdmissionUserList($ul_id);
$factored_users = array_merge($factored_users,
array_combine(array_keys($user_list->getUsers()),
array_fill(0, count($user_list->getUsers()), $user_list->getFactor())
)
);
}
return $factored_users;
}
/**
* Evaluates whether the seat distribution algorithm has already been
* executed on this course set.
*
* @return boolean True if algorithm has already run, otherwise false.
*/
public function hasAlgorithmRun() {
return $this->hasAlgorithmRun;
}
/**
* Helper function for loading data from DB.
*/
public function load() {
// Load basic data.
$stmt = DBManager::get()->prepare(
"SELECT * FROM `coursesets` WHERE set_id=? LIMIT 1");
$stmt->execute([$this->id]);
if ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
$this->name = $data['name'];
$this->infoText = $data['infotext'];
$this->hasAlgorithmRun = (bool)$data['algorithm_run'];
if ($data['algorithm']) {
if (class_exists($data['algorithm'])) {
$this->algorithm = new $data['algorithm']();
}
}
$this->private = (bool) $data['private'];
$this->user_id = $data['user_id'];
$this->chdate = $data['chdate'];
}
// Load institute assigments.
$stmt = DBManager::get()->prepare("
SELECT courseset_institute.institute_id
FROM `courseset_institute`
INNER JOIN Institute ON (courseset_institute.institute_id = Institute.Institut_id)
WHERE courseset_institute.set_id = ?
ORDER BY Institute.Name ASC
");
$stmt->execute([$this->id]);
$this->institutes = [];
while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
$this->institutes[$data['institute_id']] = true;
}
// Load courses.
$stmt = DBManager::get()->prepare(
"SELECT seminar_id FROM `seminar_courseset` WHERE set_id=?");
$stmt->execute([$this->id]);
$this->courses = [];
while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
$this->courses[$data['seminar_id']] = true;
}
// Load admission rules.
$stmt = DBManager::get()->prepare(
"SELECT * FROM `courseset_rule` WHERE set_id=?");
$stmt->execute([$this->id]);
$this->admissionRules = [];
while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (class_exists($data['type'])) {
$this->admissionRules[$data['rule_id']] =
new $data['type']($data['rule_id'], $this->id);
}
}
// Load assigned user lists.
$stmt = DBManager::get()->prepare("SELECT `factorlist_id`
FROM `courseset_factorlist` WHERE `set_id`=?");
$stmt->execute([$this->id]);
$this->userlists = [];
while ($current = $stmt->fetch(PDO::FETCH_ASSOC)) {
$this->userlists[$current['factorlist_id']] = true;
}
return $this;
}
/**
* Removes the course with the given ID from the set.
*
* @param String courseId
* @return CourseSet
*/
public function removeCourse($courseId)
{
unset($this->courses[$courseId]);
return $this;
}
/**
* Removes the rule with the given ID from the set.
*
* @param String ruleId
* @return CourseSet
*/
public function removeAdmissionRule($ruleId)
{
unset($this->admissionRules[$ruleId]);
return $this;
}
/**
* Removes the institute with the given ID from the set.
*
* @param String instituteId
* @return CourseSet
*/
public function removeInstitute($instituteId)
{
unset($this->institutes[$instituteId]);
return $this;
}
/**
* Removes the user list with the given ID from the set.
*
* @param String listId
* @return CourseSet
*/
public function removeUserlist($listId)
{
unset($this->userlists[$listId]);
return $this;
}
/**
* Adds several admission rules after clearing the existing rule
* assignments.
*
* @param Array newIds
* @return CourseSet
*/
public function setAdmissionRules($newRules) {
$this->admissionRules = [];
foreach ($newRules as $newRule) {
$rule = ObjectBuilder::build($newRule, 'AdmissionRule');
$this->addAdmissionRule($rule);
}
return $this;
}
/**
* Sets a seat distribution algorithm for this course set. This will only
* have an effect in conjunction with a TimedAdmission, as the algorithm
* needs a defined point in time where it will start.
*
* @param String newAlgorithm
* @return CourseSet
*/
public function setAlgorithm($newAlgorithm)
{
try {
$this->algorithm = new $newAlgorithm();
} catch (Exception $e) {
}
return $this;
}
/**
* Adds several course IDs after clearing the existing course
* assignments.
*
* @param Array newIds
* @return CourseSet
*/
public function setCourses($newIds) {
$this->courses = array_fill_keys($newIds, true);
return $this;
}
/**
* Adds several institute IDs after clearing the existing institute
* assignments.
*
* @param Array newIds
* @return CourseSet
*/
public function setInstitutes($newIds) {
$this->institutes = [];
$this->addInstitutes($newIds);
return $this;
}
/**
* Set the course set's info text.
*
* @return CourseSet
*/
public function setInfoText($newText)
{
$this->infoText = $newText;
return $this;
}
/* Sets a new name for this course set.
*
* @param String newName
* @return CourseSet
*/
public function setName($newName) {
$this->name = $newName;
return $this;
}
/**
* Set a new value for courseset privacy.
*
* @param bool $newPrivate
* @return CourseSet
*/
public function setPrivate($newPrivate) {
$this->private = $newPrivate;
return $this;
}
/**
* Adds several user list IDs after clearing the existing user list
* assignments.
*
* @param Array newIds
* @return CourseSet
*/
public function setUserlists($newIds) {
$this->userlists = [];
foreach ($newIds as $newId) {
$this->addUserlist($newId);
}
return $this;
}
public function store() {
// Generate new ID if course set doesn't exist in DB yet.
if (!$this->id) {
do {
$newid = md5(uniqid(get_class($this), true));
$db = DBManager::get()->query("SELECT `set_id`
FROM `coursesets` WHERE `set_id`='$newid'");
} while ($db->fetch());
$this->id = $newid;
}
if (!$this->user_id) {
$this->user_id = $GLOBALS['user']->id;
}
if ($this->isSeatDistributionEnabled()) {
if (!$this->getAlgorithm()) {
$algorithm = new RandomAlgorithm();
$this->setAlgorithm($algorithm);
}
if (!$this->getSeatDistributionTime()) {
$this->setAlgorithmRun(true);
//Delete priorities
AdmissionPriority::unsetAllPriorities($this->getId());
}
if ($this->getSeatDistributionTime() > time()) {
$this->setAlgorithmRun(false);
}
}
// Store basic data.
$stmt = DBManager::get()->prepare("INSERT INTO `coursesets`
(`set_id`, `user_id`, `name`, `infotext`, `algorithm`, `algorithm_run`,
`private`, `mkdate`, `chdate`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE
`name`=VALUES(`name`), `infotext`=VALUES(`infotext`),
`algorithm`=VALUES(`algorithm`), `algorithm_run`=VALUES(`algorithm_run`), `private`=VALUES(`private`),
`chdate`=VALUES(`chdate`)");
$stmt->execute([$this->id, $this->user_id, $this->name, $this->infoText,
get_class($this->algorithm), $this->hasAlgorithmRun(), intval($this->private), time(), time()]);
// Delete removed institute assignments from database.
DBManager::get()->exec("DELETE FROM `courseset_institute`
WHERE `set_id`='".$this->id."' AND `institute_id` NOT IN ('".
implode("', '", array_keys($this->institutes))."')");
// Store associated institute IDs.
foreach ($this->institutes as $institute => $associated) {
$stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_institute`
(`set_id`, `institute_id`, `mkdate`)
VALUES (?, ?, ?)");
$stmt->execute([$this->id, $institute, time()]);
}
// log removed course assignments.
DBManager::get()->fetchAll("SELECT seminar_id,set_id FROM `seminar_courseset`
WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : ''],
function ($row) {
StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'],
null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id']));
//Delete priorities
AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']);
Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate();
});
//removed course assignments
DBManager::get()->execute("DELETE FROM `seminar_courseset`
WHERE `set_id` = ? AND `seminar_id` NOT IN (?)", [$this->id, count($this->courses) ? array_keys($this->courses) : '']);
//log removing other associations
DBManager::get()->execute("SELECT seminar_id,set_id FROM `seminar_courseset`
WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)],
function ($row) {
StudipLog::log('SEM_CHANGED_ACCESS', $row['seminar_id'],
null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $row['set_id']));
//Delete priorities
AdmissionPriority::unsetAllPrioritiesForCourse($row['seminar_id']);
Course::buildExisting(['seminar_id' => $row['seminar_id']])->triggerChdate();
});
//Delete other associations, only one set possible
DBManager::get()->execute("DELETE FROM `seminar_courseset`
WHERE `set_id` <> ? AND `seminar_id` IN (?)", [$this->id, array_keys($this->courses)]);
// Store associated course IDs.
foreach ($this->courses as $course => $associated) {
$stmt = DBManager::get()->prepare("INSERT IGNORE INTO `seminar_courseset`
(`set_id`, `seminar_id`, `mkdate`)
VALUES (?, ?, ?)");
$stmt->execute([$this->id, $course, time()]);
if ($stmt->rowCount()) {
StudipLog::log('SEM_CHANGED_ACCESS', $course,
null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $this->id));
Course::buildExisting(['seminar_id' => $course])->triggerChdate();
}
}
// Delete removed user list assignments from database.
DBManager::get()->exec("DELETE FROM `courseset_factorlist`
WHERE `set_id`='".$this->id."' AND `factorlist_id` NOT IN ('".
implode("', '", array_keys($this->userlists))."')");
// Store associated user list IDs.
foreach ($this->userlists as $list => $associated) {
$stmt = DBManager::get()->prepare("INSERT IGNORE INTO `courseset_factorlist`
(`set_id`, `factorlist_id`, `mkdate`)
VALUES (?, ?, ?)");
$stmt->execute([$this->id, $list, time()]);
}
// Delete removed admission rules from database.
$stmt = DBManager::get()->query("SELECT `rule_id`, `type` FROM `courseset_rule`
WHERE `set_id`='".$this->id."' AND `rule_id` NOT IN ('".
implode("', '", array_keys($this->admissionRules))."')");
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($data as $ruleData) {
$rule = new $ruleData['type']($ruleData['rule_id']);
$rule->delete();
}
// Store all rules.
foreach ($this->admissionRules as $rule) {
// Store each rule...
$rule->store();
// ... and its connection to the current course set.
$stmt = DBManager::get()->prepare("INSERT INTO `courseset_rule`
(`set_id`, `rule_id`, `type`, `mkdate`)
VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE
`type`=VALUES(`type`)");
$stmt->execute([$this->id, $rule->getId(), get_class($rule), time()]);
}
//fix free access courses
if (count($this->courses)) {
DBManager::get()->execute("UPDATE seminare SET Lesezugriff=1,Schreibzugriff=1 WHERE seminar_id IN(?)", [array_keys($this->courses)]);
}
//create general log
$this->log_store();
}
/**
* Generates a general log entry if the CourseSet rules were changed.
*/
private function log_store()
{
$text = '';
$rule_counter = 1;
foreach ($this->getAdmissionRules() as $rule) {
$rule_text = strip_tags(kill_format($rule->toString()));
$semicolon = ($rule_counter < count($this->getAdmissionRules()) ? '; ' : '');
$text .= '#' . $rule_counter . ' => ' . $rule_text . $semicolon;
$rule_counter++;
}
$courses = $this->getCourses();
foreach ($courses as $course_id) {
StudipLog::log(
'SEM_CHANGED_ACCESS',
$course_id,
NULL,
$text,
sprintf('Anmeldeset: %s (%s)', strip_tags(kill_format(($this->name))), $this->id)
);
}
}
/**
* A textual description of the current rule.
*
* @param bool short Show only short info without overview of assigned
* courses and institutes.
* @return String
*/
public function toString($short=false) {
$tpl = $GLOBALS['template_factory']->open('admission/courseset/info');
$tpl->set_attribute('courseset', $this);
$institutes = [];
if (!$short) {
$institutes = Institute::findAndMapMany(function($i) {return $i->name;}, array_keys($this->institutes), 'ORDER BY Name');
$tpl->set_attribute('institutes', $institutes);
}
if (!$short || $this->hasAdmissionRule('LimitedAdmission')) {
$courses = Course::findAndMapMany(function($c) {
return [
'id' => $c->id,
'name' => $c->getFullname('number-name-semester'),
'visible' => $c->visible
];
},
array_keys($this->courses),
'ORDER BY start_time,VeranstaltungsNummer,Name');
if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) {
$courses = array_filter($courses,
function ($c) {
return $c['visible'];
}
);
}
$tpl->set_attribute('is_limited', $this->hasAdmissionRule('LimitedAdmission'));
$tpl->set_attribute('courses', $courses);
}
$tpl->set_attribute('short', $short);
return $tpl->render();
}
public function __toString() {
return $this->toString();
}
/**
* is user with given user id allowed to assign/unassign given course to courseset
*
* @param string $user_id
* @param string $course_id
* @return boolean
*/
public function isUserAllowedToAssignCourse($user_id, $course_id)
{
global $perm;
$is_dozent = $perm->have_studip_perm('tutor', $course_id, $user_id);
$is_admin = $perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN);
$is_private = $this->getPrivate();
$is_my_own = $this->getUserId() == $user_id;
$is_correct_institute = $this->isGlobal() || isset($this->institutes[Course::find($course_id)->institut_id]);
return $is_dozent && $is_correct_institute && ($is_my_own || $is_admin || !$is_private || $this->isGlobal()) || $perm->have_perm('root', $user_id);
}
/**
* is user with given user id allowed to edit or delete the courseset
*
* @param string $user_id
* @return boolean
*/
public function isUserAllowedToEdit($user_id)
{
global $perm;
if ($this->getUserId() == '') {
return false;
}
if ($perm->have_perm('root', $user_id) || $this->getUserId() == $user_id) {
return true;
}
if (count($this->institutes) == 0 && count($this->courses) == 1 && $perm->have_studip_perm('tutor', current($this->getCourses()), $user_id)) {
return true;
}
if ($perm->have_perm('admin', $user_id) || ($perm->have_perm('dozent', $user_id) && Config::get()->ALLOW_DOZENT_COURSESET_ADMIN)) {
foreach (array_keys($this->getInstituteIds()) as $one) {
if ($perm->have_studip_perm('dozent', $one, $user_id)) {
return true;
}
}
}
return false;
}
/**
* checks if given rule is allowed to be added to current set of rules
*
* @param AdmissionRule|string $admission_rule
* @return boolean
*/
public function isAdmissionRuleAllowed($admission_rule)
{
foreach ($this->getAdmissionRules() as $one) {
if (!$one->isCombinationAllowed($admission_rule)) {
return false;
}
}
return true;
}
public static function getGlobalLockedAdmissionSetId()
{
$db = DBManager::get();
$locked_set_id = $db->fetchColumn("SELECT cr.set_id FROM courseset_rule cr
INNER JOIN coursesets USING(set_id)
WHERE type='LockedAdmission' and private=1 and user_id='' LIMIT 1");
if (!$locked_set_id) {
$cs_insert = $db->prepare("INSERT INTO coursesets (set_id,user_id,name,infotext,algorithm,private,mkdate,chdate)
VALUES (?,?,?,?,'',?,?,?)");
$cs_r_insert = $db->prepare("INSERT INTO courseset_rule (set_id,rule_id,type,mkdate) VALUES (?,?,?,UNIX_TIMESTAMP())");
$locked_insert = $db->prepare("INSERT INTO lockedadmissions (rule_id,message,mkdate,chdate) VALUES (?,'Die Anmeldung ist gesperrt',UNIX_TIMESTAMP(),UNIX_TIMESTAMP())");
$locked_set_id = md5(uniqid('coursesets',1));
$name = 'Anmeldung gesperrt (global)';
$cs_insert->execute([$locked_set_id,'',$name,'',1,time(),time()]);
$locked_rule_id = md5(uniqid('lockedadmissions',1));
$locked_insert->execute([$locked_rule_id]);
$cs_r_insert->execute([$locked_set_id,$locked_rule_id,'LockedAdmission']);
}
return $locked_set_id;
}
public static function addCourseToSet($set_id, $course_id)
{
$db = DBManager::get();
$ok = $db->execute("INSERT IGNORE INTO seminar_courseset (set_id,seminar_id,mkdate) VALUES (?,?,UNIX_TIMESTAMP())", [$set_id, $course_id]);
if ($ok) {
StudipLog::log('SEM_CHANGED_ACCESS', $course_id,
null, 'Zuordnung zu Anmeldeset', sprintf('Anmeldeset: %s', $set_id));
$course = Course::find($course_id);
if ($course) {
$course->chdate = time();
$course->lesezugriff = 1;
$course->schreibzugriff = 1;
$course->store();
}
}
return $ok;
}
public static function removeCourseFromSet($set_id, $course_id)
{
$db = DBManager::get();
$ok = $db->execute("DELETE FROM seminar_courseset WHERE set_id=? AND seminar_id=? LIMIT 1", [$set_id, $course_id]);
if ($ok) {
StudipLog::log('SEM_CHANGED_ACCESS', $course_id,
null, 'Entfernung von Anmeldeset', sprintf('Anmeldeset: %s', $set_id));
//Delete priorities
AdmissionPriority::unsetAllPrioritiesForCourse($course_id);
Course::buildExisting(['seminar_id' => $course_id])->triggerChdate();
}
return $ok;
}
public function __clone()
{
$this->courses = [];
$this->id = null;
$this->user_id = null;
$cloned_rules = [];
foreach ($this->admissionRules as $key => $rule) {
$dolly = clone $rule;
$cloned_rules[$dolly->id] = $dolly;
}
$this->admissionRules = $cloned_rules;
}
} /* end of class CourseSet */