aboutsummaryrefslogtreecommitdiff
path: root/lib/models/StudipStudyArea.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/models/StudipStudyArea.php')
-rw-r--r--lib/models/StudipStudyArea.php637
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;
+ }
+
+}