diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index 258c7e97ed06b9cc01e6629cf2c31e80b78048da..6eb99181090f63086f4f695c702dd15f26985af0 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -1417,14 +1417,6 @@ class Admin_CoursesController extends AuthenticatedController
                 'attributes' => ['data-dialog' => 'size=auto'],
                 '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'
-            ],
             22 => [
                 'name'       => _('Teilnehmendenexport'),
                 'title'      => _('Teilnehmendenexport'),
diff --git a/app/controllers/admin/tree.php b/app/controllers/admin/tree.php
deleted file mode 100644
index 18ddb0652774917d3d91814070daabc755ba23ad..0000000000000000000000000000000000000000
--- a/app/controllers/admin/tree.php
+++ /dev/null
@@ -1,292 +0,0 @@
-<?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)
-        );
-
-    }
-}
diff --git a/app/controllers/search/courses.php b/app/controllers/search/courses.php
index 71b7f7d689a231e484091b993d70e0f907d6a292..536c361ae412fdef1f33cb94ce0dc5955f632122 100644
--- a/app/controllers/search/courses.php
+++ b/app/controllers/search/courses.php
@@ -27,30 +27,108 @@ class Search_CoursesController extends AuthenticatedController
 
         PageLayout::setHelpKeyword('Basis.VeranstaltungenAbonnieren');
 
-        $this->type = Request::option('type', 'semtree');
-        $this->semester = Request::option('semester', Semester::findCurrent()->id);
-        $this->semClass = Request::int('semclass', 0);
+        // activate navigation item
+        $nav_options = Config::get()->COURSE_SEARCH_NAVIGATION_OPTIONS;
+        URLHelper::bindLinkParam('option', $this->nav_option);
+        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()
     {
-        $nodeClass = '';
-        if (Request::option('type', 'semtree') === 'semtree') {
-            Navigation::activateItem('/search/courses/semtree');
-            $nodeClass = StudipStudyArea::class;
-            $this->treeTitle = _('Studienbereiche');
-            $this->breadcrumbIcon = 'literature';
-            $this->editUrl = $this->url_for('studyarea/edit');
-        } else if (Request::option('type', 'semtree') === 'rangetree') {
-            Navigation::activateItem('/search/courses/rangetree');
-            $nodeClass = RangeTreeNode::class;
-            $this->treeTitle = _('Einrichtungen');
-            $this->breadcrumbIcon = 'institute';
-            $this->editUrl = $this->url_for('rangetree/edit');
+        SemBrowse::transferSessionData();
+        $this->sem_browse_obj = new SemBrowse();
+
+        if (!$GLOBALS['perm']->have_perm('root')) {
+            $this->sem_browse_obj->target_url = 'dispatch.php/course/details/';
+            $this->sem_browse_obj->target_id = 'sem_id';
+        } else {
+            $this->sem_browse_obj->target_url = 'seminar_main.php';
+            $this->sem_browse_obj->target_id = 'auswahl';
+        }
+
+        $sidebar = Sidebar::get();
+
+        // 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->setupSidebar();
+        $this->controller = $this;
     }
 
     public function export_results_action()
@@ -65,33 +143,4 @@ 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'));
-    }
 }
diff --git a/app/controllers/studyarea.php b/app/controllers/studyarea.php
deleted file mode 100644
index e43c9b0af15bfe919b648379ed47e6f8f2368ffc..0000000000000000000000000000000000000000
--- a/app/controllers/studyarea.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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));
-    }
-
-}
diff --git a/app/controllers/tree.php b/app/controllers/tree.php
deleted file mode 100644
index 7665b60c10a4c79c6c27f7e473a5c42eb206eb6b..0000000000000000000000000000000000000000
--- a/app/controllers/tree.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?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.');
-        }
-    }
-}
diff --git a/app/views/admin/courses/batch_assign_semtree.php b/app/views/admin/courses/batch_assign_semtree.php
deleted file mode 100644
index b28e96bb6117dce99f6337d5e4a8069ff8103262..0000000000000000000000000000000000000000
--- a/app/views/admin/courses/batch_assign_semtree.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-/**
- * @var Course $course
- */
-?>
-<label>
-    <input name="assign_semtree[]" type="checkbox" value="<?= htmlReady($course->id) ?>">
-</label>
diff --git a/app/views/admin/tree/assign_courses.php b/app/views/admin/tree/assign_courses.php
deleted file mode 100644
index df57aef0acad5be1f8626b8deee012353555bb27..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/assign_courses.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<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>
diff --git a/app/views/admin/tree/batch_assign_semtree.php b/app/views/admin/tree/batch_assign_semtree.php
deleted file mode 100644
index c28660281182799305918f5e1a1c1854be204d68..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/batch_assign_semtree.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<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>
diff --git a/app/views/admin/tree/create.php b/app/views/admin/tree/create.php
deleted file mode 100644
index 6b7255fdc6a57e76216566eefd1d5a65a07c6ee0..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/create.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<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>
diff --git a/app/views/admin/tree/edit.php b/app/views/admin/tree/edit.php
deleted file mode 100644
index 0076b95c8ff0c73d0a7f51f73c0584c3b28b3a23..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/edit.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<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>
diff --git a/app/views/admin/tree/rangetree.php b/app/views/admin/tree/rangetree.php
deleted file mode 100644
index 1e3e9453f535997e9cf76fc69a504de4939f4a62..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/rangetree.php
+++ /dev/null
@@ -1,9 +0,0 @@
-<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>
diff --git a/app/views/admin/tree/semtree.php b/app/views/admin/tree/semtree.php
deleted file mode 100644
index 0c48245a6ed599d147af257371e94fa3767fa57d..0000000000000000000000000000000000000000
--- a/app/views/admin/tree/semtree.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<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>
diff --git a/app/views/search/courses/index.php b/app/views/search/courses/index.php
index c6f490506018972d0765c56676e34fb9b73d6835..63b9dc44aa26969231e6b1d6bf0e7bce2ad23315 100644
--- a/app/views/search/courses/index.php
+++ b/app/views/search/courses/index.php
@@ -1,12 +1,2 @@
-<?php
-/**
- * @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>
+<?
+$sem_browse_obj->do_output();
diff --git a/app/views/studyarea/edit.php b/app/views/studyarea/edit.php
deleted file mode 100644
index 45a48d133d2504635b8f3cf84647998abc64bdc5..0000000000000000000000000000000000000000
--- a/app/views/studyarea/edit.php
+++ /dev/null
@@ -1 +0,0 @@
-<?= $form->render() ?>
diff --git a/db/migrations/5.4.6_tree_changes.php b/db/migrations/5.4.6_tree_changes.php
deleted file mode 100644
index 94d015caed9a976c8b84b3ca0aa1551208bfa529..0000000000000000000000000000000000000000
--- a/db/migrations/5.4.6_tree_changes.php
+++ /dev/null
@@ -1,66 +0,0 @@
-<?
-
-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`)");
-    }
-
-}
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index dadfa5c8733a2be74e9db832edcf5d940e310f83..d97e08d05acfdd6a16b6d3b9abc4bc563550b302 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -136,7 +136,6 @@ class RouteMap
         $this->addAuthenticatedNewsRoutes($group);
         $this->addAuthenticatedStockImagesRoutes($group);
         $this->addAuthenticatedStudyAreasRoutes($group);
-        $this->addAuthenticatedTreeRoutes($group);
         $this->addAuthenticatedWikiRoutes($group);
     }
 
@@ -302,17 +301,6 @@ class RouteMap
         $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
     {
         $this->addRelationship($group, '/wiki-pages/{id:.+}/relationships/parent', Routes\Wiki\Rel\ParentPage::class);
diff --git a/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php b/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php
deleted file mode 100644
index 8773c0a5b60940fbdff30041ce15ad60de8957e0..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/ChildrenOfRangeTreeNode.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/RangeTree/CoursesOfRangeTreeNode.php b/lib/classes/JsonApi/Routes/RangeTree/CoursesOfRangeTreeNode.php
deleted file mode 100644
index 980cac104afc8fab43771dc8d7a52bb78cea7b31..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/CoursesOfRangeTreeNode.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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)
-        );
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/RangeTree/InstituteOfRangeTreeNode.php b/lib/classes/JsonApi/Routes/RangeTree/InstituteOfRangeTreeNode.php
deleted file mode 100644
index 6ecf52a6033f6d5275872251b935fe3b45b300bc..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/InstituteOfRangeTreeNode.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/RangeTree/ParentOfRangeTreeNode.php b/lib/classes/JsonApi/Routes/RangeTree/ParentOfRangeTreeNode.php
deleted file mode 100644
index 01a40d3cd36d0d26ec0cfdd690c925a106f5477a..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/ParentOfRangeTreeNode.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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 ParentOfRangeTreeNode extends JsonApiController
-{
-    protected $allowedIncludePaths = [
-        'children',
-        'courses',
-        'institute',
-        'parent',
-    ];
-
-    /**
-     * @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->getParent());
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/RangeTree/RangeTreeIndex.php b/lib/classes/JsonApi/Routes/RangeTree/RangeTreeIndex.php
deleted file mode 100644
index c706c482204dc2e8ec58e354b5c3602e7d008e7a..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/RangeTreeIndex.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?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;
-
-/**
- * Zeigt eine bestimmte Veranstaltung an.
- */
-class RangeTreeIndex extends JsonApiController
-{
-
-    protected $allowedIncludePaths = [
-        'children',
-        'courses',
-        'institute',
-        'parent',
-    ];
-    protected $allowedPagingParameters = ['offset', 'limit'];
-
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        $tree = \TreeAbstract::getInstance('StudipSemTree', ['visible_only' => 1]);
-        $studyAreas = self::mapTree('root', $tree);
-        list($offset, $limit) = $this->getOffsetAndLimit();
-
-        return $this->getPaginatedContentResponse(
-            array_slice($studyAreas, $offset, $limit),
-            count($studyAreas)
-        );
-    }
-
-    private function mapTree($parentId, &$tree)
-    {
-        $level = [];
-        $kids = $tree->getKids($parentId);
-        if (is_array($kids) && count($kids) > 0) {
-            foreach ($kids as $kid) {
-                $level[] = \StudipStudyArea::find($kid);
-                $level = array_merge($level, self::mapTree($kid, $tree));
-            }
-        }
-
-        return $level;
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/RangeTree/RangeTreeShow.php b/lib/classes/JsonApi/Routes/RangeTree/RangeTreeShow.php
deleted file mode 100644
index c9f52ed11787e3a90a524aedfeb353023980b17c..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/RangeTree/RangeTreeShow.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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 RangeTreeShow extends JsonApiController
-{
-    protected $allowedIncludePaths = [
-        'children',
-        'courses',
-        'institute',
-        'parent',
-    ];
-
-    /**
-     * @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);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/StudyAreas/StudyAreasShow.php b/lib/classes/JsonApi/Routes/StudyAreas/StudyAreasShow.php
index 9ce57289658e1d62362a46032ccd2b5c08bfeaee..628abc828bd9966e2ba9b73031111dbfd62d1db3 100644
--- a/lib/classes/JsonApi/Routes/StudyAreas/StudyAreasShow.php
+++ b/lib/classes/JsonApi/Routes/StudyAreas/StudyAreasShow.php
@@ -22,8 +22,7 @@ class StudyAreasShow extends JsonApiController
      */
     public function __invoke(Request $request, Response $response, $args)
     {
-        $studyArea = \StudipStudyArea::find($args['id']);
-        if (!$studyArea && $args['id'] !== 'root') {
+        if (!$studyArea = \StudipStudyArea::find($args['id'])) {
             throw new RecordNotFoundException();
         }
 
diff --git a/lib/classes/JsonApi/Routes/Tree/ChildrenOfTreeNode.php b/lib/classes/JsonApi/Routes/Tree/ChildrenOfTreeNode.php
deleted file mode 100644
index 6419d03c6cef22ed815547a5878c548695bda047..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/ChildrenOfTreeNode.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use JsonApi\Errors\RecordNotFoundException;
-use JsonApi\JsonApiController;
-
-class ChildrenOfTreeNode extends JsonApiController
-{
-    protected $allowedFilteringParameters = ['visible'];
-
-    protected $allowedIncludePaths = [
-        'children',
-        'courses',
-        'institute',
-        'parent',
-    ];
-
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        list($classname, $id) = explode('_', $args['id']);
-
-        $node = $classname::getNode($id);
-        if (!$node) {
-            throw new RecordNotFoundException();
-        }
-
-        $filters = $this->getContextFilters();
-
-        $data = $node->getChildNodes((bool) $filters['visible']);
-
-        return $this->getContentResponse($data);
-    }
-
-    private function getContextFilters()
-    {
-        $defaults = [
-            'visible' => false
-        ];
-
-        $filtering = $this->getQueryParameters()->getFilteringParameters() ?: [];
-
-        return array_merge($defaults, $filtering);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/Tree/CourseInfoOfTreeNode.php b/lib/classes/JsonApi/Routes/Tree/CourseInfoOfTreeNode.php
deleted file mode 100644
index 283593150cf5c0c4ec8d3fb5cc0ae2555b9ff2d4..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/CourseInfoOfTreeNode.php
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use JsonApi\Errors\BadRequestException;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use JsonApi\Errors\RecordNotFoundException;
-use JsonApi\NonJsonApiController;
-
-class CourseinfoOfTreeNode extends NonJsonApiController
-{
-    protected $allowedFilteringParameters = ['q', 'semester', 'semclass', 'recursive'];
-
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        list($classname, $id) = explode('_', $args['id']);
-
-        $node = $classname::getNode($id);
-        if (!$node) {
-            throw new RecordNotFoundException();
-        }
-
-        $error = $this->validateFilters($request);
-        if ($error) {
-            throw new BadRequestException($error);
-        }
-
-        $filters = $this->getContextFilters($request);
-
-        $info = [
-            'courses' => (int) $node->countCourses($filters['semester'], $filters['semclass']),
-            'allCourses' => (int) $node->countCourses($filters['semester'], $filters['semclass'], true)
-        ];
-
-        $response->getBody()->write(json_encode($info));
-
-        return $response->withHeader('Content-type', 'application/json');
-    }
-
-    private function validateFilters($request)
-    {
-        $filtering = $request->getQueryParams()['filter'] ?: [];
-
-        // keyword aka q
-        if (isset($filtering['q']) && mb_strlen($filtering['q']) < 3) {
-            return 'Search term too short.';
-        }
-
-        // semester
-        if (isset($filtering['semester']) && $filtering['semester'] !== 'all') {
-            $semester = \Semester::find($filtering['semester']);
-            if (!$semester) {
-                return 'Invalid "semester".';
-            }
-        }
-
-        // course category
-        if (!empty($filtering['semclass'])) {
-            $semclass = \SeminarCategories::Get($filtering['semclass']);
-            if (!$semclass) {
-                return 'Invalid "course category".';
-            }
-        }
-    }
-
-    private function getContextFilters($request)
-    {
-        $defaults = [
-            'q' => '',
-            'semester' => 'all',
-            'semclass' => 0,
-            'recursive' => false
-        ];
-
-        $filtering = $request->getQueryParams()['filter'] ?: [];
-
-        return array_merge($defaults, $filtering);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/Tree/CoursesOfTreeNode.php b/lib/classes/JsonApi/Routes/Tree/CoursesOfTreeNode.php
deleted file mode 100644
index 623e6198520aebc04e7201918be95b31a5e12f1a..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/CoursesOfTreeNode.php
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use JsonApi\Errors\BadRequestException;
-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 CoursesOfTreeNode extends JsonApiController
-{
-    protected $allowedFilteringParameters = ['q', 'semester', 'semclass', 'recursive', 'ids'];
-
-    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)
-    {
-        list($classname, $id) = explode('_', $args['id']);
-
-        $node = $classname::getNode($id);
-        if (!$node) {
-            throw new RecordNotFoundException();
-        }
-
-        $error = $this->validateFilters();
-        if ($error) {
-            throw new BadRequestException($error);
-        }
-
-        $filters = $this->getContextFilters();
-
-        list($offset, $limit) = $this->getOffsetAndLimit();
-        $courses = \SimpleCollection::createFromArray(
-            $node->getCourses(
-                $filters['semester'],
-                $filters['semclass'],
-                $filters['q'],
-                (bool) $filters['recursive'],
-                $filters['ids']
-            )
-        );
-
-        return $this->getPaginatedContentResponse(
-            $courses->limit($offset, $limit),
-            count($courses)
-        );
-    }
-
-    private function validateFilters()
-    {
-        $filtering = $this->getQueryParameters()->getFilteringParameters() ?: [];
-
-        // keyword aka q
-        if (isset($filtering['q']) && mb_strlen($filtering['q']) < 3) {
-            return 'Search term too short.';
-        }
-
-        // semester
-        if (isset($filtering['semester']) && $filtering['semester'] !== 'all') {
-            $semester = \Semester::find($filtering['semester']);
-            if (!$semester) {
-                return 'Invalid "semester".';
-            }
-        }
-
-        // course category
-        if (!empty($filtering['semclass'])) {
-            $semclass = \SeminarCategories::Get($filtering['semclass']);
-            if (!$semclass) {
-                return 'Invalid "course category".';
-            }
-        }
-    }
-
-    private function getContextFilters()
-    {
-        $defaults = [
-            'q' => '',
-            'semester' => 'all',
-            'semclass' => 0,
-            'recursive' => false,
-            'ids' => []
-        ];
-
-        $filtering = $this->getQueryParameters()->getFilteringParameters() ?: [];
-
-        return array_merge($defaults, $filtering);
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php b/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php
deleted file mode 100644
index cef1077ecb7b1d8f15b515092865ab128c703c4f..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/DetailsOfTreeNodeCourse.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use JsonApi\Errors\AuthorizationFailedException;
-use JsonApi\Errors\RecordNotFoundException;
-use JsonApi\NonJsonApiController;
-
-class DetailsOfTreeNodeCourse extends NonJsonApiController
-{
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        $course = \Course::find($args['id']);
-        if (!$course) {
-            throw new RecordNotFoundException();
-        }
-
-        // Get course dates in textual form
-        $dates = \Seminar::GetInstance($args['id'])->getDatesHTML([
-            'semester_id' => null,
-            'show_room'   => true,
-        ]);
-
-        $data = [
-            'semester' => $course->semester_text,
-            'lecturers' => [],
-            'admissionstate' => null,
-            'dates' => $dates
-        ];
-
-        // Get lecturers
-        $lecturers = \SimpleCollection::createFromArray(
-            \CourseMember::findByCourseAndStatus($args['id'], 'dozent')
-        )->orderBy('position, nachname, vorname');
-        foreach ($lecturers as $l) {
-            $data['lecturers'][] = [
-                'id' => $l->user_id,
-                'username' => $l->username,
-                'name' => $l->getUserFullname()
-            ];
-        }
-
-        // Get admission state indicator if necessary
-        if (\Config::get()->COURSE_SEARCH_SHOW_ADMISSION_STATE) {
-            switch (\GlobalSearchCourses::getStatusCourseAdmission($course->id, $course->admission_prelim)) {
-                case 1:
-                    $data['admissionstate'] = [
-                        'icon' => 'decline-circle',
-                        'role' => \Icon::ROLE_STATUS_YELLOW,
-                        'info' => _('Eingeschränkter Zugang')
-                    ];
-                    break;
-                case 2:
-                    $data['admissionstate'] = [
-                        'icon' => 'decline-circle',
-                        'role' => \Icon::ROLE_STATUS_RED,
-                        'info' => _('Kein Zugang')
-                    ];
-                    break;
-                default:
-                    $data['admissionstate'] = [
-                        'icon' => 'check-circle',
-                        'role' => \Icon::ROLE_STATUS_GREEN,
-                        'info' => _('Uneingeschränkter Zugang')
-                    ];
-            }
-
-        }
-
-        $response->getBody()->write(json_encode($data));
-
-        return $response->withHeader('Content-type', 'application/json');
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/Tree/PathinfoOfTreeNodeCourse.php b/lib/classes/JsonApi/Routes/Tree/PathinfoOfTreeNodeCourse.php
deleted file mode 100644
index 282b7f9489031cc54149ebe7e49cc36547fa607c..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/PathinfoOfTreeNodeCourse.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-use JsonApi\Errors\AuthorizationFailedException;
-use JsonApi\Errors\RecordNotFoundException;
-use JsonApi\NonJsonApiController;
-
-class PathinfoOfTreeNodeCourse extends NonJsonApiController
-{
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        $course = \Course::find($args['id']);
-        if (!$course) {
-            throw new RecordNotFoundException();
-        }
-
-        $classname = $args['classname'];
-
-        $path = [];
-        foreach ($classname::getCourseNodes($args['id']) as $node) {
-            $path[] = $node->getAncestors();
-        }
-
-        $response->getBody()->write(json_encode($path));
-
-        return $response->withHeader('Content-type', 'application/json');
-    }
-}
diff --git a/lib/classes/JsonApi/Routes/Tree/TreeShow.php b/lib/classes/JsonApi/Routes/Tree/TreeShow.php
deleted file mode 100644
index 1eaa7972c6e4caf7996833a3330df142a90f0aa7..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Routes/Tree/TreeShow.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?php
-
-namespace JsonApi\Routes\Tree;
-
-use JsonApi\Errors\BadRequestException;
-use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
-use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
-use Neomerx\JsonApi\Schema\Link;
-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 TreeShow extends JsonApiController
-{
-    protected $allowedIncludePaths = [
-        'children',
-        'courseinfo',
-        'courses',
-        'institute',
-        'parent'
-    ];
-
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
-     */
-    public function __invoke(Request $request, Response $response, $args)
-    {
-        list($classname, $id) = explode('_', $args['id']);
-
-        $node = $classname::getNode($id);
-        if (!$node) {
-            throw new RecordNotFoundException();
-        }
-
-        return $this->getContentResponse($node);
-    }
-
-}
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index 97212bc65524bc7603199880c399811d81519b55..c1ce34d25bc8e41ce3d5f1952de5e5e0474c5ce5 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -46,7 +46,7 @@ class SchemaMap
             \JsonApi\Models\StudipProperty::class => Schemas\StudipProperty::class,
             \StudipComment::class => Schemas\StudipComment::class,
             \StudipNews::class => Schemas\StudipNews::class,
-            \StudipTreeNode::class => Schemas\TreeNode::class,
+            \StudipStudyArea::class => Schemas\StudyArea::class,
             \WikiPage::class => Schemas\WikiPage::class,
             \Studip\Activity\Activity::class => Schemas\Activity::class,
             \User::class => Schemas\User::class,
diff --git a/lib/classes/JsonApi/Schemas/StudyArea.php b/lib/classes/JsonApi/Schemas/StudyArea.php
index e9779c701090cb534361c248c005b8f22648a224..f077d83b70bf888c2a680bbfe232529c0995c3ef 100644
--- a/lib/classes/JsonApi/Schemas/StudyArea.php
+++ b/lib/classes/JsonApi/Schemas/StudyArea.php
@@ -1,4 +1,5 @@
 <?php
+
 namespace JsonApi\Schemas;
 
 use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
@@ -24,9 +25,6 @@ class StudyArea extends SchemaProvider
             'info' => (string) $resource['info'],
             'priority' => (int) $resource['priority'],
             'type-name' => (string) $resource->getTypeName(),
-            'has-children' => (bool) $resource->hasChildNodes(),
-            'ancestors' => (array) $resource->getAncestors(),
-            'classname' => get_class($resource)
         ];
     }
 
diff --git a/lib/classes/JsonApi/Schemas/TreeNode.php b/lib/classes/JsonApi/Schemas/TreeNode.php
deleted file mode 100644
index 90e6846de9d577b78e64be6d0ee7f9bc48b2e734..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Schemas/TreeNode.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-
-namespace JsonApi\Schemas;
-
-use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
-use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
-use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
-use Neomerx\JsonApi\Schema\Link;
-
-class TreeNode extends SchemaProvider
-{
-    const REL_CHILDREN = 'children';
-
-    const REL_COURSEINFO = 'courseinfo';
-    const REL_COURSES = 'courses';
-    const REL_INSTITUTE = 'institute';
-    const REL_PARENT = 'parent';
-
-    const TYPE = 'tree-node';
-
-    public function getId($resource): ?string
-    {
-        return get_class($resource) . '_' . $resource['id'];
-    }
-
-    public function getAttributes($resource, ContextInterface $context): iterable
-    {
-        $schema = [
-            'id' => (string) $resource->getId(),
-            'name' => (string) $resource->getName(),
-            'description' => (string) $resource->getDescription(),
-            'description-formatted' => (string) formatReady($resource->getDescription()),
-            'has-children' => (bool) $resource->hasChildNodes(),
-            'ancestors' => (array) $resource->getAncestors(),
-            'classname' => get_class($resource),
-            'visible' => true,
-            'editable' => true,
-            'assignable' => true
-        ];
-
-        // Some special options for sem_tree entries.
-        if (get_class($resource) === 'StudipStudyArea') {
-            if ($GLOBALS['SEM_TREE_TYPES'][$resource->type]['hidden'] ?? false) {
-                $schema['visible'] = false;
-            }
-            if ($GLOBALS['SEM_TREE_TYPES'][$resource->type]['editable'] ?? false) {
-                $schema['editable'] = false;
-            }
-            if (!\Config::get()->SEM_TREE_ALLOW_BRANCH_ASSIGN && $resource->hasChildNodes()) {
-                $schema['assignable'] = false;
-            }
-        }
-
-        return $schema;
-    }
-
-    public function getRelationships($resource, ContextInterface $context): iterable
-    {
-        $relationships = [];
-
-        $relationships = $this->addChildrenRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_CHILDREN));
-
-        if (property_exists($resource, 'courses') || method_exists($resource, 'getCourses')) {
-            $relationships = $this->addCourseInfoRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_COURSEINFO));
-            $relationships = $this->addCoursesRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_COURSES));
-        }
-        $relationships = $this->addInstituteRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_INSTITUTE));
-        $relationships = $this->addParentRelationship($relationships, $resource, $this->shouldInclude($context, self::REL_PARENT));
-
-        return $relationships;
-    }
-
-    private function addChildrenRelationship(array $relationships, $resource, $includeData)
-    {
-        $relationships[self::REL_CHILDREN] = [
-            self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CHILDREN),
-            ]
-        ];
-
-        if ($includeData) {
-            $children = $resource->getChildNodes();
-            $relationships[self::REL_CHILDREN][self::RELATIONSHIP_DATA] = $children;
-        }
-
-        return $relationships;
-    }
-
-
-    private function addCourseInfoRelationship(array $relationships, $resource, $includeData)
-    {
-        $relationships[self::REL_COURSEINFO] = [
-            self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COURSEINFO),
-            ],
-        ];
-
-        if ($includeData) {
-            $children = $resource->courses;
-            $relationships[self::REL_COURSES][self::RELATIONSHIP_DATA] = $children;
-        }
-
-        return $relationships;
-    }
-
-    private function addCoursesRelationship(array $relationships, $resource, $includeData)
-    {
-        $relationships[self::REL_COURSES] = [
-            self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COURSES)
-            ]
-        ];
-
-        if ($includeData) {
-            $courses = $resource->courses;
-            $relationships[self::REL_COURSES][self::RELATIONSHIP_DATA] = $courses;
-        }
-
-        return $relationships;
-    }
-
-    private function addInstituteRelationship(array $relationships, $resource, $includeData)
-    {
-        $relationships[self::REL_INSTITUTE] = [
-            self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_INSTITUTE),
-            ],
-        ];
-
-        if ($includeData) {
-            $relationships[self::REL_INSTITUTE][self::RELATIONSHIP_DATA] = $resource->institute;
-        }
-
-        return $relationships;
-    }
-
-    private function addParentRelationship(array $relationships, $resource, $includeData)
-    {
-        $relationships[self::REL_PARENT] = [
-            self::RELATIONSHIP_LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_PARENT)
-            ],
-        ];
-
-        if ($includeData) {
-            $relationships[self::REL_PARENT][self::RELATIONSHIP_DATA] = $resource->getParent();
-        }
-
-        return $relationships;
-    }
-}
diff --git a/lib/classes/SemBrowse.class.php b/lib/classes/SemBrowse.class.php
index c1cae8d12f24a73b3cef19d3e9fdaf2fc3ae1766..14f0d0e51e13cf250ce4e6f9308aa2db522f2ad1 100644
--- a/lib/classes/SemBrowse.class.php
+++ b/lib/classes/SemBrowse.class.php
@@ -1191,13 +1191,19 @@ class SemBrowse {
                     return new Navigation(_('Vorlesungsverzeichnis'),
                             URLHelper::getURL('dispatch.php/search/courses',
                                     [
-                                        'type' => 'semtree'
+                                        'level' => 'vv',
+                                        'cmd'   => 'qs',
+                                        'sset'  => '0',
+                                        'option' => ''
                                     ], true));
                 case 'rangetree':
                     return new Navigation(_('Einrichtungsverzeichnis'),
                             URLHelper::getURL('dispatch.php/search/courses',
                                     [
-                                        'type' => 'rangetree'
+                                        'level' => 'ev',
+                                        'cmd'   => 'qs',
+                                        'sset'  => '0',
+                                        'option' => ''
                                     ], true));
                 case 'module':
                     return new MVVSearchNavigation(_('Modulverzeichnis'),
diff --git a/lib/classes/StudipTreeNode.php b/lib/classes/StudipTreeNode.php
deleted file mode 100644
index a1d72586d71100ce73c091171b115c041c5f0323..0000000000000000000000000000000000000000
--- a/lib/classes/StudipTreeNode.php
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-
-/**
- * Interface StudipTreeNode
- * An abstract representation of a tree node in Stud.IP
- *
- * @author Thomas Hackl <hackl@data-quest.de>
- * @license GPL2 or any later version
- * @since   Stud.IP 5.3
- */
-
-interface StudipTreeNode
-{
-
-    /**
-     * Fetches a node by the given ID. The implementing class knows what to do.
-     *
-     * @param mixed $id
-     * @return StudipTreeNode
-     */
-    public static function getNode($id): StudipTreeNode;
-
-    /**
-     * Get all direct children of the given node.
-     *
-     * @param bool $onlyVisible fetch only visible nodes?
-     * @return StudipTreeNode[]
-     */
-    public function getChildNodes(bool $onlyVisible = false): array;
-
-    /**
-     * Fetches an array of all nodes the given course is assigned to.
-     *
-     * @param string $course_id
-     * @return array
-     */
-    public static function getCourseNodes(string $course_id): array;
-
-    /**
-     * This node's unique ID.
-     *
-     * @return mixed
-     */
-    public function getId();
-
-    /**
-     * A name (=label) for this node.
-     *
-     * @return string
-     */
-    public function getName(): string;
-
-    /**
-     * Optional description for this node.
-     *
-     * @return string
-     */
-    public function getDescription(): string;
-
-    /**
-     * Gets an optional Image (Icon or Avatar) for this node.
-     *
-     * @return Icon|Avatar|null
-     */
-    public function getImage();
-
-    /**
-     * Indicator if this node has children.
-     *
-     * @return bool
-     */
-    public function hasChildNodes(): bool;
-
-    /**
-     * How many courses are assigned to this node in the given semester?
-     *
-     * @param string $semester_id
-     * @param int $semclass
-     * @param bool $with_children
-     * @return int
-     */
-    public function countCourses(
-        string $semester_id = '',
-        int $semclass = 0,
-        bool $with_children = false
-    ): int;
-
-    /**
-     * Fetches courses assigned to this node in the given semester.
-     *
-     * @param string $semester_id
-     * @param int $semclass
-     * @param string $searchterm
-     * @param bool $with_children
-     * @param string[] $courses
-     *
-     * @return Course[]
-     */
-    public function getCourses(
-        string $semester_id = 'all',
-        int $semclass = 0,
-        string $searchterm = '',
-        bool $with_children = false,
-        array $courses = []
-    ): array;
-
-    /**
-     * Returns an array containing all ancestor nodes with id and name.
-     *
-     * @return array
-     */
-    public function getAncestors(): array;
-
-}
diff --git a/lib/classes/forms/QuicksearchInput.php b/lib/classes/forms/QuicksearchInput.php
index f4be547393429919b6196c6048bce7c64caf2ab2..2531a2e19c3ee13134ae6d54bf987fbf789aabd9 100644
--- a/lib/classes/forms/QuicksearchInput.php
+++ b/lib/classes/forms/QuicksearchInput.php
@@ -6,7 +6,7 @@ class QuicksearchInput extends Input
 {
     public function render()
     {
-        $template = $GLOBALS['template_factory']->open('forms/quicksearch_input');
+        $template = $GLOBALS['template_factory']->open('forms/checkbox_input');
         $template->title = $this->title;
         $template->name = $this->name;
         $template->value = $this->value;
diff --git a/lib/classes/searchtypes/TreeSearch.class.php b/lib/classes/searchtypes/TreeSearch.class.php
deleted file mode 100644
index 2fecf60e0cd9c81493b36424d47c12c8aed91fa0..0000000000000000000000000000000000000000
--- a/lib/classes/searchtypes/TreeSearch.class.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-/**
- * TreeSearch.class.php - Class of type SearchType used for searches with QuickSearch
- *
- * 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
- * @category    Stud.IP
- */
-
-class TreeSearch extends StandardSearch
-{
-    /**
-     *
-     * @param string $search The search type.
-     *
-     * @param Array $search_settings Settings for the selected seach type.
-     *     Depending on the search type different settings are possible
-     *     which can change the output or the display of the output
-     *     of the search. The array must be an associative array
-     *     with the setting as array key.
-     *     The following settings are implemented:
-     *     Search type 'room':
-     *     - display_seats: If set to true, the seats will be displayed
-     *       after the name of the room.
-     *
-     * @return void
-     */
-    public function __construct($search, $search_settings = [])
-    {
-        if (is_array($search_settings)) {
-            $this->search_settings = $search_settings;
-        }
-
-        $this->avatarLike = $this->search = $search;
-        $this->sql = $this->getSQL();
-    }
-
-    /**
-     * returns the title/description of the searchfield
-     *
-     * @return string title/description
-     */
-    public function getTitle()
-    {
-        switch ($this->search) {
-            case 'sem_tree_id':
-                return _('Studienbereich suchen');
-            case 'range_tree_id':
-                return _('Eintrag in der Einrichtungshierarchie suchen');
-            default:
-                throw new UnexpectedValueException('Invalid search type {$this->search}');
-        }
-    }
-
-    /**
-     * returns a sql-string appropriate for the searchtype of the current class
-     *
-     * @return string
-     */
-    private function getSQL()
-    {
-        switch ($this->search) {
-            case 'sem_tree_id':
-                return "SELECT `sem_tree_id`, `name`
-                        FROM `sem_tree`
-                        WHERE `name` LIKE :input
-                           OR `info` LIKE :input
-                       ORDER BY `name`";
-            case 'range_tree_id':
-                return "SELECT t.`item_id`, IF(t.`studip_object_id` IS NULL, t.`name`, i.`name`)
-                        FROM `range_tree` t
-                        LEFT JOIN `Institute` i ON (i.`Institut_id` = t.`studip_object_id`)
-                        WHERE t.`name` LIKE :input
-                           OR i.`Name` LIKE :input
-                        ORDER BY t.`name`, i.`Name`";
-            default:
-                throw new UnexpectedValueException("Invalid search type {$this->search}");
-        }
-    }
-
-    /**
-     * A very simple overwrite of the same method from SearchType class.
-     * returns the absolute path to this class for autoincluding this class.
-     *
-     * @return: path to this class
-     */
-    public function includePath()
-    {
-        return studip_relative_path(__FILE__);
-    }
-}
diff --git a/lib/models/RangeTreeNode.php b/lib/models/RangeTreeNode.php
deleted file mode 100644
index 9592515abc929d80bc904973f69009cfd3ae855c..0000000000000000000000000000000000000000
--- a/lib/models/RangeTreeNode.php
+++ /dev/null
@@ -1,265 +0,0 @@
-<?php
-
-/**
- * RangeTreeNode.php
- * model class for table range_tree
- *
- * 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>
- * @copyright   2022 Stud.IP Core-Group
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category    Stud.IP
- * @since       5.3
- *
- * @property string $id alias column for item_id
- * @property string $item_id database column
- * @property string $parent_id database column
- * @property int $level database column
- * @property int $priority database column
- * @property string $name database column
- * @property string|null $studip_object database column
- * @property string|null $studip_object_id database column
- * @property SimpleORMapCollection|RangeTreeNode[] $children has_many RangeTreeNode
- * @property Institute|null $institute belongs_to Institute
- * @property RangeTreeNode $parent belongs_to RangeTreeNode
- */
-class RangeTreeNode extends SimpleORMap implements StudipTreeNode
-{
-    protected static function configure($config = [])
-    {
-        $config['db_table'] = 'range_tree';
-
-        $config['belongs_to']['institute'] = [
-            'class_name'  => Institute::class,
-            'foreign_key' => 'studip_object_id',
-        ];
-        $config['belongs_to']['parent'] = [
-            'class_name'  => RangeTreeNode::class,
-            'foreign_key' => 'parent_id',
-        ];
-        $config['has_many']['children'] = [
-            'class_name'  => RangeTreeNode::class,
-            'foreign_key' => 'item_id',
-            'assoc_foreign_key' => 'parent_id',
-            'order_by' => 'ORDER BY priority, name',
-            'on_delete' => 'delete'
-        ];
-
-        parent::configure($config);
-    }
-
-    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
-    {
-        $nodes = [];
-        foreach (Course::find($course_id)->institutes as $institute) {
-            $range = self::findOneByStudip_object_id($institute->id);
-            if ($range) {
-                $nodes[] = $range;
-            }
-        }
-        return $nodes;
-    }
-
-    public function getName(): string
-    {
-        if ($this->id === 'root') {
-            return Config::get()->UNI_NAME_CLEAN;
-        }
-
-        if ($this->institute) {
-           return (string) $this->institute->name;
-        }
-
-        return $this->content['name'];
-    }
-
-    public function getDescription(): string
-    {
-        return '';
-    }
-
-    public function getImage()
-    {
-        return $this->institute ?
-            Avatar::getAvatar($this->studip_object_id) :
-            Icon::create('institute');
-    }
-
-    public function hasChildNodes(): bool
-    {
-        return count($this->children) > 0;
-    }
-
-    /**
-     * @see StudipTreeNode::getChildNodes()
-     */
-    public function getChildNodes(bool $onlyVisible = false): array
-    {
-        return self::findByParent_id($this->id, "ORDER BY `priority`, `name`");
-    }
-
-    /**
-     * @see StudipTreeNode::countCourses()
-     */
-    public function countCourses($semester_id = '', $semclass = 0, $with_children = false): int
-    {
-        if ($semester_id) {
-            $query = "SELECT COUNT(DISTINCT i.`seminar_id`)
-                      FROM `seminar_inst` i
-                      JOIN `seminare` s ON (s.`Seminar_id` = i.`seminar_id`)
-                      LEFT JOIN `semester_courses` sc ON (i.`seminar_id` = sc.`course_id`)
-                      WHERE i.`institut_id` IN (
-                          SELECT DISTINCT `studip_object_id`
-                          FROM `range_tree`
-                          WHERE `item_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 `seminar_id`)
-                      FROM `seminar_inst` i
-                      JOIN `seminare` s ON (s.`Seminar_id` = i.`seminar_id`)
-                      WHERE `institut_id` IN (
-                          SELECT DISTINCT `studip_object_id`
-                          FROM `range_tree`
-                          WHERE `item_id` IN (:ids)
-                      )";
-            $parameters = ['ids' => $with_children ? $this->getDescendantIds() : [$this->id]];
-        }
-
-        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->institute && !$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_inst` i ON (i.`seminar_id` = s.`Seminar_id`)
-                      LEFT JOIN `semester_courses` sem ON (sem.`course_id` = s.`Seminar_id`)
-                      WHERE i.`institut_id` IN (
-                          SELECT DISTINCT `studip_object_id`
-                          FROM `range_tree`
-                          WHERE `item_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_inst` i ON (i.`seminar_id` = s.`Seminar_id`)
-                      WHERE i.`institut_id` IN (
-                          SELECT DISTINCT `studip_object_id`
-                          FROM `range_tree`
-                          WHERE `item_id` IN (:ids)
-                      )";
-            $parameters = ['ids' => $with_children ? $this->getDescendantIds() : [$this->id]];
-        }
-
-        if ($searchterm) {
-            $query .= " AND s.`Name` LIKE :searchterm";
-            $parameters['searchterm'] = '%' . trim($searchterm) . '%';
-        }
-
-        if ($courses) {
-            $query .= " AND s.`Seminar_id` IN (:courses)";
-            $parameters['courses'] = $courses;
-        }
-
-        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 (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 getDescendantIds()
-    {
-        $ids = [];
-
-        foreach ($this->children as $child) {
-            $ids = array_merge($ids, [$child->id], $child->getDescendantIds());
-        }
-
-        return $ids;
-    }
-
-    public function getAncestors(): array
-    {
-        $path = [
-            [
-                'id' => $this->id,
-                'name' => $this->getName(),
-                'classname' => self::class
-            ]
-        ];
-
-        if ($this->parent_id) {
-            $path = array_merge($this->getNode($this->parent_id)->getAncestors(), $path);
-        }
-
-        return $path;
-    }
-
-}
diff --git a/lib/models/StudipStudyArea.class.php b/lib/models/StudipStudyArea.class.php
index 8b79970d27bf7b8502e35942378c60a3b9a73b5b..06b023c98435673d1b27fa658a11b554c2cb51e2 100644
--- a/lib/models/StudipStudyArea.class.php
+++ b/lib/models/StudipStudyArea.class.php
@@ -30,7 +30,7 @@
  * @property SimpleORMapCollection|Course[] $courses has_and_belongs_to_many Course
  */
 
-class StudipStudyArea extends SimpleORMap implements StudipTreeNode
+class StudipStudyArea extends SimpleORMap
 {
     /**
      * This constant represents the key of the root area.
@@ -51,6 +51,10 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
             'class_name' => Course::class,
             'thru_table' => 'seminar_sem_tree',
         ];
+        $config['belongs_to']['institute'] = [
+            'class_name' => Institute::class,
+            'foreign_key' => 'studip_object_id',
+        ];
         $config['belongs_to']['_parent'] = [
             'class_name' => StudipStudyArea::class,
             'foreign_key' => 'parent_id',
@@ -121,8 +125,11 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
     /**
      * Get the display name of this study area.
      */
-    public function getName(): string
+    public function getName()
     {
+        if ($this->studip_object_id) {
+            return $this->institute ? $this->institute->name : _('Unbekannte Einrichtung');
+        }
         return $this->content['name'];
     }
 
@@ -277,6 +284,26 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
     }
 
 
+    /**
+     * Get the studip_object_id of this study area.
+     */
+    public function getStudipObjectId()
+    {
+        return $this->studip_object_id;
+    }
+
+
+    /**
+     * Set the studip_object_id of this study area.
+     */
+    public function setStudipObjectId($id)
+    {
+        $this->studip_object_id = (string) $id;
+        $this->resetRelation('institute');
+        return $this;
+    }
+
+
     /**
      * Returns the children of this study area.
      */
@@ -419,191 +446,4 @@ class StudipStudyArea extends SimpleORMap implements StudipTreeNode
         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 ($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 ($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;
-    }
-
-    private function getDescendantIds()
-    {
-        $ids = [];
-
-        foreach ($this->_children as $child) {
-            $ids = array_merge($ids, [$child->id], $child->getDescendantIds());
-        }
-
-        return $ids;
-    }
-
 }
diff --git a/lib/navigation/AdminNavigation.php b/lib/navigation/AdminNavigation.php
index 43864ea1fec8afbec71cfac71eea98da4cc650c5..3b0afaf1eb48043d90c149aef144524c22f09772 100644
--- a/lib/navigation/AdminNavigation.php
+++ b/lib/navigation/AdminNavigation.php
@@ -90,9 +90,12 @@ class AdminNavigation extends Navigation
         $this->addSubNavigation('institute', $navigation);
         $navigation = new Navigation(_('Standort'));
 
-        if ($perm->have_perm('root')) {
-            $navigation->addSubNavigation('range_tree', new Navigation(_('Einrichtungshierarchie'), 'dispatch.php/admin/tree/rangetree'));
-            $navigation->addSubNavigation('sem_tree', new Navigation(_('Veranstaltungshierarchie'), 'dispatch.php/admin/tree/semtree'));
+        if ($perm->have_perm(Config::get()->RANGE_TREE_ADMIN_PERM ? Config::get()->RANGE_TREE_ADMIN_PERM : 'admin')) {
+            $navigation->addSubNavigation('range_tree', new Navigation(_('Einrichtungshierarchie'), 'admin_range_tree.php'));
+        }
+
+        if ($perm->have_perm(Config::get()->SEM_TREE_ADMIN_PERM ? Config::get()->SEM_TREE_ADMIN_PERM : 'admin') && $perm->is_fak_admin()) {
+            $navigation->addSubNavigation('sem_tree', new Navigation(_('Veranstaltungshierarchie'), 'admin_sem_tree.php'));
         }
 
         if ($perm->have_perm(Config::get()->LOCK_RULE_ADMIN_PERM ? Config::get()->LOCK_RULE_ADMIN_PERM : 'admin')) {
diff --git a/public/admin_range_tree.php b/public/admin_range_tree.php
new file mode 100644
index 0000000000000000000000000000000000000000..364f4fc95ce57737ff34a2c5b4a2ddb0f1df8483
--- /dev/null
+++ b/public/admin_range_tree.php
@@ -0,0 +1,59 @@
+<?php
+# Lifter002: TODO
+# Lifter007: TODO
+# Lifter003: TODO
+# Lifter010: TODO
+/**
+* Frontend
+*
+*
+*
+* @author       André Noack <andre.noack@data.quest.de>
+* @access       public
+* @modulegroup  admin_modules
+* @module       admin_range_tree
+* @package      Admin
+*/
+// +---------------------------------------------------------------------------+
+// This file is part of Stud.IP
+// admin_range_tree.php
+//
+// Copyright (c) 2002 André Noack <noack@data-quest.de>
+// Suchi & Berg GmbH <info@data-quest.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 any later version.
+// +---------------------------------------------------------------------------+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+// +---------------------------------------------------------------------------+
+
+
+require '../lib/bootstrap.php';
+
+page_open(["sess" => "Seminar_Session", "auth" => "Seminar_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User"]);
+$perm->check(Config::get()->RANGE_TREE_ADMIN_PERM ?: 'admin');
+
+include 'lib/seminar_open.php'; //hier werden die sessions initialisiert
+
+PageLayout::setTitle(_('Einrichtungshierarchie bearbeiten'));
+Navigation::activateItem('/admin/locations/range_tree');
+
+ob_start();
+
+$the_tree = new StudipRangeTreeViewAdmin();
+$the_tree->open_ranges['root'] = true;
+$the_tree->showTree();
+
+$template = $GLOBALS['template_factory']->open('layouts/base.php');
+$template->content_for_layout = ob_get_clean();
+echo $template->render();
+
+page_close();
diff --git a/public/admin_sem_tree.php b/public/admin_sem_tree.php
new file mode 100644
index 0000000000000000000000000000000000000000..e981be7230d0d51971a9698059cbbcdf16e9adb5
--- /dev/null
+++ b/public/admin_sem_tree.php
@@ -0,0 +1,310 @@
+<?php
+# Lifter001: TEST
+# Lifter002: TODO
+# Lifter007: TODO
+# Lifter003: TODO
+# Lifter010: TODO
+// +---------------------------------------------------------------------------+
+// This file is part of Stud.IP
+// admin_sem_tree.php
+//
+//
+// Copyright (c) 2003 André Noack <noack@data-quest.de>
+// Suchi & Berg GmbH <info@data-quest.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 any later version.
+// +---------------------------------------------------------------------------+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+// +---------------------------------------------------------------------------+
+
+use Studip\Button, Studip\LinkButton;
+
+require '../lib/bootstrap.php';
+
+page_open(["sess" => "Seminar_Session", "auth" => "Seminar_Auth", "perm" => "Seminar_Perm", "user" => "Seminar_User"]);
+$perm->check(Config::get()->SEM_TREE_ADMIN_PERM ?: 'admin');
+if (!$perm->is_fak_admin()){
+    $perm->perm_invalid(0,0);
+    page_close();
+    die;
+}
+
+include 'lib/seminar_open.php'; // initialise Stud.IP-Session
+
+PageLayout::setTitle(_('Veranstaltungshierachie bearbeiten'));
+Navigation::activateItem('/admin/locations/sem_tree');
+
+// Start of Output
+ob_start();
+
+$view = DbView::getView('sem_tree');
+$the_tree = new StudipSemTreeViewAdmin(Request::option('start_item_id'));
+$search_obj = new StudipSemSearch();
+
+$_open_items =& $the_tree->open_items;
+$_open_ranges =& $the_tree->open_ranges;
+$_possible_open_items = [];
+
+if (!Config::GetInstance()->getValue('SEM_TREE_ALLOW_BRANCH_ASSIGN')){
+    if(is_array($_open_items)){
+        foreach($_open_items as $item_id => $value){
+            if(!$the_tree->tree->getNumKids($item_id)) $_possible_open_items[$item_id] = $value;
+        }
+    }
+} else {
+    $_possible_open_items = $_open_items;
+}
+
+// allow add only for items where user has admin permission and which are not hidden
+if (is_array($_possible_open_items)) {
+    foreach ($_possible_open_items as $item_id => $value) {
+        if (!$the_tree->isItemAdmin($item_id) || $the_tree->tree->isHiddenItem($item_id)) {
+            unset($_possible_open_items[$item_id]);
+        }
+    }
+}
+
+if ($search_obj->search_done){
+    if ($search_obj->search_result->numRows > 50){
+        PageLayout::postError(_("Es wurden mehr als 50 Veranstaltungen gefunden! Bitte schränken Sie Ihre Suche weiter ein."));
+    } elseif ($search_obj->search_result->numRows > 0){
+        PageLayout::postSuccess(sprintf(
+            _("Es wurden %s Veranstaltungen gefunden, und in Ihre Merkliste eingefügt"),
+            $search_obj->search_result->numRows
+        ));
+        if (is_array($_SESSION['_marked_sem']) && count($_SESSION['_marked_sem'])){
+            $_SESSION['_marked_sem'] = array_merge(
+                (array)$_SESSION['_marked_sem'],
+                (array)$search_obj->search_result->getDistinctRows("seminar_id")
+            );
+        } else {
+            $_SESSION['_marked_sem'] = $search_obj->search_result->getDistinctRows("seminar_id");
+        }
+    } else {
+        PageLayout::postInfo(_("Es wurden keine Veranstaltungen gefunden, auf die Ihre Suchkriterien zutreffen."));
+    }
+}
+
+if (Request::option('cmd') === "MarkList"){
+    $sem_mark_list = Request::quotedArray('sem_mark_list');
+    if ($sem_mark_list){
+        if (Request::quoted('mark_list_aktion') == "del"){
+            $count_del = 0;
+            for ($i = 0; $i < count($sem_mark_list); ++$i){
+                if (isset($_SESSION['_marked_sem'][$sem_mark_list[$i]])){
+                    ++$count_del;
+                    unset($_SESSION['_marked_sem'][$sem_mark_list[$i]]);
+                }
+            }
+            PageLayout::postSuccess(sprintf(
+                _("%s Veranstaltung(en) wurde(n) aus Ihrer Merkliste entfernt."),
+                $count_del
+            ));
+        } else {
+            $tmp = explode("_",Request::quoted('mark_list_aktion'));
+            $item_ids[0] = $tmp[1];
+            if ($item_ids[0] === "all"){
+                $item_ids = [];
+                foreach ($_possible_open_items as $key => $value){
+                    if($key !== 'root')
+                        $item_ids[] = $key;
+                }
+            }
+            for ($i = 0; $i < count($item_ids); ++$i){
+                $count_ins = 0;
+                for ($j = 0; $j < count($sem_mark_list); ++$j){
+                    if ($sem_mark_list[$j]){
+                        $count_ins += StudipSemTree::InsertSemEntry($item_ids[$i], $sem_mark_list[$j]);
+                    }
+                }
+                $_msg .= sprintf(
+                    _("%s Veranstaltung(en) in <b>" .htmlReady($the_tree->tree->tree_data[$item_ids[$i]]['name']) . "</b> eingetragen.<br>"),
+                    $count_ins
+                );
+            }
+            if ($_msg) {
+                PageLayout::postSuccess($_msg);
+            }
+            $the_tree->tree->init();
+        }
+    }
+}
+if ($the_tree->mode === "MoveItem" || $the_tree->mode === "CopyItem"){
+    if ($_msg){
+        $_msg .= "§";
+    }
+    if ($the_tree->mode === "MoveItem"){
+        $text = _("Der Verschiebemodus ist aktiviert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element <b>%s</b> an diese Stelle zu verschieben.%s");
+    } else {
+        $text = _("Der Kopiermodus ist aktiviert. Bitte wählen Sie ein Einfügesymbol %s aus, um das Element <b>%s</b> an diese Stelle zu kopieren.%s");
+    }
+    PageLayout::postInfo(sprintf(
+        $text ,
+        Icon::create('arr_2right', 'sort', ['title' => _('Einfügesymbol')])->asImg(),
+        htmlReady($the_tree->tree->tree_data[$the_tree->move_item_id]['name']),
+        "<div align=\"right\">"
+        . LinkButton::createCancel(
+                _('Abbrechen'),
+                $the_tree->getSelf("cmd=Cancel&item_id=$the_tree->move_item_id"),
+                ['title' => _("Verschieben / Kopieren abbrechen")]
+        )
+        ."</div>"
+    ));
+}
+
+?>
+    <?
+    $search_obj->attributes_default = ['style' => ''];
+    // $search_obj->search_fields['type']['size'] = 30 ;
+    echo $search_obj->getFormStart(URLHelper::getLink($the_tree->getSelf()), ['class' => 'default narrow']);
+    ?>
+    <fieldset>
+        <legend><?= _("Veranstaltungssuche") ?></legend>
+
+        <label class="col-3">
+            <?=_("Titel")?>
+            <?=$search_obj->getSearchField("title")?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Untertitel")?>
+            <?=$search_obj->getSearchField("sub_title")?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Nummer")?>
+            <?=$search_obj->getSearchField("number")?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Kommentar")?>
+            <?=$search_obj->getSearchField("comment")?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Lehrende")?>
+            <?=$search_obj->getSearchField("lecturer")?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Bereich")?>
+            <?=$search_obj->getSearchField("scope")?>
+        </label>
+
+        <label>
+            <?=_("Kombination")?>
+            <?=$search_obj->getSearchField('combination')?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Typ")?>
+            <?=$search_obj->getSearchField("type", ['class' => 'size-s'])?>
+        </label>
+
+        <label class="col-3">
+            <?=_("Semester")?>
+            <?=$search_obj->getSearchField("sem", ['class' => 'size-s'])?>
+        </label>
+    </fieldset>
+
+    <footer>
+        <?=$search_obj->getSearchButton();?>
+        <?=$search_obj->getNewSearchButton();?>
+    </footer>
+
+    <?=$search_obj->getFormEnd();?>
+<br>
+<table width="100%" border="0" cellpadding="0" cellspacing="0">
+    <tr>
+        <td class="blank" width="75%" align="left" valign="top" colspan="2">
+            <? $the_tree->showSemTree(); ?>
+        </td>
+    </tr>
+</table>
+
+<?
+// Create Clipboard (use a second output buffer)
+ob_start();
+?>
+    <form action="<?=URLHelper::getLink($the_tree->getSelf("cmd=MarkList"))?>" method="post" class="default">
+    <?= CSRFProtection::tokenTag() ?>
+        <select multiple size="10" name="sem_mark_list[]" style="font-size:8pt;width:100%" class="nested-select">
+            <?
+            $cols = 50;
+            if (is_array($_SESSION['_marked_sem']) && count($_SESSION['_marked_sem'])){
+                $view->params[0] = array_keys($_SESSION['_marked_sem']);
+                $entries = new DbSnapshot($view->get_query("view:SEMINAR_GET_SEMDATA"));
+                $sem_data = $entries->getGroupedResult("seminar_id");
+                $sem_number = -1;
+                foreach ($sem_data as $seminar_id => $data) {
+                    if ((int)key($data['sem_number']) !== $sem_number){
+                        if ($sem_number !== -1) {
+                            echo '</optgroup>';
+                        }
+                        $sem_number = key($data['sem_number']);
+                        echo "\n<optgroup label=\"" . $the_tree->tree->sem_dates[$sem_number]['name'] . "\">";
+                    }
+                    $sem_name = key($data["Name"]);
+                    $sem_number_end = (int)key($data["sem_number_end"]);
+                    if ($sem_number !== $sem_number_end){
+                        $sem_name .= " (" . $the_tree->tree->sem_dates[$sem_number]['name'] . " - ";
+                        $sem_name .= (($sem_number_end === -1) ? _("unbegrenzt") : $the_tree->tree->sem_dates[$sem_number_end]['name']) . ")";
+                    }
+                    $line = htmlReady(my_substr($sem_name,0,$cols));
+                    $tooltip = $sem_name . " (" . join(",",array_keys($data["doz_name"])) . ")";
+                    echo "\n<option value=\"$seminar_id\" " . tooltip($tooltip,false) . ">$line</option>";
+                }
+                echo '</optgroup>';
+            }
+            ?>
+        </select>
+        <select name="mark_list_aktion" style="font-size:8pt;width:100%;margin-top:5px;">
+            <?
+            if (is_array($_possible_open_items) && count($_possible_open_items) && !(count($_possible_open_items) === 1 && $_possible_open_items['root'])){
+                echo "\n<option  value=\"insert_all\">" . _("Markierte in alle geöffneten Bereiche eintragen") . "</option>";
+                foreach ($_possible_open_items as $item_id => $value){
+                    echo "\n<option value=\"insert_{$item_id}\">"
+                   . sprintf(
+                       _('Markierte in "%s" eintragen'),
+                       htmlReady(my_substr($the_tree->tree->tree_data[$item_id]['name'],0,floor($cols * .8))
+                   ))
+                    . "</option>";
+                }
+            }
+            ?>
+            <option value="del"><?=_("Markierte aus der Merkliste löschen")?></option>
+        </select>
+        <div align="center">
+            <?= Button::create(
+                _('OK'),
+                [
+                    'title' => _("Gewählte Aktion starten"),
+                    'style' => 'vertical-align:middle;margin:3px;',
+                    'class' => 'accept button'
+                ]
+            ); ?>
+        </div>
+    </form>
+<?
+
+// Add Clipboard to Sidebar (get the inner/second output buffer)
+$content = ob_get_clean();
+$widget = new SidebarWidget();
+$widget->setTitle(_('Merkliste'));
+$widget->addElement(new WidgetElement($content));
+Sidebar::get()->addWidget($widget);
+
+$template = $GLOBALS['template_factory']->open('layouts/base.php');
+$template->content_for_layout = ob_get_clean();
+echo $template->render();
+
+page_close();
diff --git a/resources/assets/javascripts/bootstrap/treeview.js b/resources/assets/javascripts/bootstrap/treeview.js
deleted file mode 100644
index 998a70e72ff8bbb2838a8e03d95a89307b956569..0000000000000000000000000000000000000000
--- a/resources/assets/javascripts/bootstrap/treeview.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import StudipTree from '../../../vue/components/tree/StudipTree.vue'
-
-STUDIP.ready(() => {
-    document.querySelectorAll('[data-studip-tree]').forEach(element => {
-        STUDIP.Vue.load().then(({ createApp }) => {
-            createApp({
-                el: element,
-                components: { StudipTree }
-            })
-        })
-    });
-});
diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js
index 470e8699b31066e5a966384183e9785e3969dcdf..10686df05d7e0d669da1e00118cb31a2fb6dab5a 100644
--- a/resources/assets/javascripts/entry-base.js
+++ b/resources/assets/javascripts/entry-base.js
@@ -83,7 +83,6 @@ import "./bootstrap/oer.js"
 import "./bootstrap/courseware.js"
 import "./bootstrap/contentmodules.js"
 import "./bootstrap/responsive-navigation.js"
-import "./bootstrap/treeview.js"
 import "./bootstrap/stock-images.js"
 
 import "./mvv_course_wizard.js"
diff --git a/resources/assets/stylesheets/scss/tree.scss b/resources/assets/stylesheets/scss/tree.scss
deleted file mode 100644
index dbad68b1bc507c14dacc893b93662e8631eb6e84..0000000000000000000000000000000000000000
--- a/resources/assets/stylesheets/scss/tree.scss
+++ /dev/null
@@ -1,384 +0,0 @@
-$tree-outline: 1px solid var(--light-gray-color-40);
-
-.studip-tree {
-    &.studip-tree-navigatable {
-        > header {
-            display: flex;
-            flex-direction: row;
-            flex-wrap: nowrap;
-
-            h1 {
-                display: inline-block;
-                width: calc(100% - 28px);
-            }
-        }
-
-        .contentbar {
-            display: relative;
-
-            .contentbar-wrapper-right {
-                display: inherit;
-
-                .action-menu {
-                    button {
-                        top: -2px;
-                    }
-                }
-            }
-        }
-
-        .studip-tree-navigation-wrapper {
-            margin-right: 15px;
-            text-indent: 0;
-
-            .studip-tree-navigation {
-                background-color: var(--white);
-                border: 1px solid var(--content-color-40);
-                box-shadow: 2px 2px mix($base-gray, $white, 20%);
-                right: -20px;
-                padding: 10px;
-                position: absolute;
-                top: -15px;
-                width: 400px;
-                z-index: 3;
-
-                > header {
-                    border-bottom: 1px solid var(--content-color-40);
-                    display: flex;
-                    height: 60px;
-                    margin-bottom: 15px;
-                    margin-top: -15px;
-                    padding: 2px 0;
-
-                    h1 {
-                        line-height:60px;
-                        margin-bottom: 0;
-                        width: calc(100% - 40px);
-                    }
-
-                    button {
-                        flex: 0;
-                        padding-top: 10px;
-                    }
-                }
-
-                .studip-tree-node {
-                    width: 100%;
-                }
-            }
-        }
-    }
-
-    section {
-        margin-left: 0;
-        margin-right: 0;
-    }
-
-    button {
-        background: transparent;
-        border: 0;
-        color: var(--base-color);
-        cursor: pointer;
-        padding: 0;
-
-        &:hover {
-            .studip-tree-child-title {
-                text-decoration: underline;
-            }
-        }
-    }
-
-    .studip-tree-course {
-        .course-dates {
-            color: var(--dark-gray-color-80);
-            font-size: $font-size-small;
-            padding-left: 35px;
-        }
-
-        .course-details {
-            color: var(--dark-gray-color-80);
-            font-size: $font-size-small;
-            text-align: right;
-
-            .admission-state {
-                height: 18px;
-            }
-
-            .course-lecturers {
-                list-style: none;
-                padding-left: 0;
-            }
-        }
-    }
-
-    /* Display as foldable tree */
-    .studip-tree-node {
-
-        width: 100%;
-
-        a {
-            cursor: pointer;
-            display: flex;
-
-            img {
-                vertical-align: bottom;
-            }
-        }
-
-        .studip-tree-node-content {
-
-            display: flex;
-
-            &.studip-tree-node-active {
-                background-color: var(--light-gray-color-20);
-                margin: -5px;
-                padding: 5px;
-            }
-
-            .studip-tree-node-toggle {
-                margin-left: -2px;
-                margin-right: 5px;
-            }
-
-            .tooltip {
-                line-height: 24px;
-                margin-left: 5px;
-            }
-
-            .studip-tree-node-assignment-state {
-                margin-right: 10px;
-
-                img, svg {
-                    vertical-align: text-bottom;
-                }
-            }
-
-            a.studip-tree-node-edit-link {
-                opacity: 0;
-                visibility: hidden;
-
-            }
-
-            &:hover {
-                background-color: var(--light-gray-color-20);
-
-                a.studip-tree-node-edit-link {
-                    opacity: 1;
-                    visibility: visible;
-                }
-            }
-        }
-
-        .studip-tree-children {
-            list-style: none;
-            padding-left: 38px;
-
-            li {
-                border-left: $tree-outline;
-                display: flex;
-                margin-left: -31px;
-                padding: 5px 0 5px 5px;
-
-                &:before {
-                    border-bottom: $tree-outline;
-                    content: "";
-                    display: inline-block;
-                    height: 1em;
-                    left: -5px;
-                    position: relative;
-                    top: -5px;
-                    vertical-align: top;
-                    width: 10px;
-                }
-
-                &:last-child {
-                    border-left: none;
-
-                    &:before {
-                        border-left: $tree-outline;
-                    }
-                }
-            }
-        }
-    }
-
-    > .studip-tree-node {
-        width: calc(100% - 25px);
-    }
-
-    /* Top breadcrumb */
-    .studip-tree-breadcrumb {
-        display: flex;
-        flex-direction: row;
-        flex-wrap: nowrap;
-        max-width: 100%;
-        padding: 1em;
-        top: 2px;
-
-        .contentbar-wrapper-left {
-            max-width: calc(100% - 25px);
-
-            &.with-navigation {
-                max-width: calc(100% - 50px);
-            }
-
-            &.editable {
-                max-width: calc(100% - 50px);
-            }
-
-            &.with-navigation-and-editable {
-                max-width: calc(100% - 75px);
-            }
-
-            img {
-                vertical-align: text-bottom;
-            }
-
-            .studip-tree-breadcrumb-list {
-                display: inline-block;
-                flex: 1;
-                line-height: 24px;
-                margin-left: 15px;
-                overflow: hidden;
-                text-overflow: ellipsis;
-                white-space: nowrap;
-            }
-        }
-
-        .action-menu {
-            position: relative;
-            top: 5px;
-            width: 30px;
-        }
-
-    }
-
-    /* Display as tiled list */
-    .studip-tree-list {
-        section, nav:not(.contentbar-nav) {
-            padding: 15px;
-        }
-
-        .studip-tree-children {
-            display: grid;
-            grid-gap: 15px;
-            grid-template-columns: repeat(auto-fit, $sidebar-width);
-            list-style: none;
-            overflow-wrap: break-word;
-            padding-left: 0;
-
-            .studip-tree-child {
-                background: var(--dark-gray-color-5);
-                border: solid thin var(--light-gray-color-40);
-                display: flex;
-                height: 130px;
-                padding: 10px;
-
-                /* Handle for drag&drop */
-                .drag-handle {
-                    background-position-y: 8px;
-                }
-
-                a {
-                    display: flex;
-                    flex-direction: column;
-                    padding: 10px;
-                    text-align: left;
-
-                    .studip-tree-child-title {
-                        font-size: 1.1em;
-                        font-weight: bold;
-                    }
-                }
-
-                &:hover {
-                    background: var(--white);
-
-                    button {
-                        .studip-tree-child-title {
-                            color: var(--red);
-                        }
-                    }
-                }
-            }
-        }
-
-        table {
-            tr {
-                td {
-                    line-height: 24px;
-                    padding: 10px;
-                    vertical-align: top;
-
-                    a {
-                        img {
-                            margin-right: 5px;
-                            vertical-align: bottom;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /* Display as table */
-    .studip-tree-table {
-        table {
-            .studip-tree-node-info {
-                font-size: 0.9em;
-                padding: 15px;
-            }
-
-            tbody {
-                tr {
-
-                    &.studip-tree-course {
-                        .course-dates {
-                            padding-left: 0;
-                        }
-                    }
-
-                    td {
-                        line-height: 28px;
-                        padding: 5px;
-                        vertical-align: top;
-
-                        /* Handle for drag&drop */
-                        .drag-handle {
-                            background-position-y: -5px;
-                            padding-right: 10px;
-                        }
-
-                        button {
-                            background: transparent;
-                            border: 0;
-                            color: var(--base-color);
-                            cursor: pointer;
-
-                            &:hover {
-                                text-decoration: underline;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    .studip-tree-course-path {
-        font-size: 0.9em;
-        list-style: none;
-        padding: 5px;
-
-        button {
-            padding: 0;
-        }
-    }
-}
-
-form.default {
-    .studip-tree-node {
-        padding-top: unset !important;
-    }
-}
-
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index 467c50243ee266c2df38c91eb3c0e993688da2a2..8cd12b32b34192bce51d3886e9fb6939bd4da704 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -98,7 +98,6 @@
 @import "scss/tooltip";
 @import "scss/tfa";
 @import "scss/tour";
-@import "scss/tree";
 @import "scss/typography";
 @import "scss/user-administration";
 @import "scss/wiki";
diff --git a/resources/vue/components/SearchWidget.vue b/resources/vue/components/SearchWidget.vue
deleted file mode 100644
index d590b0092447257009da9989a626772c22620d6e..0000000000000000000000000000000000000000
--- a/resources/vue/components/SearchWidget.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<template>
-    <sidebar-widget id="search-widget" class="sidebar-search" :title="$gettext('Suche')">
-        <template #content>
-            <form class="sidebar-search">
-                <ul class="needles">
-                    <li>
-                        <div class="input-group files-search">
-                            <input type="text" id="searchterm" name="searchterm" v-model="searchterm"
-                                   :placeholder="$gettext('Veranstaltung suchen')"
-                                   :aria-label="$gettext('Veranstaltung suchen')">
-                            <a v-if="isActive" @click.prevent="cancelSearch" class="reset-search">
-                                <studip-icon shape="decline" size="20"></studip-icon>
-                            </a>
-                            <button type="submit" class="submit-search" :title="$gettext('Suchen')"
-                                    @click.prevent="doSearch">
-                                <studip-icon shape="search" :size="20"></studip-icon>
-                            </button>
-                        </div>
-                    </li>
-                </ul>
-            </form>
-        </template>
-    </sidebar-widget>
-</template>
-
-<script>
-import SidebarWidget from './SidebarWidget.vue';
-import StudipIcon from './StudipIcon.vue';
-
-export default {
-    name: 'search-widget',
-    components: {
-        StudipIcon,
-        SidebarWidget
-    },
-    data() {
-        return {
-            searchterm: '',
-            isActive: false
-        };
-    },
-    methods: {
-        doSearch() {
-            if (this.searchterm !== '') {
-                this.isActive = true;
-                STUDIP.eventBus.emit('do-search', this.searchterm);
-            }
-        },
-        cancelSearch() {
-            this.isActive = false;
-            this.searchterm = '';
-            STUDIP.eventBus.emit('cancel-search');
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/StudipProgressIndicator.vue b/resources/vue/components/StudipProgressIndicator.vue
index 62a33a8840fa4c476571da1d25bc903d126b150c..b6f7ee23537f6553d2cc30751dd64f3800c44838 100644
--- a/resources/vue/components/StudipProgressIndicator.vue
+++ b/resources/vue/components/StudipProgressIndicator.vue
@@ -31,7 +31,7 @@ export default {
             };
         },
         hasDescription () {
-            return this.description?.trim().length > 0;
+            return this.description.trim().length > 0;
         }
     }
 }
diff --git a/resources/vue/components/tree/AssignLinkWidget.vue b/resources/vue/components/tree/AssignLinkWidget.vue
deleted file mode 100644
index fec478f43b994f4c5572980bd82a6b013acb46c2..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/AssignLinkWidget.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-    <sidebar-widget v-if="node" id="assignwidget" class="sidebar-assign" :title="$gettext('Zuordnung')">
-        <template #content>
-            <a :href="assignUrl" :title="$gettext('Angezeigte Veranstaltungen zuordnen')"
-               @click.prevent="assignCurrentCourses">
-                <studip-icon shape="arr_2right"></studip-icon>
-                {{ $gettext('Angezeigte Veranstaltungen zuordnen') }}
-            </a>
-        </template>
-    </sidebar-widget>
-</template>
-
-<script>
-import SidebarWidget from '../SidebarWidget.vue';
-import StudipIcon from '../StudipIcon.vue';
-import { TreeMixin } from '../../mixins/TreeMixin';
-
-export default {
-    name: 'AssignLinkWidget',
-    components: { SidebarWidget, StudipIcon },
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: String,
-            required: true
-        },
-        courses: {
-            type: Array,
-            default: () => []
-        }
-    },
-    computed: {
-        assignUrl() {
-            return STUDIP.URLHelper.getURL('dispatch.php/admin/tree/batch_assign_semtree');
-        }
-    },
-    methods: {
-        assignCurrentCourses() {
-            STUDIP.Dialog.fromURL(this.assignUrl, { data: {
-                assign_semtree: this.courses.map(course => course.id),
-                return: window.location.href
-            }});
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/StudipTree.vue b/resources/vue/components/tree/StudipTree.vue
deleted file mode 100644
index ff493739f9823a121fc00fd54550fab639070c5c..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/StudipTree.vue
+++ /dev/null
@@ -1,214 +0,0 @@
-<template>
-    <div>
-        <div v-if="!isSearching"
-             class="studip-tree" :class="{'studip-tree-navigatable': showStructureAsNavigation}">
-            <studip-progress-indicator v-if="isLoading" :size="48"></studip-progress-indicator>
-            <studip-tree-list v-if="viewType === 'list' && startNode"  :with-children="withChildren"
-                              :visible-children-only="visibleChildrenOnly"
-                              :with-courses="withCourses" :semester="semester" :sem-class="semClass" :node="startNode"
-                              :breadcrumb-icon="breadcrumbIcon" :editable="editable" :edit-url="editUrl"
-                              :create-url="createUrl" :delete-url="deleteUrl" :with-export="withExport"
-                              :show-structure-as-navigation="showStructureAsNavigation" :assignable="assignable"
-                              :with-course-assign="withCourseAssign"
-                              @change-current-node="changeCurrentNode"></studip-tree-list>
-            <studip-tree-table v-else-if="viewType === 'table' && startNode" :with-children="withChildren"
-                               :visible-children-only="visibleChildrenOnly"
-                               :with-courses="withCourses" :semester="semester" :sem-class="semClass" :node="startNode"
-                               :breadcrumb-icon="breadcrumbIcon" :editable="editable" :edit-url="editUrl"
-                               :create-url="createUrl" :delete-url="deleteUrl" :with-export="withExport"
-                               :show-structure-as-navigation="showStructureAsNavigation" :assignable="assignable"
-                               :with-course-assign="withCourseAssign"
-                               @change-current-node="changeCurrentNode"></studip-tree-table>
-            <studip-tree-node v-else-if="viewType === 'tree' && startNode" :with-info="withInfo"
-                              :visible-children-only="visibleChildrenOnly" :node="startNode"
-                              :open-levels="openLevels" :openNodes="openNodes" :breadcrumb-icon="breadcrumbIcon"
-                              :editable="editable" :edit-url="editUrl" :create-url="createUrl" :delete-url="deleteUrl"
-                              :assignable="assignable" :assign-leaves-only="assignLeavesOnly"
-                              :not-assignable-nodes="notAssignableNodes"></studip-tree-node>
-
-        </div>
-        <div v-else class="studip-tree">
-            <tree-search-result :search-config="searchConfig"></tree-search-result>
-        </div>
-        <MountingPortal v-if="withSearch" mountTo="#search-widget" name="sidebar-search">
-            <search-widget></search-widget>
-        </MountingPortal>
-    </div>
-</template>
-
-<script>
-import axios from 'axios';
-import { TreeMixin } from '../../mixins/TreeMixin';
-import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import SearchWidget from '../SearchWidget.vue';
-import StudipTreeList from './StudipTreeList.vue';
-import StudipTreeTable from './StudipTreeTable.vue';
-import StudipTreeNode from './StudipTreeNode.vue';
-import TreeSearchResult from './TreeSearchResult.vue';
-
-export default {
-    name: 'StudipTree',
-    components: {
-        TreeSearchResult, SearchWidget, StudipProgressIndicator, StudipTreeList, StudipTreeTable, StudipTreeNode
-    },
-    mixins: [ TreeMixin ],
-    props: {
-        viewType: {
-            type: String,
-            default: 'tree'
-        },
-        treeId: {
-            type: String,
-            default: ''
-        },
-        startId: {
-            type: String,
-            required: true
-        },
-        title: {
-            type: String,
-            default: ''
-        },
-        openNodes: {
-            type: Array,
-            default: () => []
-        },
-        openLevels: {
-            type: Number,
-            default: 0
-        },
-        withChildren: {
-            type: Boolean,
-            default: true
-        },
-        withInfo: {
-            type: Boolean,
-            default: true
-        },
-        visibleChildrenOnly: {
-            type: Boolean,
-            default: true
-        },
-        withCourses: {
-            type: Boolean,
-            default: false
-        },
-        semester: {
-            type: String,
-            default: ''
-        },
-        semClass: {
-            type: Number,
-            default: 0
-        },
-        breadcrumbIcon: {
-            type: String,
-            default: 'literature'
-        },
-        itemIcon: {
-            type: String,
-            default: 'literature'
-        },
-        withSearch: {
-            type: Boolean,
-            default: false
-        },
-        withExport: {
-            type: Boolean,
-            default: false
-        },
-        withCourseAssign: {
-            type: Boolean,
-            default: false
-        },
-        editable: {
-            type: Boolean,
-            default: false
-        },
-        editUrl: {
-            type: String,
-            default: ''
-        },
-        createUrl: {
-            type: String,
-            default: ''
-        },
-        deleteUrl: {
-            type: String,
-            default: ''
-        },
-        showStructureAsNavigation: {
-            type: Boolean,
-            default: false
-        },
-        assignable: {
-            type: Boolean,
-            default: false
-        },
-        assignLeavesOnly: {
-            type: Boolean,
-            default: false
-        },
-        notAssignableNodes: {
-            type: Array,
-            default: () => []
-        }
-    },
-    data() {
-        return {
-            nodeId: this.startId,
-            startNode: null,
-            currentNode: this.startNode,
-            loaded: false,
-            isLoading: false,
-            showStructuralNavigation: false,
-            searchConfig: {},
-            isSearching: false
-        }
-    },
-    methods: {
-        changeCurrentNode(node) {
-            this.currentNode = node;
-            this.$nextTick(() => {
-                document.getElementById('tree-breadcrumb-' + node.attributes.id)?.focus();
-            });
-        },
-        exportUrl() {
-            return STUDIP.URLHelper.getURL('dispatch.php/tree/export_csv');
-        }
-    },
-    mounted() {
-        window.focus();
-
-        const loadingIndicator = axios.interceptors.request.use(config => {
-            setTimeout(() => {
-                if (!this.loaded) {
-                    this.isLoading = true;
-                }
-            }, this.showProgressIndicatorTimeout);
-            return config;
-        });
-
-        this.getNode(this.startId).then(response => {
-            this.startNode = response.data.data;
-            this.currentNode = this.startNode;
-            this.loaded = true;
-            this.isLoading = false;
-        });
-
-        axios.interceptors.request.eject(loadingIndicator);
-
-        this.globalOn('do-search', searchterm => {
-            this.searchConfig.searchterm = searchterm;
-            this.searchConfig.semester = this.semester;
-            this.searchConfig.classname = this.startNode.attributes.classname;
-            this.isSearching = true;
-        });
-
-        this.globalOn('cancel-search', () => {
-            this.searchConfig = {};
-            this.isSearching = false;
-        });
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/StudipTreeList.vue b/resources/vue/components/tree/StudipTreeList.vue
deleted file mode 100644
index d773f433f891041b28e161f3786e578c44c763ca..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/StudipTreeList.vue
+++ /dev/null
@@ -1,359 +0,0 @@
-<template>
-    <article class="studip-tree-list">
-        <header>
-            <tree-breadcrumb v-if="currentNode.id !== 'root'" :node="currentNode"
-                             :edit-url="editUrl" :icon="breadcrumbIcon" :assignable="assignable"
-                             :num-children="children.length" :num-courses="courses.length"
-                             :show-navigation="showStructureAsNavigation"
-                             :visible-children-only="visibleChildrenOnly"></tree-breadcrumb>
-        </header>
-        <studip-progress-indicator v-if="isLoading"></studip-progress-indicator>
-        <section v-else>
-            <h1>
-                {{ currentNode.attributes.name }}
-                <a v-if="isEditable && currentNode.attributes.id !== 'root'"
-                   :href="editUrl + '/' + currentNode.attributes.id"
-                   @click.prevent="editNode(editUrl, currentNode.id)" data-dialog="size=medium"
-                   :title="$gettextInterpolate($gettext('%{name} bearbeiten'), {name: currentNode.attributes.name})">
-                    <studip-icon shape="edit" :size="20"></studip-icon>
-                </a>
-            </h1>
-            <p v-if="currentNode.attributes.description?.trim() !== ''" class="studip-tree-node-info"
-               v-html="currentNode.attributes['description-formatted']">
-            </p>
-        </section>
-
-        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
-
-        <nav v-if="withChildren && currentNode.attributes['has-children']" >
-            <h1>
-                {{ $gettext('Unterebenen') }}
-            </h1>
-            <draggable v-model="children" handle=".drag-handle" :animation="300" tag="ul"
-                       class="studip-tree-children" @end="dropChild">
-                <li v-for="(child, index) in children" :key="index" class="studip-tree-child">
-                    <a v-if="editable && children.length > 1" class="drag-link"
-                       tabindex="0"
-                       :title="$gettextInterpolate($gettext('Sortierelement für Element %{node}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {node: child.attributes.name})"
-                       @keydown="keyHandler($event, index)"
-                       :ref="'draghandle-' + index">
-                        <span class="drag-handle"></span>
-                    </a>
-                    <tree-node-tile :node="child" :semester="withCourses ? semester : 'all'" :sem-class="semClass"
-                                    :url="nodeUrl(child.id, semester !== 'all' ? semester : null)"></tree-node-tile>
-                </li>
-            </draggable>
-        </nav>
-        <section v-else-if="withChildren && !currentNode.attributes['has-children']"  class="studip-tree-node-no-children">
-            {{ $gettext('Auf dieser Ebene existieren keine weiteren Unterebenen.') }}
-        </section>
-        <section v-if="withCourses && thisLevelCourses === 0" class="studip-tree-node-no-courses">
-            {{ $gettext('Auf dieser Ebene sind keine Veranstaltungen zugeordnet.')}}
-        </section>
-
-        <section v-if="thisLevelCourses + subLevelsCourses > 0">
-            <span v-if="withCourses && showingAllCourses">
-                <button type="button" @click="showAllCourses(false)"
-                        :title="$gettext('Veranstaltungen auf dieser Ebene anzeigen')">
-                    Veranstaltungen auf dieser Ebene anzeigen
-                </button>
-            </span>
-            <template v-if="thisLevelCourses > 0 && subLevelsCourses > 0">
-                |
-            </template>
-            <span v-if="withCourses && subLevelsCourses > 0 && !showingAllCourses">
-                <button type="button" @click="showAllCourses(true)"
-                        :title="$gettext('Veranstaltungen auf allen Unterebenen anzeigen')">
-                    Veranstaltungen auf allen Unterebenen anzeigen
-                </button>
-            </span>
-        </section>
-        <table v-if="courses.length > 0" class="default">
-            <caption>{{ $gettext('Veranstaltungen') }}</caption>
-            <colgroup>
-                <col>
-                <col>
-            </colgroup>
-            <thead>
-                <tr>
-                    <th>{{ $gettext('Name') }}</th>
-                    <th>{{ $gettext('Information') }}</th>
-                </tr>
-            </thead>
-            <tbody>
-                <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
-                    <td>
-                        <a :href="courseUrl(course.id)"
-                           :title="$gettextInterpolate($gettext('Zur Veranstaltung %{ course }'),
-                                { course: course.attributes.title })">
-                            <studip-icon shape="seminar" :size="26"></studip-icon>
-                            <template v-if="course.attributes['course-number']">
-                                {{ course.attributes['course-number'] }}
-                            </template>
-                            {{ course.attributes.title }}
-                        </a>
-                        <div :id="'course-dates-' + course.id" class="course-dates"></div>
-                    </td>
-                    <td>
-                        <tree-course-details :course="course.id"></tree-course-details>
-                    </td>
-                </tr>
-            </tbody>
-        </table>
-        <MountingPortal v-if="withExport" mountTo="#export-widget" name="sidebar-export">
-            <tree-export-widget v-if="courses.length > 0"
-                                :title="$gettext('Veranstaltungen exportieren')" :url="exportUrl()"
-                                :export-data="courses"></tree-export-widget>
-        </MountingPortal>
-        <MountingPortal v-if="withCourseAssign" mountTo="#assign-widget" name="sidebar-assign-courses">
-            <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
-        </MountingPortal>
-    </article>
-</template>
-
-<script>
-import draggable from 'vuedraggable';
-import { TreeMixin } from '../../mixins/TreeMixin';
-import TreeExportWidget from './TreeExportWidget.vue';
-import TreeBreadcrumb from './TreeBreadcrumb.vue';
-import TreeNodeTile from './TreeNodeTile.vue';
-import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import TreeCourseDetails from './TreeCourseDetails.vue';
-import AssignLinkWidget from "./AssignLinkWidget.vue";
-
-export default {
-    name: 'StudipTreeList',
-    components: {
-        draggable, StudipProgressIndicator, TreeExportWidget, TreeBreadcrumb, TreeNodeTile, TreeCourseDetails,
-        AssignLinkWidget
-    },
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        breadcrumbIcon: {
-            type: String,
-            default: 'literature'
-        },
-        editable: {
-            type: Boolean,
-            default: false
-        },
-        editUrl: {
-            type: String,
-            default: ''
-        },
-        createUrl: {
-            type: String,
-            default: ''
-        },
-        deleteUrl: {
-            type: String,
-            default: ''
-        },
-        withCourses: {
-            type: Boolean,
-            default: false
-        },
-        withExport: {
-            type: Boolean,
-            default: false
-        },
-        withChildren: {
-            type: Boolean,
-            default: true
-        },
-        visibleChildrenOnly: {
-            type: Boolean,
-            default: true
-        },
-        assignable: {
-            type: Boolean,
-            default: false
-        },
-        withCourseAssign: {
-            type: Boolean,
-            default: false
-        },
-        semester: {
-            type: String,
-            default: ''
-        },
-        semClass: {
-            type: Number,
-            default: 0
-        },
-        showStructureAsNavigation: {
-            type: Boolean,
-            default: false
-        }
-    },
-    data() {
-        return {
-            currentNode: this.node,
-            isLoading: false,
-            isLoaded: false,
-            children: [],
-            courses: [],
-            assistiveLive: '',
-            subLevelsCourses: 0,
-            thisLevelCourses: 0,
-            showingAllCourses: false
-        }
-    },
-    methods: {
-        openNode(node, pushState = true) {
-            this.currentNode = node;
-            this.$emit('change-current-node', node);
-
-            if (this.withChildren) {
-                this.getNodeChildren(node, this.visibleChildrenOnly).then(response => {
-                    this.children = response.data.data;
-                });
-            }
-
-            this.getNodeCourseInfo(node, this.semester, this.semClass)
-                .then(response => {
-                    this.thisLevelCourses = response?.data.courses;
-                    this.subLevelsCourses = response?.data.allCourses;
-                });
-
-            if (this.withCourses) {
-                this.getNodeCourses(node, this.semester, this.semClass, '', false)
-                    .then(courses => {
-                        this.courses = courses.data.data;
-                    });
-            }
-
-            // Update browser history.
-            if (pushState) {
-                const nodeId = node.id;
-                const url = STUDIP.URLHelper.getURL('', {node_id: nodeId});
-                window.history.pushState({nodeId}, '', url);
-            }
-
-            // Update node_id for semester selector.
-            const semesterSelector = document.querySelector('#semester-selector-node-id');
-            semesterSelector.value = node.id;
-        },
-        dropChild() {
-            this.updateSorting(this.currentNode.id, this.children);
-        },
-        keyHandler(e, index) {
-            switch (e.keyCode) {
-                case 38: // up
-                    e.preventDefault();
-                    this.decreasePosition(index);
-                    this.$nextTick(() => {
-                        this.$refs['draghandle-' + (index - 1)][0].focus();
-                        this.assistiveLive = this.$gettextInterpolate(
-                            this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
-                            { pos: index, listLength: this.children.length }
-                        );
-                    });
-                    break;
-                case 40: // down
-                    e.preventDefault();
-                    this.increasePosition(index);
-                    this.$nextTick(function () {
-                        this.$refs['draghandle-' + (index + 1)][0].focus();
-                        this.assistiveLive = this.$gettextInterpolate(
-                            this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
-                            { pos: index + 2, listLength: this.children.length }
-                        );
-                    });
-                    break;
-            }
-        },
-        decreasePosition(index) {
-            if (index > 0) {
-                const temp = this.children[index - 1];
-                this.children[index - 1] = this.children[index];
-                this.children[index] = temp;
-                this.updateSorting(this.currentNode.id, this.children);
-            }
-        },
-        increasePosition(index) {
-            if (index < this.children.length) {
-                const temp = this.children[index + 1];
-                this.children[index + 1] = this.children[index];
-                this.children[index] = temp;
-                this.updateSorting(this.currentNode.id, this.children);
-            }
-        },
-        showAllCourses(state) {
-            this.getNodeCourses(this.currentNode, this.semester, this.semClass, '', state)
-                .then(courses => {
-                    this.courses = courses.data.data;
-                    this.showingAllCourses = state;
-                });
-        }
-    },
-    mounted() {
-        if (this.withChildren) {
-            this.getNodeChildren(this.currentNode, this.visibleChildrenOnly).then(response => {
-                this.children = response.data.data;
-            });
-        }
-
-        this.getNodeCourseInfo(this.currentNode, this.semester, this.semClass)
-            .then(response => {
-                this.thisLevelCourses = response?.data.courses;
-                this.subLevelsCourses = response?.data.allCourses;
-            });
-
-        if (this.withCourses) {
-            this.getNodeCourses(this.currentNode, this.semester, this.semClass)
-                .then(courses => {
-                    this.courses = courses.data.data;
-                });
-        }
-
-        this.globalOn('open-tree-node', node => {
-            this.openNode(node);
-        });
-
-        this.globalOn('load-tree-node', id => {
-            this.getNode(id).then(response => {
-                this.openNode(response.data.data);
-            });
-        });
-
-        this.globalOn('sort-tree-children', data => {
-            if (this.currentNode.id === data.parent) {
-                this.children = data.children;
-            }
-        });
-
-        window.addEventListener('popstate', (event) => {
-            if (event.state) {
-                if ('nodeId' in event.state) {
-                    this.getNode(event.state.nodeId).then(response => {
-                        this.openNode(response.data.data, false);
-                    });
-                }
-            } else {
-                this.openNode(this.node, false);
-            }
-        });
-
-        // Add current node to semester selector widget.
-        this.$nextTick(() => {
-            const semesterForm = document.querySelector('#semester-selector .sidebar-widget-content form');
-            const nodeField = document.createElement('input');
-            nodeField.id = 'semester-selector-node-id';
-            nodeField.type = 'hidden';
-            nodeField.name = 'node_id';
-            nodeField.value = this.node.id;
-            semesterForm.appendChild(nodeField);
-        });
-    },
-    beforeDestroy() {
-        STUDIP.eventBus.off('open-tree-node');
-        STUDIP.eventBus.off('load-tree-node');
-        STUDIP.eventBus.off('sort-tree-children');
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/StudipTreeNode.vue b/resources/vue/components/tree/StudipTreeNode.vue
deleted file mode 100644
index 39f696ccc14e12480701eb2501a71b34cffacf4a..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/StudipTreeNode.vue
+++ /dev/null
@@ -1,277 +0,0 @@
-<template>
-    <section class="studip-tree-node">
-        <span :class="{ 'studip-tree-node-content': true, 'studip-tree-node-active': node?.id === activeNode?.id }">
-            <a @click.prevent="toggleNode(true)">
-                <div v-if="node.attributes['has-children']" class="studip-tree-node-toggle">
-                    <studip-icon :shape="openState ? 'arr_1down': 'arr_1right'" :size="20"/>
-                </div>
-            </a>
-            <button v-if="isAssignable && node.attributes.id !== 'root'" class="studip-tree-node-assignment-state"
-                    @click.prevent="changeAssignmentState()" :title="$gettext('Zuordnung ändern')">
-                <studip-icon :shape="assignmentState === 0
-                    ? 'checkbox-unchecked'
-                    : (assignmentState === 1 ? 'checkbox-checked' : 'checkbox-indeterminate')"></studip-icon>
-            </button>
-            <a @click.prevent="toggleNode(true)">
-                <div class="studip-tree-node-name">
-                    {{ node.attributes.name }}
-                </div>
-            </a>
-            <studip-tooltip-icon v-if="withInfo && !isLoading && node.attributes.description?.trim() !== ''"
-                                 :text="node.attributes['description-formatted'].trim()"></studip-tooltip-icon>
-            <input v-if="isAssignable && node.attributes.id !== 'root'" type="hidden" :name="assignmentAction"
-                   :value="node.attributes.id">
-            <a v-if="editable && node.attributes.id !== 'root'" :href="editUrl + '/' + node.attributes.id"
-               @click.prevent="editNode(editUrl, node.id)" data-dialog="size=medium"
-               class="studip-tree-node-edit-link">
-                <studip-icon shape="edit"></studip-icon>
-            </a>
-        </span>
-        <div v-if="isLoading" class="studip-spinner">
-            <studip-asset-img file="ajax-indicator-black.svg" width="20"/>
-            {{ $gettext('Daten werden geladen...' )}}
-        </div>
-        <ul v-if="node.attributes['has-children'] && openState" class="studip-tree-children">
-            <li v-for="(child) in children" :key="child.id" >
-                <studip-tree-node :node="child" :editable="editable" :edit-url="editUrl" :create-url="createUrl"
-                                  :delete-url="deleteUrl" :assignable="assignable" :ancestors="theAncestors"
-                                  :not-assignable-nodes="notAssignableNodes" :open-nodes="openNodes"
-                                  :open-levels="openLevels > 0 ? (openLevels - 1) : 0"
-                                  :visible-children-only="visibleChildrenOnly"
-                                  :active-node="activeNode" :with-info="withInfo"></studip-tree-node>
-            </li>
-        </ul>
-    </section>
-</template>
-
-<script>
-import { TreeMixin } from '../../mixins/TreeMixin';
-import StudipIcon from '../StudipIcon.vue';
-import StudipAssetImg from '../StudipAssetImg.vue';
-import axios from 'axios';
-import StudipTooltipIcon from '../StudipTooltipIcon.vue';
-
-export default {
-    name: 'StudipTreeNode',
-    components: { StudipTooltipIcon, StudipAssetImg, StudipIcon },
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        activeNode: {
-            type: Object,
-            default: null
-        },
-        isOpen: {
-            type: Boolean,
-            default: false
-        },
-        breadcrumbIcon: {
-            type: String,
-            default: 'literature'
-        },
-        withInfo: {
-            type: Boolean,
-            default: true
-        },
-        editable: {
-            type: Boolean,
-            default: false
-        },
-        editUrl: {
-            type: String,
-            default: ''
-        },
-        createUrl: {
-            type: String,
-            default: ''
-        },
-        deleteUrl: {
-            type: String,
-            default: ''
-        },
-        visibleChildrenOnly: {
-            type: Boolean,
-            default: true
-        },
-        withCourses: {
-            type: Boolean,
-            default: true
-        },
-        assignable: {
-            type: Boolean,
-            default: false
-        },
-        assignLeavesOnly: {
-            type: Boolean,
-            default: false
-        },
-        notAssignableNodes: {
-            type: Array,
-            default: () => []
-        },
-        openLevels: {
-            type: Number,
-            default: 0
-        },
-        openNodes: {
-            type: Array,
-            default: () => []
-        },
-        ancestors: {
-            type: Array,
-            default: () => []
-        }
-    },
-    data() {
-        return {
-            isLoading: false,
-            childrenLoaded: false,
-            children: [],
-            semester: 'all',
-            openState: this.isOpen,
-            theAncestors: this.ancestors,
-            assignedCourses: 0,
-            assignmentState: 0,
-            assignmentAction: ''
-        }
-    },
-    methods: {
-        toggleNode(emitEvent = false) {
-            this.courses = [];
-            this.openState = !this.openState;
-            if (emitEvent) {
-                STUDIP.eventBus.emit('load-tree-node', this.node.id);
-            }
-            if (!this.childrenLoaded) {
-                this.children = [];
-                const loadingIndicator = axios.interceptors.request.use(config => {
-                    setTimeout(() => {
-                        if (!this.childrenLoaded) {
-                            this.isLoading = true;
-                        }
-                    }, 500);
-                    return config;
-                });
-                this.getNodeChildren(this.node, this.visibleChildrenOnly)
-                    .then(response => {
-                        this.isLoading = false;
-                        this.children = response.data.data;
-                        this.childrenLoaded = true;
-                    });
-                axios.interceptors.request.eject(loadingIndicator);
-            }
-        },
-        /**
-         * Check whether currently selected course are assigned to this node.
-         */
-        checkAssignments() {
-            const courses = document.querySelectorAll('table.selected-courses input[name="courses[]"]') ?? [];
-            let ids = [];
-            for (const course of courses) {
-                ids.push(course.value);
-            }
-
-            if (ids.length > 0) {
-                this.getNodeCourses(this.node, 'all', 0, '', false, ids)
-                    .then(response => {
-                        // None of the given courses are assigned here.
-                        if (response.data.data.length === 0) {
-                            this.assignedCourses = this.assignmentState = 0;
-                        // All of the given courses are assigned here.
-                        } else if (response.data.data.length === ids.length) {
-                            this.assignedCourses = this.assignmentState = 1;
-                        // Some of the given courses are assigned here.
-                        } else {
-                            this.assignedCourses = this.assignmentState = -1;
-                        }
-                    });
-            }
-        },
-        /**
-         * Change what shall be done on submitting the form.
-         */
-        changeAssignmentState() {
-            // Current state is 0 -> remove all assignments.
-            if (this.assignmentState === 0) {
-                // Not all courses are assigned here -> next state is indeterminate.
-                if (this.assignedCourses === -1) {
-                    this.assignmentState = -1;
-                // Next state is 1 -> add assignments here.
-                } else {
-                    this.assignmentState = 1;
-                }
-            // Current state is 1 -> next state is 0 -> remove assignments here.
-            } else if (this.assignmentState === 1) {
-                this.assignmentState = 0;
-            // Current state is indeterminate -> next state is 1 -> add assignments here.
-            } else {
-                this.assignmentState = 1;
-            }
-
-            // Current state returned to original, nothing needs to be done.
-            if (this.assignmentState === this.assignedCourses) {
-                this.assignmentAction = '';
-            // Current state is different from original state -> add or remove.
-            } else {
-                switch (this.assignmentState) {
-                    case 0:
-                        this.assignmentAction = 'delete_assignments[]';
-                        break;
-                    case 1:
-                        this.assignmentAction = 'add_assignments[]';
-                        break;
-                }
-            }
-
-        }
-    },
-    computed: {
-        isAssignable() {
-            return this.assignable && !this.notAssignableNodes?.includes(this.node.id);
-        }
-    },
-    mounted() {
-        if (this.openLevels > 0) {
-            this.toggleNode();
-        }
-
-        if (this.ancestors.length === 0) {
-            for (const open of this.openNodes) {
-                this.getNode(open).then((response) => {
-                    const haystack = response.data.data.attributes.ancestors?.map(element => {
-                        return element.classname + '_' + element.id;
-                    });
-                    if (haystack) {
-                        this.theAncestors = haystack;
-                        if (this.theAncestors.includes(this.node.id)) {
-                            this.toggleNode();
-                        }
-                    }
-                });
-
-            }
-        }
-
-        this.globalOn('sort-tree-children', data => {
-            if (this.node.id === data.parent) {
-                this.children = data.children;
-            }
-        });
-
-        this.$nextTick(() => {
-            if (this.theAncestors?.includes(this.node.id) && !this.openState) {
-                this.toggleNode();
-            }
-            if (this.isAssignable && this.node.attributes.id !== 'root') {
-                this.checkAssignments();
-            }
-        });
-    },
-    beforeDestroy() {
-        STUDIP.eventBus.off('sort-tree-children');
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/StudipTreeTable.vue b/resources/vue/components/tree/StudipTreeTable.vue
deleted file mode 100644
index 0bfc244fb7554d8b48fdec1d034795a1d32a6226..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/StudipTreeTable.vue
+++ /dev/null
@@ -1,377 +0,0 @@
-<template>
-    <div v-if="isLoading">
-        <studip-progress-indicator></studip-progress-indicator>
-    </div>
-    <article v-else class="studip-tree-table">
-        <header>
-            <tree-breadcrumb v-if="currentNode.id !== 'root'" :node="currentNode"
-                             :icon="breadcrumbIcon" :editable="editable" :edit-url="editUrl" :create-url="createUrl"
-                             :delete-url="deleteUrl" :show-navigation="showStructureAsNavigation"
-                             :num-children="children.length" :num-courses="courses.length"
-                             :assignable="assignable" :visible-children-only="visibleChildrenOnly"></tree-breadcrumb>
-        </header>
-        <section v-if="withChildren && !currentNode.attributes['has-children']" class="studip-tree-node-no-children">
-            {{ $gettext('Auf dieser Ebene existieren keine weiteren Unterebenen.')}}
-        </section>
-
-        <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
-
-        <div v-if="currentNode.attributes.description?.trim() !== ''"
-             v-html="currentNode.attributes['description-formatted']"></div>
-
-        <section v-if="thisLevelCourses === 0" class="studip-tree-node-no-courses">
-            {{ $gettext('Auf dieser Ebene sind keine Veranstaltungen zugeordnet.')}}
-        </section>
-
-        <section v-if="thisLevelCourses + subLevelsCourses > 0">
-            <span v-if="withCourses && showingAllCourses">
-                <button type="button" @click="showAllCourses(false)"
-                        :title="$gettext('Veranstaltungen auf dieser Ebene anzeigen')">
-                    {{ $gettext('Veranstaltungen auf dieser Ebene anzeigen') }}
-                </button>
-            </span>
-            <template v-if="thisLevelCourses > 0 && subLevelsCourses > 0">
-                |
-            </template>
-            <span v-if="withCourses && subLevelsCourses > 0 && !showingAllCourses">
-                <button type="button" @click="showAllCourses(true)"
-                        :title="$gettext('Veranstaltungen auf allen Unterebenen anzeigen')">
-                    {{ $gettext('Veranstaltungen auf allen Unterebenen anzeigen') }}
-                </button>
-            </span>
-        </section>
-
-        <table v-if="currentNode.attributes['has-children'] || courses.length > 0" class="default">
-            <caption class="studip-tree-node-info">
-                <span v-if="withChildren && children.length > 0">
-                    {{ $gettextInterpolate($gettext('%{ count } Unterebenen'), { count: children.length }) }}
-                </span>
-                <span v-if="withChildren && children.length > 0 && withCourses && courses.length > 0">
-                    ,
-                </span>
-            </caption>
-            <colgroup>
-                <col style="width: 20px">
-                <col style="width: 30px">
-                <col>
-                <col style="width: 40%">
-            </colgroup>
-            <thead>
-                <tr>
-                    <th></th>
-                    <th>{{ $gettext('Typ') }}</th>
-                    <th>{{ $gettext('Name') }}</th>
-                    <th>{{ $gettext('Information') }}</th>
-                </tr>
-            </thead>
-            <draggable v-model="children" handle=".drag-handle" :animation="300"
-                       @end="dropChild" tag="tbody" role="listbox">
-                <tr v-for="(child, index) in children" :key="index" class="studip-tree-child">
-                    <td>
-                        <a v-if="editable && children.length > 1" class="drag-link" role="option"
-                           tabindex="0"
-                           :title="$gettextInterpolate($gettext('Sortierelement für Element %{node}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {node: child.attributes.name})"
-                           @keydown="keyHandler($event, index)"
-                           :ref="'draghandle-' + index">
-                            <span class="drag-handle"></span>
-                        </a>
-                    </td>
-                    <td>
-                        <studip-icon :shape="child.attributes['has-children'] ? 'folder-full' : 'folder-empty'"
-                                     :size="26"></studip-icon>
-                    </td>
-                    <td>
-                        <a :href="nodeUrl(child.id, semester !== 'all' ? semester : null)" tabindex="0"
-                           @click.prevent="openNode(child)"
-                           :title="$gettextInterpolate($gettext('Unterebene %{ node } öffnen'),
-                                { node: node.attributes.name })">
-                            {{ child.attributes.name }}
-                        </a>
-                    </td>
-                    <td>
-                        <tree-node-course-info :node="child" :semester="semester"
-                                               :sem-class="semClass"></tree-node-course-info>
-                    </td>
-                </tr>
-                <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
-                    <td></td>
-                    <td>
-                        <studip-icon shape="seminar" :size="26"></studip-icon>
-                    </td>
-                    <td>
-                        <a :href="courseUrl(course.id)" tabindex="0"
-                           :title="$gettextInterpolate($gettext('Zur Veranstaltung %{ course }'),
-                                { course: course.attributes.title })">
-                            <template v-if="course.attributes['course-number']">
-                                {{ course.attributes['course-number'] }}
-                            </template>
-                            {{ course.attributes.title }}
-                        </a>
-                        <div :id="'course-dates-' + course.id" class="course-dates"></div>
-                    </td>
-                    <td :colspan="editable ? 2 : null">
-                        <tree-course-details :course="course.id"></tree-course-details>
-                    </td>
-                </tr>
-            </draggable>
-        </table>
-        <MountingPortal v-if="withExport" mountTo="#export-widget" name="sidebar-export">
-            <tree-export-widget v-if="courses.length > 0" :title="$gettext('Download des Ergebnisses')" :url="exportUrl()"
-                                :export-data="courses"></tree-export-widget>
-        </MountingPortal>
-        <MountingPortal v-if="withCourseAssign" mountTo="#assign-widget" name="sidebar-assign-courses">
-            <assign-link-widget v-if="courses.length > 0" :node="currentNode" :courses="courses"></assign-link-widget>
-        </MountingPortal>
-    </article>
-</template>
-
-<script>
-import draggable from 'vuedraggable';
-import { TreeMixin } from '../../mixins/TreeMixin';
-import TreeExportWidget from './TreeExportWidget.vue';
-import TreeBreadcrumb from './TreeBreadcrumb.vue';
-import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import StudipIcon from '../StudipIcon.vue';
-import TreeNodeCourseInfo from './TreeNodeCourseInfo.vue';
-import TreeCourseDetails from "./TreeCourseDetails.vue";
-import AssignLinkWidget from "./AssignLinkWidget.vue";
-
-export default {
-    name: 'StudipTreeTable',
-    components: {
-        draggable, TreeExportWidget, TreeCourseDetails, StudipIcon, StudipProgressIndicator, TreeBreadcrumb,
-        TreeNodeCourseInfo, AssignLinkWidget
-    },
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        breadcrumbIcon: {
-            type: String,
-            default: 'literature'
-        },
-        editable: {
-            type: Boolean,
-            default: false
-        },
-        editUrl: {
-            type: String,
-            default: ''
-        },
-        createUrl: {
-            type: String,
-            default: ''
-        },
-        deleteUrl: {
-            type: String,
-            default: ''
-        },
-        withCourses: {
-            type: Boolean,
-            default: false
-        },
-        withExport: {
-            type: Boolean,
-            default: false
-        },
-        withChildren: {
-            type: Boolean,
-            default: true
-        },
-        visibleChildrenOnly: {
-            type: Boolean,
-            default: true
-        },
-        assignable: {
-            type: Boolean,
-            default: false
-        },
-        withCourseAssign: {
-            type: Boolean,
-            default: false
-        },
-        semester: {
-            type: String,
-            default: ''
-        },
-        semClass: {
-            type: Number,
-            default: 0
-        },
-        showStructureAsNavigation: {
-            type: Boolean,
-            default: false
-        }
-    },
-    data() {
-        return {
-            currentNode: this.node,
-            isLoading: false,
-            isLoaded: false,
-            children: [],
-            courses: [],
-            assistiveLive: '',
-            subLevelsCourses: 0,
-            thisLevelCourses: 0,
-            showingAllCourses: false
-        }
-    },
-    methods: {
-        openNode(node, pushState = true) {
-            this.currentNode = node;
-            this.$emit('change-current-node', node);
-
-            if (this.withChildren) {
-                this.getNodeChildren(node, this.visibleChildrenOnly).then(response => {
-                    this.children = response.data.data;
-                });
-            }
-
-            this.getNodeCourseInfo(node, this.semester, this.semClass)
-                .then(response => {
-                    this.thisLevelCourses = response?.data.courses;
-                    this.subLevelsCourses = response?.data.allCourses;
-                });
-
-            if (this.withCourses) {
-
-                this.getNodeCourses(node, this.semester, this.semClass, '', false)
-                    .then(response => {
-                        this.courses = response.data.data;
-                    });
-            }
-
-            // Update browser history.
-            if (pushState) {
-                const nodeId = node.id;
-                const url = STUDIP.URLHelper.getURL('', {node_id: nodeId});
-                window.history.pushState({nodeId}, '', url);
-            }
-
-            // Update node_id for semester selector.
-            const semesterSelector = document.querySelector('#semester-selector-node-id');
-            semesterSelector.value = node.id;
-        },
-        dropChild() {
-            this.updateSorting(this.currentNode.id, this.children);
-        },
-        keyHandler(e, index) {
-            switch (e.keyCode) {
-                case 38: // up
-                    e.preventDefault();
-                    this.decreasePosition(index);
-                    this.$nextTick(() => {
-                        this.$refs['draghandle-' + (index - 1)][0].focus();
-                        this.assistiveLive = this.$gettextInterpolate(
-                            this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
-                            { pos: index, listLength: this.children.length }
-                        );
-                    });
-                    break;
-                case 40: // down
-                    e.preventDefault();
-                    this.increasePosition(index);
-                    this.$nextTick(function () {
-                        this.$refs['draghandle-' + (index + 1)][0].focus();
-                        this.assistiveLive = this.$gettextInterpolate(
-                            this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
-                            { pos: index + 2, listLength: this.children.length }
-                        );
-                    });
-                    break;
-            }
-        },
-        decreasePosition(index) {
-            if (index > 0) {
-                const temp = this.children[index - 1];
-                this.children[index - 1] = this.children[index];
-                this.children[index] = temp;
-                this.updateSorting(this.currentNode.id, this.children);
-            }
-        },
-        increasePosition(index) {
-            if (index < this.children.length) {
-                const temp = this.children[index + 1];
-                this.children[index + 1] = this.children[index];
-                this.children[index] = temp;
-                this.updateSorting(this.currentNode.id, this.children);
-            }
-        },
-        showAllCourses(state) {
-            this.getNodeCourses(this.currentNode, this.semester, this.semClass, '', state)
-                .then(courses => {
-                    this.courses = courses.data.data;
-                    this.showingAllCourses = state;
-                });
-        }
-    },
-    mounted() {
-        if (this.withChildren) {
-            this.getNodeChildren(this.node, this.visibleChildrenOnly).then(response => {
-                this.children = response.data.data;
-            });
-        }
-
-        this.getNodeCourseInfo(this.currentNode, this.semester, this.semClass)
-            .then(response => {
-                this.thisLevelCourses = response?.data.courses;
-                this.subLevelsCourses = response?.data.allCourses;
-            });
-
-        if (this.withCourses) {
-            this.getNodeCourses(this.currentNode, this.semester, this.semClass)
-                .then(courses => {
-                    this.courses = courses.data.data;
-                });
-        }
-
-        this.globalOn('open-tree-node', node => {
-            STUDIP.eventBus.emit('cancel-search');
-            this.openNode(node);
-        });
-
-        this.globalOn('load-tree-node', id => {
-            STUDIP.eventBus.emit('cancel-search');
-            this.getNode(id).then(response => {
-                this.openNode(response.data.data);
-            });
-        });
-
-        this.globalOn('sort-tree-children', data => {
-            if (this.currentNode.id === data.parent) {
-                this.children = data.children;
-            }
-        });
-
-        window.addEventListener('popstate', (event) => {
-            if (event.state) {
-                if ('nodeId' in event.state) {
-                    this.getNode(event.state.nodeId).then(response => {
-                        this.openNode(response.data.data, false);
-                    });
-                }
-            } else {
-                this.openNode(this.node, false);
-            }
-        });
-
-        // Add current node to semester selector widget.
-        this.$nextTick(() => {
-            const semesterForm = document.querySelector('#semester-selector .sidebar-widget-content form');
-            const nodeField = document.createElement('input');
-            nodeField.id = 'semester-selector-node-id';
-            nodeField.type = 'hidden';
-            nodeField.name = 'node_id';
-            nodeField.value = this.node.id;
-            semesterForm.appendChild(nodeField);
-        });
-    },
-    beforeDestroy() {
-        STUDIP.eventBus.off('open-tree-node');
-        STUDIP.eventBus.off('load-tree-node');
-        STUDIP.eventBus.off('sort-tree-children');
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeBreadcrumb.vue b/resources/vue/components/tree/TreeBreadcrumb.vue
deleted file mode 100644
index 33b04b3c0a21ed310863957f07b177ef0dd3c06f..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeBreadcrumb.vue
+++ /dev/null
@@ -1,194 +0,0 @@
-<template>
-    <div class="studip-tree-breadcrumb contentbar">
-        <nav class="contentbar-nav"></nav>
-        <div :class="{'contentbar-wrapper-left': true, 'with-navigation': showNavigation, 'editable': editable,
-                'with-navigation-and-editable': showNavigation && editable}">
-            <studip-icon :shape="icon" :size="24"></studip-icon>
-            <nav v-if="node.attributes.ancestors" class="studip-tree-breadcrumb-list contentbar-nav">
-                <span v-for="(ancestor, index) in node.attributes.ancestors"
-                          :key="ancestor.id">
-                    <a :href="nodeUrl(ancestor.classname + '_' + ancestor.id)" :ref="ancestor.id"
-                       @click.prevent="openNode(ancestor.id, ancestor.classname)" tabindex="0"
-                       :id="'tree-breadcrumb-' + ancestor.id"
-                       :title="$gettextInterpolate($gettext('%{ node } öffnen'), { node: ancestor.name})">
-                        {{ ancestor.name }}
-                    </a>
-                    <template v-if="index !== node.attributes.ancestors.length - 1">
-                        /
-                    </template>
-                </span>
-            </nav>
-        </div>
-        <div class="contentbar-wrapper-right">
-            <div v-if="showNavigation" class="studip-tree-navigation-wrapper">
-                <button type="button" tabindex="0"
-                        :title="navigationOpen ? $gettext('Navigation schließen') : $gettext('Navigation öffnen')"
-                        @click.prevent="toggleNavigation" :aria-expanded="navigationOpen">
-                    <studip-icon shape="table-of-contents" :size="24"></studip-icon>
-                </button>
-                <article class="studip-tree-navigation" v-if="navigationOpen">
-                    <header>
-                        <h1>{{ $gettext('Inhalt') }}</h1>
-                        <button type="button" tabindex="0"
-                                @click.prevent="toggleNavigation">
-                            <studip-icon shape="decline" :size="24"></studip-icon>
-                        </button>
-                    </header>
-                    <studip-tree-node :with-info="false" :node="rootNode" :active-node="node" :open-nodes="[ node.id ]"
-                                      :visible-children-only="visibleChildrenOnly"></studip-tree-node>
-                </article>
-            </div>
-            <button v-if="assignable" type="submit" class="assign-button"
-                    :title="$gettext('Diesen Eintrag zuweisen')">
-                <studip-icon shape="arr_2right" :size="20"></studip-icon>
-            </button>
-            <studip-action-menu v-if="editable" :items="actionMenuItems()"
-                                @add-tree-node="addNode" @edit-tree-node="editNode" @delete-tree-node="deleteNode"/>
-        </div>
-    </div>
-</template>
-
-<script>
-import { TreeMixin } from '../../mixins/TreeMixin';
-import StudipIcon from '../StudipIcon.vue';
-import StudipTreeNode from './StudipTreeNode.vue';
-import axios from 'axios';
-
-export default {
-    name: 'TreeBreadcrumb',
-    components: { StudipIcon, StudipTreeNode },
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        icon: {
-            type: String,
-            required: true
-        },
-        editable: {
-            type: Boolean,
-            default: false
-        },
-        editUrl: {
-            type: String,
-            default: ''
-        },
-        createUrl: {
-            type: String,
-            default: ''
-        },
-        deleteUrl: {
-            type: String,
-            default: ''
-        },
-        showNavigation: {
-            type: Boolean,
-            default: false
-        },
-        assignable: {
-            type: Boolean,
-            default: false
-        },
-        numChildren: {
-            type: Number,
-            default: 0
-        },
-        numCourses: {
-            type: Number,
-            default: 0
-        },
-        visibleChildrenOnly: {
-            type: Boolean,
-            default: true
-        }
-    },
-    data() {
-        return {
-            navigationOpen: false,
-            rootNode: null
-        }
-    },
-    methods: {
-        openNode(id, classname) {
-            STUDIP.eventBus.emit('load-tree-node', classname + '_' + id);
-            this.$refs[id][0].focus();
-        },
-        actionMenuItems() {
-            let entries = [];
-
-            if (this.editable && this.createUrl !== '') {
-                entries.push({
-                    id: 'create',
-                    label: this.$gettext('Neues Unterelement anlegen'),
-                    icon: 'add',
-                    emit: 'add-tree-node',
-                    emitArguments: this.node
-                });
-            }
-
-            if (this.editable && this.node.attributes.id !== 'root') {
-                entries.push({
-                    id: 'edit',
-                    label: this.$gettext('Dieses Element bearbeiten'),
-                    icon: 'edit',
-                    emit: 'edit-tree-node',
-                    emitArguments: this.node
-                });
-                entries.push({
-                    id: 'delete',
-                    label: this.$gettext('Dieses Element löschen'),
-                    icon: 'trash',
-                    emit: 'delete-tree-node',
-                    emitArguments: this.node
-                });
-            }
-
-            return entries;
-        },
-        toggleNavigation() {
-            this.navigationOpen = !this.navigationOpen;
-        },
-        addNode(parent) {
-            STUDIP.Dialog.fromURL(this.createUrl + '/' + parent.id, { data: { from: this.nodeUrl(parent.id) }});
-        },
-        editNode(node) {
-            STUDIP.Dialog.fromURL(this.editUrl + '/' + node.id, { data: { from: this.nodeUrl(node.id) }});
-        },
-        deleteNode(node) {
-            let text = this.$gettext('Sind sie sicher, dass der Eintrag "%{ node }" gelöscht werden soll?');
-            let context = {
-                node: node.attributes.name
-            };
-
-            if (this.numChildren > 0 && this.numCourses === 0) {
-                text = this.$gettext('Sind sie sicher, dass der Eintrag "%{ node }" gelöscht werden soll? Er hat %{ children } Unterelemente.');
-                context.children = this.numChildren;
-            } else if (this.numChildren === 0 && this.numCourses > 0) {
-                text = this.$gettext('Sind sie sicher, dass der Eintrag "%{ node }" gelöscht werden soll? Er hat %{ courses } Veranstaltungszuordnungen.');
-                context.courses = this.numCourses;
-            } else if (this.numChildren > 0 && this.numCourses > 0) {
-                text = this.$gettext('Sind sie sicher, dass der Eintrag "%{ node }" gelöscht werden soll? Er hat %{ children } Unterelemente und %{ courses } Veranstaltungszuordnungen.');
-                context.children = this.numChildren;
-                context.courses = this.numCourses;
-            }
-
-            STUDIP.Dialog.confirm(
-                this.$gettextInterpolate(text, context)
-            ).done(() => {
-                axios.post(this.deleteUrl + '/' + node.id).then(() => {
-                    const parent = node.attributes.ancestors[node.attributes.ancestors.length - 2];
-                    window.location = this.nodeUrl(parent.classname + '_' + parent.id);
-                });
-            });
-        }
-    },
-    mounted() {
-        const root = this.node.attributes.ancestors[0];
-        this.getNode(root.classname + '_' + root.id).then(response => {
-            this.rootNode = response.data.data;
-        });
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeCourseDetails.vue b/resources/vue/components/tree/TreeCourseDetails.vue
deleted file mode 100644
index 30f101b2413912d3020f27fb8419a2c52d81b342..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeCourseDetails.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<template>
-    <div v-if="details" class="course-details">
-        <div class="semester">
-            ({{ details.semester }})
-        </div>
-        <div class="admission-state" v-if="details.admissionstate">
-            <studip-icon :shape="details.admissionstate.icon" :role="details.admissionstate.role"
-                         :title="details.admissionstate.info"></studip-icon>
-        </div>
-        <ul class="course-lecturers">
-            <li v-for="(lecturer, index) in details.lecturers" :key="index">
-                <a :href="profileUrl(lecturer.username)"
-                   :title="$gettextInterpolate($gettext('Zum Profil von %{ user }'),
-                        { user: lecturer.name })">
-                    {{ lecturer.name }}
-                </a>
-            </li>
-        </ul>
-        <MountingPortal :mountTo="'#course-dates-' + course" :append="true">
-            <span v-html="details.dates"></span>
-        </MountingPortal>
-    </div>
-</template>
-
-<script>
-import axios from 'axios';
-import { TreeMixin } from '../../mixins/TreeMixin';
-
-export default {
-    name: 'TreeCourseDetails',
-    mixins: [ TreeMixin ],
-    props: {
-        course: {
-            type: String,
-            required: true
-        }
-    },
-    data() {
-        return {
-            details: null
-        }
-    },
-    mounted() {
-        axios.get(
-            STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/course/details/' + this.course)
-        ).then(response => {
-            this.details = response.data;
-        });
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeExportWidget.vue b/resources/vue/components/tree/TreeExportWidget.vue
deleted file mode 100644
index 62b73b0378cde62eb48dcb80d5f4c2ee9db336b0..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeExportWidget.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-<template>
-    <sidebar-widget v-if="exportData.length > 0" id="export-widget" class="sidebar-export" :title="$gettext('Export')">
-        <template #content>
-            <form class="sidebar-export">
-                <studip-icon shape="export" :size="16"></studip-icon>
-                <a :href="url" :title="title" @click.prevent="createExport()">{{ title }}</a>
-            </form>
-        </template>
-    </sidebar-widget>
-</template>
-
-<script>
-import axios from 'axios';
-import SidebarWidget from '../SidebarWidget.vue';
-import StudipIcon from '../StudipIcon.vue';
-
-export default {
-    name: 'TreeExportWidget',
-    components: {
-        SidebarWidget, StudipIcon
-    },
-    props: {
-        url: {
-            type: String,
-            required: true
-        },
-        title: {
-            type: String,
-            required: true
-        },
-        exportData: {
-            type: Array,
-            default: () => []
-        }
-    },
-    methods: {
-        createExport() {
-            const fd = new FormData();
-            fd.append('courses', this.exportData.map(entry => entry.id));
-            axios.post(
-                this.url,
-                fd,
-                { headers: { 'Content-Type': 'multipart/form-data' }}
-            ).then(response => {
-                window.open(response.data);
-            });
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeNodeCourseInfo.vue b/resources/vue/components/tree/TreeNodeCourseInfo.vue
deleted file mode 100644
index db31d148c3f144ec4b221f91fd77667f5cf6f250..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeNodeCourseInfo.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
-    <div class="studip-tree-child-description">
-        <template v-if="showingAllCourses">
-            <div v-translate="{ count: courseCount }" :translate-n="courseCount"
-                  translate-plural="<strong>%{count}</strong> Veranstaltungen auf dieser Ebene.">
-                <strong>Eine</strong> Veranstaltung auf dieser Ebene.
-            </div>
-        </template>
-        <div v-else v-translate="{ count: courseCount }" :translate-n="courseCount"
-              translate-plural="<strong>%{count}</strong> Veranstaltungen auf dieser Ebene.">
-            <strong>Eine</strong> Veranstaltung auf dieser Ebene.
-        </div>
-        <template v-if="!showingAllCourses">
-            <div v-translate="{ count: allCourseCount }" :translate-n="allCourseCount"
-                  translate-plural="<strong>%{count}</strong> Veranstaltungen auf allen Unterebenen.">
-                <strong>Eine</strong> Veranstaltung auf allen Unterebenen.
-            </div>
-        </template>
-        <div v-else v-translate="{ count: allCourseCount }" :translate-n="allCourseCount"
-              translate-plural="<strong>%{count}</strong> Veranstaltungen auf allen Unterebenen.">
-            <strong>Eine</strong> Veranstaltung auf allen Unterebenen.
-        </div>
-    </div>
-</template>
-
-<script>
-import { TreeMixin } from '../../mixins/TreeMixin';
-
-export default {
-    name: 'TreeNodeCourseInfo',
-    mixins: [ TreeMixin ],
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        semester: {
-            type: String,
-            default: 'all'
-        },
-        semClass: {
-            type: Number,
-            default: 0
-        }
-    },
-    data() {
-        return {
-            courseCount: 0,
-            allCourseCount: 0,
-            showingAllCourses: false
-        }
-    },
-    methods: {
-        showAllCourses(state) {
-            this.showingAllCourses = state;
-            this.$emit('showAllCourses', state);
-        }
-    },
-    mounted() {
-        this.getNodeCourseInfo(this.node, this.semester, this.semClass)
-            .then(info => {
-                this.courseCount = info?.data.courses;
-                this.allCourseCount = info?.data.allCourses;
-            });
-    },
-    watch: {
-        node(newNode) {
-            this.getNodeCourseInfo(newNode, this.semester, this.semClass)
-                .then(info => {
-                    this.courseCount = info?.data.courses;
-                    this.allCourseCount = info?.data.allCourses;
-                });
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeNodeCoursePath.vue b/resources/vue/components/tree/TreeNodeCoursePath.vue
deleted file mode 100644
index 26ab88eb4d42e30cc0d6f1d1faa6b19e53ab9a7b..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeNodeCoursePath.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<template>
-    <div>
-        <studip-icon shape="info-circle" @click="togglePathInfo"></studip-icon>
-        <ul v-if="showPaths" class="studip-tree-course-path">
-            <li v-for="(path, pindex) in paths" :key="pindex">
-                <button @click.prevent="openNode(path[path.length - 1].id)">
-                    <template v-for="(segment) in path">
-                        / {{ segment.name }}
-                    </template>
-                </button>
-            </li>
-        </ul>
-    </div>
-</template>
-<script>
-import axios from 'axios';
-import StudipIcon from '../StudipIcon.vue';
-
-export default {
-    name: 'TreeNodeCoursePath',
-    components: { StudipIcon },
-    props: {
-        courseId: {
-            type: String,
-            required: true
-        },
-        nodeClass: {
-            type: String,
-            required: true
-        }
-    },
-    data() {
-        return {
-            paths: [],
-            showPaths: false
-        }
-    },
-    methods: {
-        openNode(id) {
-            STUDIP.eventBus.emit('load-tree-node', this.nodeClass + '_' + id);
-        },
-        togglePathInfo() {
-            this.showPaths = !this.showPaths;
-        }
-    },
-    mounted() {
-        axios.get(
-            STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/course/pathinfo/' + this.nodeClass + '/' + this.courseId)
-        ).then(response => {
-            this.paths = response.data;
-        });
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeNodeTile.vue b/resources/vue/components/tree/TreeNodeTile.vue
deleted file mode 100644
index cbb0679eaeffeedb844b823abf4a2a5c0293e955..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeNodeTile.vue
+++ /dev/null
@@ -1,46 +0,0 @@
-<template>
-    <a :href="url" @click.prevent="openNode" :title="$gettextInterpolate($gettext('Unterebene %{ node } öffnen'),
-                                { node: node.attributes.name })">
-        <p class="studip-tree-child-title">
-            {{ node.attributes.name }}
-        </p>
-
-        <tree-node-course-info :node="node" :semester="semester"
-                               :sem-class="semClass"></tree-node-course-info>
-    </a>
-</template>
-
-<script>
-import TreeNodeCourseInfo from './TreeNodeCourseInfo.vue';
-export default {
-    name: 'TreeNodeTile',
-    components: { TreeNodeCourseInfo },
-    props: {
-        node: {
-            type: Object,
-            required: true
-        },
-        url: {
-            type: String,
-            required: true
-        },
-        withChildren: {
-            type: Boolean,
-            default: true
-        },
-        semester: {
-            type: String,
-            default: 'all'
-        },
-        semClass: {
-            type: Number,
-            default: 0
-        }
-    },
-    methods: {
-        openNode() {
-            STUDIP.eventBus.emit('open-tree-node', this.node);
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/tree/TreeSearchResult.vue b/resources/vue/components/tree/TreeSearchResult.vue
deleted file mode 100644
index 19bc6b927a4c56e57d76f6b6a0c59add4642bbab..0000000000000000000000000000000000000000
--- a/resources/vue/components/tree/TreeSearchResult.vue
+++ /dev/null
@@ -1,85 +0,0 @@
-<template>
-    <div v-if="isLoading">
-        <studip-progress-indicator></studip-progress-indicator>
-    </div>
-    <article v-else class="studip-tree-table">
-        <table v-if="courses.length > 0" class="default studip-tree-table">
-            <caption>
-                <studip-icon shape="search" :size="20"></studip-icon>
-                {{ $gettextInterpolate($ngettext('Ein Eintrag für den Begriff "%{searchterm}" gefunden',
-                    '%{count} Einträge für den Begriff "%{searchterm}" gefunden', courses.length),
-                    { count: courses.length, searchterm: searchConfig.searchterm}) }}
-            </caption>
-            <colgroup>
-                <col style="width: 30px">
-                <col>
-                <col>
-            </colgroup>
-            <thead>
-            <tr>
-                <th></th>
-                <th>{{ $gettext('Name') }}</th>
-                <th>{{ $gettext('Information') }}</th>
-            </tr>
-            </thead>
-            <tbody>
-                <tr v-for="(course) in courses" :key="course.id" class="studip-tree-child studip-tree-course">
-                    <td>
-                        <studip-icon shape="seminar" :size="26"></studip-icon>
-                    </td>
-                    <td>
-                        <a :href="courseUrl(course.id)"
-                           :title="$gettextInterpolate($gettext('Zur Veranstaltung %{name}'), {name:  + course.attributes.title})">
-                            <template v-if="course.attributes['course-number']">
-                                {{ course.attributes['course-number'] }}
-                            </template>
-                            {{ course.attributes.title }}
-                            <div :id="'course-dates-' + course.id" class="course-dates"></div>
-                        </a>
-                        <tree-node-course-path :node-class="searchConfig.classname"
-                                               :course-id="course.id"></tree-node-course-path>
-                    </td>
-                    <td>
-                        <tree-course-details :course="course.id"></tree-course-details>
-                    </td>
-                </tr>
-            </tbody>
-        </table>
-    </article>
-</template>
-
-<script>
-import { TreeMixin } from '../../mixins/TreeMixin';
-import StudipProgressIndicator from '../StudipProgressIndicator.vue';
-import StudipIcon from '../StudipIcon.vue';
-import TreeNodeCoursePath from './TreeNodeCoursePath.vue';
-import TreeCourseDetails from './TreeCourseDetails.vue';
-
-export default {
-    name: 'TreeSearchResult',
-    components: { StudipIcon, StudipProgressIndicator, TreeNodeCoursePath, TreeCourseDetails },
-    mixins: [ TreeMixin ],
-    props: {
-        searchConfig: {
-            type: Object,
-            required: true
-        }
-    },
-    data() {
-        return {
-            node: null,
-            isLoading: false,
-            isLoaded: false,
-            courses: []
-        }
-    },
-    mounted() {
-        this.getNode(this.searchConfig.classname + '_root').then(response => {
-            this.getNodeCourses(response.data.data, this.searchConfig.semester,0, this.searchConfig.searchterm, true)
-                .then(courses => {
-                    this.courses = courses.data.data;
-                });
-        });
-    }
-}
-</script>
diff --git a/resources/vue/mixins/TreeMixin.js b/resources/vue/mixins/TreeMixin.js
deleted file mode 100644
index 9a0292ecf337bbe592bbea793b121336228db64f..0000000000000000000000000000000000000000
--- a/resources/vue/mixins/TreeMixin.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import axios from 'axios';
-
-export const TreeMixin = {
-    data() {
-        return {
-            showProgressIndicatorTimeout: 500
-        };
-    },
-    methods: {
-        async getNode(id) {
-            return axios.get(STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/' + id));
-        },
-        async getNodeChildren(node, visibleOnly = true) {
-            let parameters = {};
-
-            if (visibleOnly) {
-                parameters['filter[visible]'] = true;
-            }
-
-            return axios.get(
-                STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/' + node.id + '/children'),
-                { params: parameters }
-            );
-        },
-        async getNodeCourses(node, semesterId = 'all', semClass = 0, searchterm = '', recursive = false, ids = []) {
-            let parameters = {};
-
-            if (semesterId !== 'all' && semesterId !== '0') {
-                parameters['filter[semester]'] = semesterId;
-            }
-
-            if (searchterm !== '') {
-                parameters['filter[q]'] = searchterm;
-            }
-
-            if (semClass !== 0) {
-                parameters['filter[semclass]'] = semClass;
-            }
-
-            if (recursive) {
-                parameters['filter[recursive]'] = true;
-            }
-
-            if (ids.length > 0) {
-                parameters['filter[ids]'] = ids;
-            }
-
-            return axios.get(
-                STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/' + node.id + '/courses'),
-                {params: parameters}
-            );
-        },
-        async getNodeCourseInfo(node, semesterId, semClass = 0) {
-            let parameters = {};
-
-            if (semesterId !== 'all' && semesterId !== '0') {
-                parameters['filter[semester]'] = semesterId;
-            }
-
-            if (semClass !== 0) {
-                parameters['filter[semclass]'] = semClass;
-            }
-
-            return axios.get(
-                STUDIP.URLHelper.getURL('jsonapi.php/v1/tree-node/' + node.id + '/courseinfo'),
-                { params: parameters }
-            );
-        },
-        nodeUrl(node_id, semester = null ) {
-            return STUDIP.URLHelper.getURL('', { node_id, semester })
-        },
-        courseUrl(courseId) {
-            return STUDIP.URLHelper.getURL('dispatch.php/course/details', { cid: courseId })
-        },
-        profileUrl(username) {
-            return STUDIP.URLHelper.getURL('dispatch.php/profile', { username })
-        },
-        exportUrl() {
-            return STUDIP.URLHelper.getURL('dispatch.php/tree/export_csv');
-        },
-        editNode(editUrl, id) {
-            STUDIP.Dialog.fromURL(
-                editUrl + '/' + id,
-                {
-                    size: 'medium'
-                }
-            );
-        },
-        updateSorting(parentId, children) {
-            let data = {};
-
-            let position = 0;
-            for (const child of children) {
-                data[child.attributes.id] = position;
-                position++;
-            }
-
-            const fd = new FormData();
-            fd.append('sorting', JSON.stringify(data));
-            axios.post(
-                STUDIP.URLHelper.getURL('dispatch.php/admin/tree/sort/' + parentId),
-                fd,
-                { headers: { 'Content-Type': 'multipart/form-data' }}
-            );
-            STUDIP.Vue.emit('sort-tree-children', { parent: parentId, children: children });
-        }
-    }
-}