diff options
Diffstat (limited to 'lib/models/StudipStudyArea.php')
| -rw-r--r-- | lib/models/StudipStudyArea.php | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/lib/models/StudipStudyArea.php b/lib/models/StudipStudyArea.php new file mode 100644 index 0000000..8da8e16 --- /dev/null +++ b/lib/models/StudipStudyArea.php @@ -0,0 +1,637 @@ +<?php +/** + * Studienbereich... TODO + * + * Copyright (C) 2008 - Marcus Lunzenauer <mlunzena@uos.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * @package studip + * + * @author mlunzena + * @author André Noack <noack@data-quest.de> + * @copyright (c) Authors + * + * @property string $id alias column for sem_tree_id + * @property string $sem_tree_id database column + * @property string $parent_id database column + * @property int $priority database column + * @property string $info database column + * @property string $name database column + * @property string|null $studip_object_id database column + * @property int $type database column + * @property int|null $mkdate database column + * @property int|null $chdate database column + * @property SimpleORMapCollection|StudipStudyArea[] $_children has_many StudipStudyArea + * @property StudipStudyArea $_parent belongs_to StudipStudyArea + * @property SimpleORMapCollection|Course[] $courses has_and_belongs_to_many Course + */ + +class StudipStudyArea extends SimpleORMap implements StudipTreeNode +{ + use StudipTreeNodeCachableTrait; + + /** + * This constant represents the key of the root area. + */ + const ROOT = 'root'; + + protected static function configure($config = []) + { + $config['db_table'] = 'sem_tree'; + $config['has_many']['_children'] = [ + 'class_name' => StudipStudyArea::class, + 'assoc_foreign_key' => 'parent_id', + 'assoc_func' => 'findByParent', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_and_belongs_to_many']['courses'] = [ + 'class_name' => Course::class, + 'thru_table' => 'seminar_sem_tree', + ]; + $config['belongs_to']['_parent'] = [ + 'class_name' => StudipStudyArea::class, + 'foreign_key' => 'parent_id', + ]; + + $config = self::registerCachableCallbacks($config); + + parent::configure($config); + } + + /** + * This is required, if the nodes are added backwards + */ + public $required_children = []; + + /** + * Returns the children of the study area with the specified ID. + */ + public static function findByParent($parent_id) + { + return self::findByparent_id($parent_id, "ORDER BY priority,name"); + } + + /** + * Returns the study area with the specified ID. + */ + public static function find($id) + { + + $result = NULL; + + if ($id === self::ROOT) { + $result = self::getRootArea(); + } + + else { + $result = parent::find($id); + } + + return $result; + } + + /** + * Get a string representation of this study area. + */ + public function __toString() + { + return $this->id; + } + + + /** + * Get the comment of this study area. + */ + public function getInfo() + { + return $this->content['info']; + } + + + /** + * Set the comment of this study area. + */ + public function setInfo($info) + { + $this->content['info'] = (string) $info; + return $this; + } + + + /** + * Get the display name of this study area. + */ + public function getName(): string + { + return $this->content['name']; + } + + /** + * Set the display name of this study area. + */ + public function setName($name) + { + $this->content['name'] = (string) $name; + return $this; + } + + + /** + * Get the parent ID of this study area. + */ + public function getParentId() + { + return $this->content['parent_id']; + } + + + /** + * Get the parent. + */ + public function getParent() + { + $result = NULL; + if ($this->getID() !== self::ROOT) { + $result = $this->_parent; + } + return $result; + } + + + /** + * Set the parent of this study area. + */ + public function setParentId($parent_id) + { + $this->content['parent_id'] = (string) $parent_id; + $this->resetRelation('parent'); + return $this; + } + + /** + * get the type of this study area. + */ + public function getType() + { + return $this->content['type']; + } + + /** + * set the type of this study area. + */ + public function setType($type) + { + $this->content['type'] = (int) $type; + return $this; + } + + /** + * get the name of the type of this study area, see $SEM_TREE_TYPES in config.inc.php + * + * @return string + */ + public function getTypeName() + { + if(isset($GLOBALS['SEM_TREE_TYPES'][$this->getType()]['name'])){ + return $GLOBALS['SEM_TREE_TYPES'][$this->getType()]['name']; + } else { + return ''; + } + } + + /** + * is this study area editable, see $SEM_TREE_TYPES in config.inc.php + * + * @return bool + */ + public function isEditable() + { + if(isset($GLOBALS['SEM_TREE_TYPES'][$this->getType()]['editable'])){ + return (bool)$GLOBALS['SEM_TREE_TYPES'][$this->getType()]['editable']; + } else { + return false; + } + } + + /** + * is this study area hidden, see $SEM_TREE_TYPES in config.inc.php + * + * @return bool + */ + public function isHidden() + { + if (isset($GLOBALS['SEM_TREE_TYPES'][$this->getType()]['hidden'])) { + return (bool) $GLOBALS['SEM_TREE_TYPES'][$this->getType()]['hidden']; + } else { + return false; + } + } + + /** + * Get the path along the sem_tree to this study area. + * + * @param string optional; TODO + * + * @return mixed TODO + */ + public function getPath($separator = NULL) + { + + $path = []; + + $area = $this; + while ($area) { + if ($area->getName() != '') { + $path[] = $area->getName(); + } + if ($area->getParentId() == self::ROOT) { + break; + } + $area = $area->getParent(); + } + + $path = array_reverse($path); + + return isset($separator) + ? join($separator, $path) + : $path; + } + + + /** + * Get the priority of this study area. + */ + public function getPriority() + { + return $this->content['priority']; + } + + + /** + * Set the priority of this study area. + */ + public function setPriority($priority) + { + $this->content['priority'] = (int) $priority; + return $this; + } + + + /** + * Returns the children of this study area. + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Returns1 TRUE if the area has children. + */ + public function hasChildren() + { + return sizeof($this->_children) > 0; + } + + + /** + * Returns TRUE if this area is the root. + */ + public function isRoot() + { + return $this->getId() === self::ROOT; + } + + + /** + * Returns TRUE if this area can be select. + */ + public function isAssignable() + { + $cfg = Config::GetInstance(); + $leaves_too = $cfg->getValue('SEM_TREE_ALLOW_BRANCH_ASSIGN'); + if ($leaves_too) { + return !$this->isRoot() && !$this->isHidden(); + } else { + return !$this->isRoot() && !$this->isHidden() && !$this->hasChildren(); + } + } + + /** + * is this study area considered a study modul?, see $SEM_TREE_TYPES in config.inc.php + * + * @return bool + */ + public function isModule() + { + return isset($GLOBALS['SEM_TREE_TYPES'][$this->getType()]['is_module']); + } + + /** + * Get an associative array of all study areas of a course. The array + * contains StudipStudyArea instances + * + * @param id the course's ID + * + * @return SimpleCollection a SimpleORMapCollection of that course's study areas + */ + public static function getStudyAreasForCourse($id) + { + $course = Course::find($id); + return $course ? $course->study_areas : new SimpleCollection(); + } + + + /** + * Returns the not really existing root study area. + * + * @return object the root study area object + */ + public static function getRootArea() + { + $root = new StudipStudyArea(); + $root->setID(self::ROOT); + $root->setName(Config::get()->UNI_NAME_CLEAN); + return $root; + } + + + /** + * Search for study areas whose name matches the given search term. + * + * @param string $searchTerm the seach term + * + * @return StudipStudyArea[] nodes + */ + public static function search($searchTerm) + { + return self::findBySql( + "name LIKE :searchTerm ORDER BY priority", + ['searchTerm' => "%$searchTerm%"] + ); + } + + /** + * Takes an array of StudyArea objects and produces the tree to the root node + * + * @param array $nodes All required nodes in the tree + * @return StudipStudyArea the root node + */ + public static function backwards($nodes) + { + // create the dummy root + $root = static::getRootArea(); + + $hashmap = []; + + $i = 0; + + // let the backwardssearch begin + while ($nodes && $i < 99) { + + //clear cache + $newNodes = []; + + //process nodes on this level + foreach ($nodes as $node) { + + // if we know the node already place there + if (isset($hashmap[$node->parent_id])) { + $cached = $hashmap[$node->parent_id]; + $cached->required_children[$node->id] = $node; + } else { + // if we have a node that is directly under root + if ($node->parent_id == $root->id) { + $root->required_children[$node->id] = $node; + } else { + // else store in hashmap and continue + $hashmap[$node->parent_id] = $node->_parent; + $node->_parent->required_children[$node->id] = $node; + $newNodes[$node->id] = $node->_parent; + } + } + } + $nodes = $newNodes; + $i++; + } + + // plant the tree + return $root; + } + + public static function getNode($id): StudipTreeNode + { + if ($id === 'root') { + return static::build([ + 'id' => 'root', + 'name' => Config::get()->UNI_NAME_CLEAN, + ]); + } + + return static::find($id); + } + + public static function getCourseNodes(string $course_id): array + { + return Course::find($course_id)->study_areas->getArrayCopy(); + } + + public function getDescription(): string + { + return $this->getInfo(); + } + + /** + * @see StudipTreeNode::getImage() + */ + public function getImage() + { + return null; + } + + public function hasChildNodes(): bool + { + return count($this->_children) > 0; + } + + /** + * @see StudipTreeNode::getChildNodes() + */ + public function getChildNodes(bool $onlyVisible = false): array + { + if ($onlyVisible) { + $visibleTypes = array_filter($GLOBALS['SEM_TREE_TYPES'], function ($t) { + return isset($t['hidden']) ? !$t['hidden'] : true; + }); + + return static::findBySQL( + "`parent_id` = :parent AND `type` IN (:types) ORDER BY `priority`, `name`", + ['parent' => $this->id, 'types' => $visibleTypes] + ); + } else { + return static::findByParent_id($this->id, "ORDER BY `priority`, `name`"); + } + } + + /** + * @see StudipTreeNode::countCourses() + */ + public function countCourses( + $semester_id = 'all', + $semclass = 0, + $with_children = false + ) :int + { + $query = "SELECT COUNT(DISTINCT t.`seminar_id`) FROM `seminar_sem_tree` t"; + + if ($semester_id !== 'all') { + $query .= " JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`) + LEFT JOIN `semester_courses` sc ON (t.`seminar_id` = sc.`course_id`) + WHERE sc.`semester_id` = :semester"; + $parameters = [ + 'semester' => $semester_id + ]; + } else { + $query .= " JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`)"; + $parameters = []; + } + + if ($with_children) { + $query .= " AND t.`sem_tree_id` IN (:ids)"; + $parameters['ids'] = array_merge([$this->id], $this->getDescendantIds()); + } else { + $query .= " AND t.`sem_tree_id` = :id"; + $parameters['id'] = $this->id; + } + + if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { + $query .= " AND s.`visible` = 1"; + } + + if ($semclass !== 0) { + $query .= " AND s.`status` IN (:types)"; + $parameters['types'] = array_map( + function ($type) { + return $type['id']; + }, + array_filter( + SemType::getTypes(), + function ($t) use ($semclass) { return $t['class'] === $semclass; } + ) + ); + } + + return $this->id === 'root' && !$with_children ? 0 : DBManager::get()->fetchColumn($query, $parameters); + } + + public function getCourses( + $semester_id = 'all', + $semclass = 0, + $searchterm = '', + $with_children = false, + array $courses = [] + ): array + { + $query = "SELECT DISTINCT s.* FROM `seminar_sem_tree` t"; + + if ($semester_id !== 'all') { + $query .= " JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`) + LEFT JOIN `semester_courses` sc ON (t.`seminar_id` = sc.`course_id`) + WHERE sc.`semester_id` = :semester"; + $parameters = [ + 'semester' => $semester_id + ]; + } else { + $query .= " JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`)"; + $parameters = []; + } + + if ($with_children) { + $query .= " AND t.`sem_tree_id` IN (:ids)"; + $parameters['ids'] = array_merge([$this->id], $this->getDescendantIds()); + } else { + $query .= " AND t.`sem_tree_id` = :id"; + $parameters['id'] = $this->id; + } + + if (!$GLOBALS['perm']->have_perm(Config::get()->SEM_VISIBILITY_PERM)) { + $query .= " AND s.`visible` = 1"; + } + + if ($semclass !== 0) { + $query .= " AND s.`status` IN (:types)"; + $parameters['types'] = array_map( + function ($type) { + return $type['id']; + }, + array_filter( + SemType::getTypes(), + function ($t) use ($semclass) { return $t['class'] === $semclass; } + ) + ); + } + + if ($searchterm) { + $query .= " AND s.`Name` LIKE :searchterm"; + $parameters['searchterm'] = '%' . trim($searchterm) . '%'; + } + + if ($courses) { + $query .= " AND t.`seminar_id` IN (:courses)"; + $parameters['courses'] = $courses; + } + + if (Config::get()->IMPORTANT_SEMNUMBER) { + $query .= " ORDER BY s.`start_time`, s.`VeranstaltungsNummer`, s.`Name`"; + } else { + $query .= " ORDER BY s.`start_time`, s.`Name`"; + } + + return DBManager::get()->fetchAll($query, $parameters, 'Course::buildExisting'); + } + + public function getAncestors(): array + { + $path = [ + [ + 'id' => $this->id, + 'name' => $this->getName(), + 'classname' => static::class + ] + ]; + + if ($this->parent_id) { + $path = array_merge($this->getNode($this->parent_id)->getAncestors(), $path); + } + + return $path; + } + + /** + * Constructs an index from the level hierarchy, This index is a number, + * containing the "depth" level and the priority on this level. For example, + * a node on level 2 with priority 3 will get an index of 23. + * + * @return int + */ + public function getIndex() + { + $level = 1; + $index = (string) $level . (string) $this->priority; + $current = $this; + + while ($current->getParent()) { + $current = $current->getParent(); + $index .= $level . $current->priority; + $level++; + } + + return $index; + } + +} |
