Skip to content
Snippets Groups Projects
Commit 559ab723 authored by Thomas Hackl's avatar Thomas Hackl
Browse files

Resolve "Neuentwicklung Verzeichnisstrukturen"

Closes #1664, #2693, and #2692

Merge request studip/studip!1081
parent b7f0f8bc
No related branches found
No related tags found
No related merge requests found
Showing
with 849 additions and 99 deletions
...@@ -1042,6 +1042,14 @@ class Admin_CoursesController extends AuthenticatedController ...@@ -1042,6 +1042,14 @@ class Admin_CoursesController extends AuthenticatedController
'attributes' => ['data-dialog' => 'size=auto'], 'attributes' => ['data-dialog' => 'size=auto'],
'partial' => 'notice-action.php', 'partial' => 'notice-action.php',
], ],
21 => [
'name' => _('Mehrfachzuordnung von Studienbereichen'),
'title' => _('Mehrfachzuordnung von Studienbereichen'),
'url' => 'dispatch.php/admin/tree/batch_assign_semtree',
'dialogform' => true,
'multimode' => true,
'partial' => 'batch_assign_semtree.php'
],
]; ];
if (!$GLOBALS['perm']->have_perm('admin')) { if (!$GLOBALS['perm']->have_perm('admin')) {
......
<?php
class Admin_TreeController extends AuthenticatedController
{
public function rangetree_action()
{
$GLOBALS['perm']->check('root');
Navigation::activateItem('/admin/locations/range_tree');
PageLayout::setTitle(_('Einrichtungshierarchie bearbeiten'));
$this->startId = Request::get('node_id', 'RangeTreeNode_root');
$this->semester = Request::option('semester', Semester::findCurrent()->id);
$this->classname = RangeTreeNode::class;
$this->setupSidebar();
}
public function semtree_action()
{
$GLOBALS['perm']->check('root');
Navigation::activateItem('/admin/locations/sem_tree');
PageLayout::setTitle(_('Veranstaltungshierarchie bearbeiten'));
$this->startId = Request::get('node_id', 'StudipStudyArea_root');
$this->semester = Request::option('semester', Semester::findCurrent()->id);
$this->classname = StudipStudyArea::class;
$this->setupSidebar();
}
/**
* Edit the given node.
*
* @param string $class_id concatenated classname and node id
* @return void
*/
public function edit_action(string $class_id)
{
$GLOBALS['perm']->check('root');
PageLayout::setTitle(_('Eintrag bearbeiten'));
$data = $this->checkClassAndId($class_id);
$this->node = $data['classname']::getNode($data['id']);
$parent = $data['classname']::getNode($this->node->parent_id);
$this->treesearch = QuickSearch::get(
'parent_id',
new TreeSearch($data['classname'] === StudipStudyArea::class ? 'sem_tree_id' : 'range_tree_id')
)->withButton();
$this->treesearch->defaultValue($parent->id, $parent->getName());
if ($data['classname'] === RangeTreeNode::class) {
$this->instsearch = QuickSearch::get(
'studip_object_id',
new StandardSearch('Institut_id')
)->withButton();
if ($this->node->studip_object_id) {
$this->instsearch->defaultValue($this->node->studip_object_id, $this->node->institute->name);
}
}
$this->from = Request::get('from');
}
/**
* Create a new child node of the given parent.
*
* @param string $class_id concatenated classname and parent id
* @return void
*/
public function create_action(string $class_id)
{
$GLOBALS['perm']->check('root');
PageLayout::setTitle(_('Neuen Eintrag anlegen'));
$data = $this->checkClassAndId($class_id);
$this->node = new $data['classname']();
$this->node->parent_id = $data['id'];
$parent = $data['classname']::getNode($data['id']);
$this->treesearch = QuickSearch::get(
'parent_id',
new TreeSearch(get_class($this->node) === StudipStudyArea::class ? 'sem_tree_id' : 'range_tree_id')
)->withButton();
$this->treesearch->defaultValue($parent->id, $parent->getName());
$this->instsearch = QuickSearch::get(
'studip_object_id',
new StandardSearch('Institut_id')
)->withButton();
$this->from = Request::get('from');
}
/**
* Delete the given child node.
*
* @param string $class_id concatenated classname and node id
* @return void
*/
public function delete_action(string $class_id)
{
$GLOBALS['perm']->check('root');
$data = $this->checkClassAndId($class_id);
if (!Request::isPost()) {
throw new MethodNotAllowedException();
}
$node = $data['classname']::getNode($data['id']);
if ($node) {
$node->delete();
} else {
$this->set_status(404);
}
$this->render_nothing();
}
/**
* Store the given node.
*
* @param string $classname
* @param string $node_id
* @return void
*/
public function store_action(string $classname, string $node_id = '')
{
$GLOBALS['perm']->check('root');
CSRFProtection::verifyUnsafeRequest();
$node = new $classname($node_id);
$node->parent_id = Request::option('parent_id');
$parent = $classname::getNode(Request::option('parent_id'));
$maxprio = max(array_map(
function ($c) {
return $c->priority;
},
$parent->getChildNodes()
));
$node->priority = $maxprio + 1;
if (Request::option('studip_object_id')) {
$node->studip_object_id = Request::option('studip_object_id');
$node->name = '';
} else {
$node->name = Request::get('name');
}
if ($classname === StudipStudyArea::class) {
$node->info = Request::get('description');
$node->type = Request::int('type');
}
if ($node->store() !== false) {
Pagelayout::postSuccess(_('Die Daten wurden gespeichert.'));
} else {
Pagelayout::postError(_('Die Daten konnten nicht gespeichert werden.'));
}
$this->relocate(Request::get('from'));
}
public function sort_action($parent_id)
{
$GLOBALS['perm']->check('root');
$data = $this->checkClassAndId($parent_id);
$parent = $data['classname']::getNode($data['id']);
$children = $parent->getChildNodes();
$data = json_decode(Request::get('sorting'), true);
foreach ($children as $child) {
$child->priority = $data[$child->id];
$child->store();
}
$this->render_nothing();
}
/**
* (De-)assign several courses at once to a sem_tree node
* @return void
* @throws Exception
*/
public function batch_assign_semtree_action()
{
$GLOBALS['perm']->check('admin');
//set the page title with the area of Stud.IP:
PageLayout::setTitle(_('Veranstaltungszuordnungen bearbeiten'));
Navigation::activateItem('/browse/my_courses/list');
$GLOBALS['perm']->check('admin');
// check the assign_semtree array and extract the relevant course IDs:
$courseIds = Request::optionArray('assign_semtree');
$order = Config::get()->IMPORTANT_SEMNUMBER
? "ORDER BY `start_time` DESC, `VeranstaltungsNummer`, `Name`"
: "ORDER BY `start_time` DESC, `Name`";
$this->courses = Course::findMany($courseIds, $order);
$this->return = Request::get('return');
// check if at least one course was selected (this can only happen from admin courses overview):
if (!$courseIds) {
PageLayout::postWarning('Es wurde keine Veranstaltung gewählt.');
$this->relocate('admin/courses');
}
}
public function assign_courses_action($class_id)
{
$GLOBALS['perm']->check('root');
$data = $this->checkClassAndId($class_id);
$GLOBALS['perm']->check('admin');
$this->search = QuickSearch::get('courses[]', new StandardSearch('Seminar_id'))->withButton();
$this->node = $data['id'];
}
/**
* Store (de-)assignments from courses to sem_tree nodes.
* @return void
*/
public function do_batch_assign_action()
{
$GLOBALS['perm']->check('admin');
$astmt = DBManager::get()->prepare("INSERT IGNORE INTO `seminar_sem_tree` VALUES (:course, :node)");
$dstmt = DBManager::get()->prepare(
"DELETE FROM `seminar_sem_tree` WHERE `seminar_id` IN (:courses) AND `sem_tree_id` = :node");
$success = true;
// Add course assignments to the specified nodes.
foreach (Request::optionArray('courses') as $course) {
foreach (Request::optionArray('add_assignments') as $a) {
$success = $astmt->execute(['course' => $course, 'node' => $a]);
}
}
// Remove course assignments from the specified nodes.
foreach (Request::optionArray('delete_assignments') as $d) {
$success = $dstmt->execute(['courses' => Request::optionArray('courses'), 'node' => $d]);
}
if ($success) {
PageLayout::postSuccess(_('Die Zuordnungen wurden gespeichert.'));
} else {
PageLayout::postError(_('Die Zuordnungen konnten nicht vollständig gespeichert werden.'));
}
$this->relocate(Request::get('return', 'admin/courses'));
}
private function setupSidebar()
{
$sidebar = Sidebar::Get();
$semWidget = new SemesterSelectorWidget($this->url_for(''), 'semester');
$semWidget->includeAll(true);
$semWidget->setId('semester-selector');
$semWidget->setSelection($this->semester);
$sidebar->addWidget($semWidget);
if ($this->classname === StudipStudyArea::class) {
$sidebar->addWidget(new VueWidget('assign-widget'));
}
}
/**
* CHeck a combination of class name and ID for validity: is this a StudipTreeNode subclass?
* If yes, return the corresponding object.
*
* @param string $class_id class name and ID, separated by '_'
* @return mixed
*/
private function checkClassAndId($class_id)
{
list($classname, $id) = explode('_', $class_id);
if (is_a($classname, StudipTreeNode::class, true)) {
return [
'classname' => $classname,
'id' => $id
];
}
throw new InvalidArgumentException(
sprintf('The given class "%s" does not implement the StudipTreeNode interface!', $classname)
);
}
}
...@@ -27,108 +27,30 @@ class Search_CoursesController extends AuthenticatedController ...@@ -27,108 +27,30 @@ class Search_CoursesController extends AuthenticatedController
PageLayout::setHelpKeyword('Basis.VeranstaltungenAbonnieren'); PageLayout::setHelpKeyword('Basis.VeranstaltungenAbonnieren');
// activate navigation item $this->type = Request::option('type', 'semtree');
$nav_options = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS; $this->semester = Request::option('semester', Semester::findCurrent()->id);
URLHelper::bindLinkParam('option', $this->nav_option); $this->semClass = Request::int('semclass', 0);
if (!empty($nav_options[$this->nav_option])
&& Navigation::hasItem('/search/courses/' . $this->nav_option)) {
Navigation::activateItem('/search/courses/' . $this->nav_option);
} else {
URLHelper::removeLinkParam('option');
$level = Request::get('level', $_SESSION['sem_browse_data']['level'] ?? '');
$default_option = SemBrowse::getSearchOptionNavigation('sidebar');
if (!$level) {
PageLayout::setTitle(_($default_option->getTitle()));
$this->relocate($default_option->getURL());
} elseif ($level == 'f' && $nav_options['courses']['visible']) {
$course_option = SemBrowse::getSearchOptionNavigation('sidebar','courses');
PageLayout::setTitle(_($course_option->getTitle()));
Navigation::activateItem('/search/courses/semtree');
} elseif (($level == 'vv') && $nav_options['semtree']['visible']) {
$semtree_option = SemBrowse::getSearchOptionNavigation('sidebar','semtree');
PageLayout::setTitle(_($semtree_option->getTitle()));
Navigation::activateItem('/search/courses/semtree');
} elseif ($level == 'ev' && $nav_options['rangetree']['visible']) {
$rangetree_option = SemBrowse::getSearchOptionNavigation('sidebar','rangetree');
PageLayout::setTitle(_($rangetree_option->getTitle()));
Navigation::activateItem('/search/courses/rangetree');
} else {
throw new AccessDeniedException();
}
}
} }
public function index_action() public function index_action()
{ {
SemBrowse::transferSessionData(); $nodeClass = '';
$this->sem_browse_obj = new SemBrowse(); if (Request::option('type', 'semtree') === 'semtree') {
Navigation::activateItem('/search/courses/semtree');
if (!$GLOBALS['perm']->have_perm('root')) { $nodeClass = StudipStudyArea::class;
$this->sem_browse_obj->target_url = 'dispatch.php/course/details/'; $this->treeTitle = _('Studienbereiche');
$this->sem_browse_obj->target_id = 'sem_id'; $this->breadcrumbIcon = 'literature';
} else { $this->editUrl = $this->url_for('studyarea/edit');
$this->sem_browse_obj->target_url = 'seminar_main.php'; } else if (Request::option('type', 'semtree') === 'rangetree') {
$this->sem_browse_obj->target_id = 'auswahl'; Navigation::activateItem('/search/courses/rangetree');
} $nodeClass = RangeTreeNode::class;
$this->treeTitle = _('Einrichtungen');
$sidebar = Sidebar::get(); $this->breadcrumbIcon = 'institute';
$this->editUrl = $this->url_for('rangetree/edit');
// add search options to sidebar
$level = Request::get('level', $_SESSION['sem_browse_data']['level'] ?? '');
$widget = new OptionsWidget();
$widget->setTitle(_('Suche'));
//add a quicksearch input inside the widget
$search_content = $this->sem_browse_obj->getQuickSearchForm();
$search_element = new WidgetElement($search_content);
$widget->addElement($search_element);
$widget->addCheckbox(_('Erweiterte Suche anzeigen'),
$_SESSION['sem_browse_data']['cmd'] == "xts",
URLHelper::getURL('?level='.$level.'&cmd=xts&sset=0&option='),
URLHelper::getURL('?level='.$level.'&cmd=qs&sset=0&option='));
$sidebar->addWidget($widget);
SemBrowse::setSemesterSelector($this->url_for('search/courses/index'));
SemBrowse::setClassesSelector($this->url_for('search/courses/index'));
if ($this->sem_browse_obj->show_result
&& count($_SESSION['sem_browse_data']['search_result'])) {
$actions = new ActionsWidget();
$actions->addLink(_('Download des Ergebnisses'),
URLHelper::getURL('dispatch.php/search/courses/export_results'),
Icon::create('file-office', 'clickable'));
$sidebar->addWidget($actions);
$grouping = new OptionsWidget();
$grouping->setTitle(_('Suchergebnis gruppieren:'));
foreach ($this->sem_browse_obj->group_by_fields as $i => $field) {
$grouping->addRadioButton(
$field['name'],
URLHelper::getURL('?', ['group_by' => $i,
'keep_result_set' => 1]),
$_SESSION['sem_browse_data']['group_by'] == $i
);
}
$sidebar->addWidget($grouping);
}
// show information about course class if class was changed
$class = $GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']] ?? null;
if (is_object($class) && $class->countSeminars() > 0) {
if (trim($GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]['description'])) {
PageLayout::postInfo(sprintf(_('Gewählte Veranstaltungsklasse <i>%1s</i>: %2s'),
$GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]['name'],
$GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]['description']));
} else {
PageLayout::postInfo(sprintf(_('Gewählte Veranstaltungsklasse <i>%1s</i>.'),
$GLOBALS['SEM_CLASS'][$_SESSION['sem_browse_data']['show_class']]['name']));
}
} elseif ($_SESSION['sem_browse_data']['show_class'] != 'all') {
PageLayout::postInfo(_('Im gewählten Semester ist in dieser Veranstaltungsklasse keine Veranstaltung verfügbar. Bitte wählen Sie eine andere Veranstaltungsklasse oder ein anderes Semester!'));
} }
$this->startId = Request::option('node_id', $nodeClass . '_root');
$this->controller = $this; $this->setupSidebar();
} }
public function export_results_action() public function export_results_action()
...@@ -143,4 +65,33 @@ class Search_CoursesController extends AuthenticatedController ...@@ -143,4 +65,33 @@ class Search_CoursesController extends AuthenticatedController
} }
} }
private function setupSidebar()
{
$sidebar = Sidebar::Get();
$semWidget = new SemesterSelectorWidget($this->url_for(''), 'semester');
$semWidget->includeAll(false);
$semWidget->setId('semester-selector');
$semWidget->setSelection($this->semester);
$sidebar->addWidget($semWidget);
$classWidget = $sidebar->addWidget(new SelectWidget(
_('Veranstaltungskategorie'),
URLHelper::getURL('', ['type' => $this->type, 'semester' => $this->semester]),
'semclass'
));
$classWidget->addElement(new SelectElement(0, _('Alle')));
foreach (SemClass::getClasses() as $class) {
if (!$class['studygroup_mode']) {
$classWidget->addElement(new SelectElement(
$class['id'],
$class['name'],
$this->semClass == $class['id']
));
}
}
$sidebar->addWidget(new VueWidget('search-widget'));
$sidebar->addWidget(new VueWidget('export-widget'));
}
} }
<?php
/**
* treenode.php - Controller for editing tree nodes
*
* 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 <hackl@data-quest.de>
* @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 or later
* @category Stud.IP
* @since 5.4
*/
class StudyareaController extends AuthenticatedController
{
public function edit_action($id = '')
{
if ($id !== '') {
$object = StudipStudyArea::find($id);
} else {
$object = new StudipStudyArea();
}
PageLayout::setTitle($object->isNew() ? _('Studienbereich anlegen') : _('Studienbereich bearbeiten'));
$this->form = Studip\Forms\Form::fromSORM(
$object,
[
'legend' => $object->isNew()
? _('Neuer Studienbereich')
: sprintf(_('Studienbereich %s'), $object->name),
'text' => ['text' => ''],
'fields' => [
'name' => [
'label' => _('Name'),
'type' => 'text',
'required' => true
],
'info' => [
'label' => _('Beschreibung'),
'type' => 'textarea'
]
]
]
)->setURL($this->url_for('studyarea/store', $object->id));
}
}
<?php
class TreeController extends AuthenticatedController
{
public function export_csv_action()
{
if (!Request::isPost()) {
throw new MethodNotAllowedException();
}
$ids = explode(',', Request::get('courses', ''));
$courses = Course::findMany($ids);
$captions = [
_('Veranstaltungsnummer'),
_('Name'),
_('Semester'),
_('Zeiten'),
_('Lehrende')
];
$data = [];
foreach ($courses as $course) {
$sem = Seminar::getInstance($course->id);
$lecturers = SimpleCollection::createFromArray(
CourseMember::findByCourseAndStatus($course->id, 'dozent')
)->orderBy('position, nachname, vorname');
$lecturersSorted = array_map(
function ($l) {
return implode(', ', $l);
},
$lecturers->toArray('nachname vorname title_front title_rear')
);
$data[] = [
$course->veranstaltungsnummer,
$course->getFullname('type-number-name'),
$course->getTextualSemester(),
$sem->getDatesExport(),
implode(', ', $lecturersSorted)
];
}
$tmpname = md5(uniqid('ErgebnisVeranstaltungssuche'));
if (array_to_csv($data, $GLOBALS['TMP_PATH'] . '/' . $tmpname, $captions)) {
$this->render_text(FileManager::getDownloadURLForTemporaryFile(
$tmpname,
'veranstaltungssuche.csv'
));
} else {
$this->set_status(400, 'The csv could not be created.');
}
}
}
<?php
/**
* @var Course $course
*/
?>
<label>
<input name="assign_semtree[]" type="checkbox" value="<?= htmlReady($course->id) ?>">
</label>
...@@ -14,7 +14,8 @@ $colspan = 2 ...@@ -14,7 +14,8 @@ $colspan = 2
?> ?>
<? if (!empty($actions[$selected_action]['multimode'])) : ?> <? if (!empty($actions[$selected_action]['multimode'])) : ?>
<form action="<?= URLHelper::getLink($actions[$selected_action]['url']) ?>" method="post"> <form action="<?= URLHelper::getLink($actions[$selected_action]['url']) ?>" method="post"
<?= !empty($actions[$selected_action]['dialogform']) ? ' data-dialog="auto"' : '' ?>>
<? endif ?> <? endif ?>
<?= CSRFProtection::tokenTag() ?> <?= CSRFProtection::tokenTag() ?>
<table class="default course-admin"> <table class="default course-admin">
......
<form action="<?= $controller->link_for('admin/tree/do_batch_assign') ?>" method="post">
<section>
<?= $search->render() ?>
</section>
<input type="hidden" name="node" value="<?= htmlReady($node) ?>">
<footer data-dialog-button>
<?= Studip\Button::createAccept(_('Zuordnen'), 'assign') ?>
<?= Studip\Button::createCancel(_('Abbrechen'), 'cancel', ['data-dialog' => 'close']) ?>
</footer>
</form>
<form class="default" action="<?= $controller->link_for('admin/tree/do_batch_assign') ?>" method="post">
<fieldset>
<legend><?= _('Studienbereichszuordnungen der ausgewählten Veranstaltungen bearbeiten') ?></legend>
<div data-studip-tree>
<studip-tree start-id="StudipStudyArea_root" :with-info="false" :open-levels="1"
:assignable="true"></studip-tree>
</div>
</fieldset>
<fieldset>
<legend><?= _('Diese Veranstaltungen werden zugewiesen') ?></legend>
<table class="default selected-courses">
<colgroup>
<col>
</colgroup>
<thead>
<tr>
<th><?= _('Name') ?></th>
</tr>
</thead>
<tbody>
<? foreach ($courses as $course) : ?>
<tr>
<td>
<a href="<?= URLHelper::getLink('dispatch.php/course/overview', ['cid' => $course->id])?>"
title="<?= sprintf(_('Zur Veranstaltung %s'), htmlReady($course->getFullname())) ?>"
target="_blank">
<?= htmlReady($course->getFullname('number-name-semester')) ?>
</a>
<input type="hidden" name="courses[]" value="<?= htmlReady($course->id) ?>">
</td>
</tr>
<? endforeach ?>
</tbody>
</table>
</fieldset>
<? if ($return) : ?>
<input type="hidden" name="return" value="<?= htmlReady($return) ?>">
<? endif ?>
<footer data-dialog-button>
<?= Studip\Button::createAccept(_('Speichern'), 'store') ?>
<?= Studip\Button::createCancel(_('Abbrechen'), 'cancel', ['data-dialog' => 'close']) ?>
</footer>
</form>
<form class="default" action="<?= $controller->link_for('admin/tree/store', get_class($node), $node->id ?: null) ?>" method="post">
<section>
<label>
<?= _('Name') ?>
<input type="text" name="name"
placeholder="<?= _('Name des Eintrags (wird bei Zuweisung zu einer Stud.IP-Einrichtung überschrieben)') ?>">
</label>
</section>
<? if (get_class($node) === StudipStudyArea::class): ?>
<section>
<label>
<?= _('Infotext') ?>
<textarea name="description" rows="3"></textarea>
</label>
</section>
<section>
<label>
<?= _('Typ') ?>
<select name="type">
<? foreach ($GLOBALS['SEM_TREE_TYPES'] as $index => $type) : ?>
<option value="<?= htmlReady($index) ?>">
<?= $type['name'] ?: _('Standard') ?>
<?= !$type['editable'] ? _('(nicht mehr nachträglich änderbar)') : '' ?>
<?= $type['hidden'] ? _('(dieser Knoten ist versteckt)') : '' ?>
</option>
<? endforeach ?>
</select>
</label>
</section>
<? endif ?>
<section>
<label>
<?= _('Elternelement') ?>
<?= $treesearch->render() ?>
</label>
</section>
<section>
<label>
<?= _('Zu einer Stud.IP-Einrichtung zuordnen') ?>
<?= $instsearch->render() ?>
</label>
</section>
<input type="hidden" name="from" value="<?= htmlReady($from) ?>">
<?= CSRFProtection::tokenTag() ?>
<footer data-dialog-button>
<?= Studip\Button::createAccept(_('Speichern'), 'store') ?>
<?= Studip\Button::createCancel(_('Abbrechen'), 'cancel', ['data-dialog' => 'close']) ?>
</footer>
</form>
<form class="default" action="<?= $controller->link_for('admin/tree/store', get_class($node), $node->id) ?>" method="post">
<section>
<label>
<?= (get_class($node) === RangeTreeNode::class && $node->studip_object_id)
? _('Name (kann hier nicht bearbeitet werden, da es sich um ein Stud.IP-Objekt handelt)')
: _('Name') ?>
<input type="text" name="name"
value="<?= htmlReady($node->getName()) ?>"
<?= get_class($node) === RangeTreeNode::class && $node->studip_object_id ? ' disabled' : '' ?>>
</label>
</section>
<? if (get_class($node) === StudipStudyArea::class): ?>
<section>
<label>
<?= _('Infotext') ?>
<textarea name="description" rows="3"><?= htmlReady($node->info) ?></textarea>
</label>
</section>
<section>
<label>
<?= _('Typ') ?>
<select name="type"<?= empty($GLOBALS['SEM_TREE_TYPES'][$node->type]['editable']) ? ' disabled' : '' ?>>
<? foreach ($GLOBALS['SEM_TREE_TYPES'] as $index => $type) : ?>
<option value="<?= htmlReady($index) ?>"<?= $node->type == $index ? ' selected' : '' ?>>
<?= $type['name'] ?: _('Standard') ?>
<?= !$type['editable'] ? _('(nicht mehr nachträglich änderbar)') : '' ?>
<?= $type['hidden'] ? _('(dieser Knoten ist versteckt)') : '' ?>
</option>
<? endforeach ?>
</select>
</label>
</section>
<? endif ?>
<section>
<label>
<?= _('Elternelement') ?>
<?= $treesearch->render() ?>
</label>
</section>
<? if (get_class($node) === RangeTreeNode::class): ?>
<section>
<label>
<?= _('Zu einer Stud.IP-Einrichtung zuordnen') ?>
<?= $instsearch->render() ?>
</label>
</section>
<? endif ?>
<input type="hidden" name="from" value="<?= $from ?>">
<?= CSRFProtection::tokenTag() ?>
<footer data-dialog-button>
<?= Studip\Button::createAccept(_('Speichern'), 'store') ?>
<?= Studip\Button::createCancel(_('Abbrechen'), 'cancel', ['data-dialog' => 'close']) ?>
</footer>
</form>
<div data-studip-tree>
<studip-tree start-id="<?= htmlReady($startId) ?>" view-type="table" breadcrumb-icon="institute"
:with-search="false" :visible-children-only="false"
:editable="true" edit-url="<?= $controller->url_for('admin/tree/edit') ?>"
create-url="<?= $controller->url_for('admin/tree/create') ?>"
delete-url="<?= $controller->url_for('admin/tree/delete') ?>"
:with-courses="true" semester="<?= htmlReady($semester) ?>" :show-structure-as-navigation="true"
title="<?= _('Einrichtungshierarchie bearbeiten') ?>"></studip-tree>
</div>
<div data-studip-tree>
<studip-tree start-id="<?= htmlReady($startId) ?>" view-type="table" breadcrumb-icon="literature"
:with-search="false" :visible-children-only="false"
:editable="true" edit-url="<?= $controller->url_for('admin/tree/edit') ?>"
create-url="<?= $controller->url_for('admin/tree/create') ?>"
delete-url="<?= $controller->url_for('admin/tree/delete') ?>"
:show-structure-as-navigation="true" :with-course-assign="true"
:with-courses="true" semester="<?= htmlReady($semester) ?>"
title="<?= _('Veranstaltungshierarchie bearbeiten') ?>"></studip-tree>
</div>
<? <?php
$sem_browse_obj->do_output(); /**
* @var String $startId
* @var String $nodeClass
*/
?>
<div data-studip-tree>
<studip-tree start-id="<?= htmlReady($startId) ?>" view-type="list" :visible-children-only="true"
title="<?= htmlReady($treeTitle) ?>" breadcrumb-icon="<?= htmlReady($breadcrumbIcon) ?>"
:with-search="true" :with-export="true" :with-courses="true" semester="<?= htmlReady($semester) ?>"
:sem-class="<?= htmlReady($semClass) ?>" :with-export="true"></studip-tree>
</div>
<?= $form->render() ?>
<?
final class TreeChanges extends Migration
{
const FIELDS = [
'RANGE_TREE_PERM',
'SEM_TREE_PERM'
];
public function description()
{
return 'Removes old sem_- and range_tree permission settings and institute assignments for sem_tree entries';
}
protected function up()
{
// Remove config fields for special permissions concerning sem_- and range_tree administration.
DBManager::get()->execute(
"DELETE FROM `config_values` WHERE `field` IN (:fields)",
['fields' => self::FIELDS]
);
DBManager::get()->execute(
"DELETE FROM `config` WHERE `field` IN (:fields)",
['fields' => self::FIELDS]
);
// "Transfer" names from assigned institutes to sem_tree entries.
$stmt = DBManager::get()->prepare("UPDATE `sem_tree` SET `name` = :name WHERE `studip_object_id` = :inst");
$query = "SELECT DISTINCT `Institut_id`, `Name` FROM `Institute` WHERE `Institut_id` IN (
SELECT DISTINCT `studip_object_id` FROM `sem_tree`
)";
foreach (DBManager::get()->fetchAll($query) as $institute) {
$stmt->execute(['name' => $institute['Name'], 'inst' => $institute['Institut_id']]);
}
// Remove institute assignments for sem_tree entries.
DBManager::get()->exec("ALTER TABLE `sem_tree` DROP `studip_object_id`");
}
protected function down()
{
// Restore config entries to their defaults.
DBManager::get()->exec("INSERT IGNORE INTO `config`
( `config_id` , `parent_id` , `field` , `value` ,
`is_default` , `type` , `range` , `section` ,
`position` , `mkdate` , `chdate` , `description` ,
`comment` , `message_template` )
VALUES (
MD5( 'RANGE_TREE_ADMIN_PERM' ) , '', 'RANGE_TREE_ADMIN_PERM',
'admin', '1', 'string', 'global', '', '0',
UNIX_TIMESTAMP( ) , UNIX_TIMESTAMP( ) ,
'mit welchem Status darf die Einrichtungshierarchie bearbeitet werden (admin oder root)', '', ''
), (
MD5( 'SEM_TREE_ADMIN_PERM' ) , '', 'SEM_TREE_ADMIN_PERM',
'admin', '1', 'string', 'global', '', '0', UNIX_TIMESTAMP( ) ,
UNIX_TIMESTAMP( ) , 'mit welchem Status darf die Veranstaltungshierarchie bearbeitet werden (admin oder root)', '', ''
)");
// Add database column for sem_tree institute assignments.
DBManager::get()->exec("ALTER TABLE `sem_tree` ADD
`studip_object_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NULL DEFAULT NULL AFTER `name`");
// Add index for studip_object_id.
DBManager::get()->exec("ALTER TABLE `sem_tree` ADD INDEX `studip_object_id` (`studip_object_id`)");
}
}
...@@ -132,6 +132,7 @@ class RouteMap ...@@ -132,6 +132,7 @@ class RouteMap
$this->addAuthenticatedMessagesRoutes($group); $this->addAuthenticatedMessagesRoutes($group);
$this->addAuthenticatedNewsRoutes($group); $this->addAuthenticatedNewsRoutes($group);
$this->addAuthenticatedStudyAreasRoutes($group); $this->addAuthenticatedStudyAreasRoutes($group);
$this->addAuthenticatedTreeRoutes($group);
$this->addAuthenticatedWikiRoutes($group); $this->addAuthenticatedWikiRoutes($group);
} }
...@@ -281,6 +282,17 @@ class RouteMap ...@@ -281,6 +282,17 @@ class RouteMap
$group->get('/study-areas/{id}/parent', Routes\StudyAreas\ParentOfStudyAreas::class); $group->get('/study-areas/{id}/parent', Routes\StudyAreas\ParentOfStudyAreas::class);
} }
private function addAuthenticatedTreeRoutes(RouteCollectorProxy $group): void
{
$group->get('/tree-node/{id}', Routes\Tree\TreeShow::class);
$group->get('/tree-node/{id}/children', Routes\Tree\ChildrenOfTreeNode::class);
$group->get('/tree-node/{id}/courseinfo', Routes\Tree\CourseInfoOfTreeNode::class);
$group->get('/tree-node/{id}/courses', Routes\Tree\CoursesOfTreeNode::class);
$group->get('/tree-node/course/pathinfo/{classname}/{id}', Routes\Tree\PathinfoOfTreeNodeCourse::class);
$group->get('/tree-node/course/details/{id}', Routes\Tree\DetailsOfTreeNodeCourse::class);
}
private function addAuthenticatedWikiRoutes(RouteCollectorProxy $group): void private function addAuthenticatedWikiRoutes(RouteCollectorProxy $group): void
{ {
$this->addRelationship($group, '/wiki-pages/{id:.+}/relationships/parent', Routes\Wiki\Rel\ParentPage::class); $this->addRelationship($group, '/wiki-pages/{id:.+}/relationships/parent', Routes\Wiki\Rel\ParentPage::class);
......
<?php
namespace JsonApi\Routes\RangeTree;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
class ChildrenOfRangeTreeNode extends JsonApiController
{
protected $allowedIncludePaths = [
'children',
'courses',
'institute',
'parent',
];
protected $allowedPagingParameters = ['offset', 'limit'];
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameters)
*/
public function __invoke(Request $request, Response $response, $args)
{
if (!RangeTreeNode::getNode($args['id'])) {
throw new RecordNotFoundException();
}
list($offset, $limit) = $this->getOffsetAndLimit();
$total = \RangeTreeNode::countByParent_id($args['id']);
$children = \RangeTreeNode::findByParent_id(
$args['id'],
"LIMIT {$offset}, {$limit}"
);
return $this->getPaginatedContentResponse($children, $total);
}
}
<?php
namespace JsonApi\Routes\RangeTree;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
class CoursesOfRangeTreeNode extends JsonApiController
{
protected $allowedIncludePaths = [
'blubber-threads',
'end-semester',
'events',
'feedback-elements',
'file-refs',
'folders',
'forum-categories',
'institute',
'memberships',
'news',
'participating-institutes',
'sem-class',
'sem-type',
'start-semester',
'status-groups',
'wiki-pages',
];
protected $allowedPagingParameters = ['offset', 'limit'];
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameters)
*/
public function __invoke(Request $request, Response $response, $args)
{
$node = \RangeTreeNode::find($args['id']);
if (!$node) {
throw new RecordNotFoundException();
}
list($offset, $limit) = $this->getOffsetAndLimit();
$courses = $node->getCourses();
return $this->getPaginatedContentResponse(
$courses->limit($offset, $limit),
count($courses)
);
}
}
<?php
namespace JsonApi\Routes\RangeTree;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use JsonApi\Errors\AuthorizationFailedException;
use JsonApi\Errors\RecordNotFoundException;
use JsonApi\JsonApiController;
use JsonApi\Schemas\Institute as InstituteSchema;
class InstituteOfRangeTreeNode extends JsonApiController
{
protected $allowedIncludePaths = [
InstituteSchema::REL_STATUS_GROUPS,
];
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameters)
*/
public function __invoke(Request $request, Response $response, $args)
{
$node = \RangeTreeNode::find($args['id']);
if (!$node) {
throw new RecordNotFoundException();
}
return $this->getContentResponse($node->institute);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment