Select Git revision
StudipStudyArea.class.php
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
StudipStudyArea.class.php 16.75 KiB
<?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 sem_tree_id database column
* @property string id alias column for sem_tree_id
* @property string parent_id database column
* @property string priority database column
* @property string info database column
* @property string name database column
* @property string type database column
* @property SimpleORMapCollection _children has_many StudipStudyArea
* @property Institute institute belongs_to Institute
* @property StudipStudyArea _parent belongs_to StudipStudyArea
* @property SimpleORMapCollection 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
{
if ($semester_id !== 'all') {
$query = "SELECT COUNT(DISTINCT t.`seminar_id`)
FROM `seminar_sem_tree` t
JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`)
LEFT JOIN `semester_courses` sc ON (t.`seminar_id` = sc.`course_id`)
WHERE t.`sem_tree_id` IN (:ids)
AND (
sc.`semester_id` = :semester
OR sc.`semester_id` IS NULL
)";
$parameters = [
'ids' => $with_children ? $this->getDescendantIds() : [$this->id],
'semester' => $semester_id
];
} else {
$query = "SELECT COUNT(DISTINCT t.`seminar_id`)
FROM `seminar_sem_tree` t
JOIN `seminare` s ON (s.`Seminar_id` = t.`seminar_id`)
WHERE `sem_tree_id` IN (:ids)";
$parameters = ['ids' => $with_children ? $this->getDescendantIds() : [$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
{
if ($semester_id !== 'all') {
$query = "SELECT DISTINCT s.*
FROM `seminare` s
JOIN `seminar_sem_tree` t ON (t.`seminar_id` = s.`Seminar_id`)
LEFT JOIN `semester_courses` sem ON (sem.`course_id` = s.`Seminar_id`)
WHERE t.`sem_tree_id` IN (:ids)
AND (
sem.`semester_id` = :semester
OR sem.`semester_id` IS NULL
)";
$parameters = [
'ids' => $with_children ? $this->getDescendantIds() : [$this->id],
'semester' => $semester_id
];
} else {
$query = "SELECT DISTINCT s.*
FROM `seminare` s
JOIN `seminar_sem_tree` t ON (t.`seminar_id` = s.`Seminar_id`)
WHERE t.`sem_tree_id` IN (:ids)";
$parameters = ['ids' => $with_children ? $this->getDescendantIds() : [$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;
}
}