From 74b970a8275a3894744f9b9c4969ff4dce647782 Mon Sep 17 00:00:00 2001
From: David Siegfried <david.siegfried@uni-vechta.de>
Date: Thu, 15 Dec 2022 13:33:04 +0000
Subject: [PATCH] use SORM instead plain sql, closes #29

Closes #29

Merge request studip/studip!888
---
 app/controllers/admin/semester.php        |  25 +-
 app/controllers/admission/courseset.php   |   4 +-
 app/controllers/course/admission.php      |   2 +-
 app/controllers/course/basicdata.php      |  24 +-
 app/controllers/course/members.php        | 543 +++++++++++++++++-----
 app/controllers/course/statusgroups.php   |   5 +-
 app/controllers/course/studygroup.php     |  20 +-
 app/controllers/my_courses.php            |  61 +--
 lib/admission.inc.php                     |   2 +
 lib/classes/AutoInsert.class.php          |  42 +-
 lib/classes/MembersModel.php              |  87 ++--
 lib/classes/Seminar.class.php             | 299 +++++++-----
 lib/classes/UserManagement.class.php      |  86 ++--
 lib/functions.php                         |   5 +-
 lib/models/AdmissionApplication.class.php | 174 +++++++
 lib/models/CourseMember.class.php         | 265 +++++++++++
 tests/functional/_bootstrap.php           |   1 +
 17 files changed, 1230 insertions(+), 415 deletions(-)

diff --git a/app/controllers/admin/semester.php b/app/controllers/admin/semester.php
index 1fbfc7b80d2..2130390a132 100644
--- a/app/controllers/admin/semester.php
+++ b/app/controllers/admin/semester.php
@@ -411,18 +411,25 @@ class Admin_SemesterController extends AuthenticatedController
         }
 
         // Hide courses and set lock rule
-        $query = "UPDATE `seminare`
-                  SET `visible` = 0, `lock_rule` = ?
-                  WHERE `Seminar_id` IN (?)";
-        DBManager::get()->execute($query, [$lock_rule, $course_ids]);
+        Course::findEachMany(
+            function (Course $course) use ($lock_rule) {
+                $course->visible = 0;
+                $course->lock_rule = $lock_rule;
+                $course->store();
+            },
+            [$course_ids]
+        );
 
         // Degrade users
         if ($degrade_users) {
-            $query = "UPDATE `seminar_user`
-                      SET `status` = 'user'
-                      WHERE `Seminar_id` IN (?)
-                        AND `status` = 'autor'";
-            DBManager::get()->execute($query, [$course_ids]);
+            CourseMember::findEachBySQL(
+                function (CourseMember $cm) {
+                    $cm->status = 'user';
+                    $cm->store();
+                },
+                "`Seminar_id` IN (?) and `status` = 'autor'",
+                [$course_ids]
+            );
         }
 
         // Lock enrolment
diff --git a/app/controllers/admission/courseset.php b/app/controllers/admission/courseset.php
index 11b0672826c..7d73de45522 100644
--- a/app/controllers/admission/courseset.php
+++ b/app/controllers/admission/courseset.php
@@ -14,8 +14,6 @@
  * @since       3.0
  */
 
-require_once 'lib/admission.inc.php';
-
 class Admission_CoursesetController extends AuthenticatedController
 {
     /**
@@ -539,7 +537,7 @@ class Admission_CoursesetController extends AuthenticatedController
 
                     $ok += $course->store();
                     if ($do_update_admission) {
-                        update_admission($course->id);
+                        AdmissionApplication::addMembers($course->id);
                     }
                 }
             }
diff --git a/app/controllers/course/admission.php b/app/controllers/course/admission.php
index a4d83830655..a0d38170e41 100644
--- a/app/controllers/course/admission.php
+++ b/app/controllers/course/admission.php
@@ -46,7 +46,7 @@ class Course_AdmissionController extends AuthenticatedController
         if (!SeminarCategories::GetByTypeId($this->course->status)->write_access_nobody) {
             $this->is_locked['write_level'] = 'disabled readonly';
         }
-        update_admission($this->course->id);
+        AdmissionApplication::addMembers($this->course->id);
         PageLayout::addScript('studip-admission.js');
         URLHelper::addLinkParam('return_to_dialog', Request::get('return_to_dialog'));
     }
diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php
index 708e8db49f6..a2524b4a375 100644
--- a/app/controllers/course/basicdata.php
+++ b/app/controllers/course/basicdata.php
@@ -477,7 +477,7 @@ class Course_BasicdataController extends AuthenticatedController
 
             //update admission, if turnout was raised
             if($after['admission_turnout'] > $before['admission_turnout'] && $sem->isAdmissionEnabled()) {
-                update_admission($sem->getId());
+                AdmissionApplication::addMembers($sem->getId());
             }
 
             if (sizeof($before) && sizeof($after)) {
@@ -496,9 +496,21 @@ class Course_BasicdataController extends AuthenticatedController
         }
 
         //Labels/Funktionen für Dozenten und Tutoren
-        if ($perm->have_studip_perm("dozent", $sem->getId())) {
-            foreach (Request::getArray("label") as $user_id => $label) {
-                $sem->setLabel($user_id, $label);
+        if ($perm->have_studip_perm('dozent', $sem->getId())) {
+            foreach (Request::getArray('label') as $user_id => $label) {
+                if ($GLOBALS['perm']->have_studip_perm('tutor', $sem->getId(), $user_id)) {
+                    $mb = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $sem->getId()]);
+                    if ($mb) {
+                        $mb->label = $label;
+                        if ($mb->store()) {
+                            NotificationCenter::postNotification(
+                                'CourseDidChangeMemberLabel',
+                                $sem,
+                                $mb
+                            );
+                        }
+                    }
+                }
             }
         }
 
@@ -791,7 +803,7 @@ class Course_BasicdataController extends AuthenticatedController
                     $members[$key] = $temp_member;
                 }
             }
-            $sem->setMemberPriority($members, $status);
+            $sem->setMemberPriority($members);
         } else {
             $this->msg[] = ["error", _("Sie haben keine Berechtigung diese Veranstaltung zu verändern.")];
         }
@@ -828,7 +840,7 @@ class Course_BasicdataController extends AuthenticatedController
                     $members[$key] = $temp_member;
                 }
             }
-            $sem->setMemberPriority($members, $status);
+            $sem->setMemberPriority($members);
         } else {
             $this->msg[] = ["error", _("Sie haben keine Berechtigung diese Veranstaltung zu verändern.")];
         }
diff --git a/app/controllers/course/members.php b/app/controllers/course/members.php
index ad7d6dacf93..40ebef1c4cc 100644
--- a/app/controllers/course/members.php
+++ b/app/controllers/course/members.php
@@ -16,8 +16,6 @@
  */
 
 require_once 'lib/messaging.inc.php'; //Funktionen des Nachrichtensystems
-
-require_once 'lib/admission.inc.php'; //Funktionen der Teilnehmerbegrenzung
 require_once 'lib/export/export_studipdata_func.inc.php'; // Funktionne für den Export
 require_once 'lib/export/export_linking_func.inc.php';
 
@@ -88,11 +86,8 @@ class Course_MembersController extends AuthenticatedController
         ];
 
         //check for admission / waiting list
-        update_admission($this->course_id);
-
-        // Create new MembersModel, to get additionanl informations to a given Seminar
-        $this->members = new MembersModel($this->course_id, $this->course_title);
-        $this->members->checkUserVisibility();
+        AdmissionApplication::addMembers($this->course->id);
+        $this->checkUserVisibility();
     }
 
     public function index_action()
@@ -104,17 +99,28 @@ class Course_MembersController extends AuthenticatedController
         $sem                = Seminar::getInstance($this->course_id);
         $this->sort_by      = Request::option('sortby', 'nachname');
         $this->order        = Request::option('order', 'desc');
-        $this->sort_status  = Request::get('sort_status');
+        $this->sort_status  = Request::get('sort_status', '');
 
         Navigation::activateItem('/course/members/view');
         if (Request::int('toggle')) {
             $this->order = $this->order == 'desc' ? 'asc' : 'desc';
         }
 
-        $filtered_members = $this->members->getMembers($this->sort_status, $this->sort_by . ' ' . $this->order, !$this->is_tutor ? $this->user_id : null);
+        $filtered_members = CourseMember::getMembers(
+            $this->course_id,
+            $this->sort_status,
+            $this->sort_by . ' ' . $this->order
+        );
 
         if ($this->is_tutor) {
-            $filtered_members = array_merge($filtered_members, $this->members->getAdmissionMembers($this->sort_status, $this->sort_by . ' ' . $this->order ));
+            $filtered_members = array_merge(
+                $filtered_members,
+                AdmissionApplication::getAdmissionMembers(
+                    $this->course_id,
+                    $this->sort_status,
+                    $this->sort_by . ' ' . $this->order
+                )
+            );
             $this->awaiting = $filtered_members['awaiting']->toArray('user_id username vorname nachname visible mkdate');
             $this->accepted = $filtered_members['accepted']->toArray('user_id username vorname nachname visible mkdate');
             $this->claiming = $filtered_members['claiming']->toArray('user_id username vorname nachname visible mkdate');
@@ -269,7 +275,7 @@ class Course_MembersController extends AuthenticatedController
         } else {
             PageLayout::postError(_('Bemerkung konnte nicht erfolgreich gespeichert werden.'));
         }
-        $this->redirect('course/members/index');
+        $this->redirect($this->indexURL());
     }
 
     /**
@@ -287,19 +293,20 @@ class Course_MembersController extends AuthenticatedController
         $mp = MultiPersonSearch::load("add_autor" . $this->course_id);
 
         $countAdded = 0;
+        $msg = [];
         foreach ($mp->getAddedUsers() as $a) {
-            if($this->members->addMember($a, 'autor', Request::get('consider_contingent'))) {
+            if ($this->addMember($a, true, Request::bool('consider_contingent'), $msg)) {
                 $countAdded++;
             }
         }
 
         if ($countAdded == 1) {
-            $text = _("Es wurde eine neue Person hinzugefügt.");
+            $text = _('Es wurde eine neue Person hinzugefügt.');
         } else {
-            $text = sprintf(_("Es wurden %s neue Personen hinzugefügt."), $countAdded);
+            $text = sprintf(_('Es wurden %s neue Personen hinzugefügt.'), $countAdded);
         }
-        PageLayout::postSuccess($text);
-        $this->redirect('course/members/index');
+        PageLayout::postSuccess($text, $msg);
+        $this->redirect($this->indexURL());
     }
 
      /**
@@ -350,7 +357,7 @@ class Course_MembersController extends AuthenticatedController
         $countAdded = 0;
         $countFailed = 0;
         foreach ($mp->getAddedUsers() as $a) {
-            if ($this->members->addToWaitlist($a)) {
+            if ($this->addToWaitlist($a)) {
                 $countAdded++;
             } else {
                 $countFailed++;
@@ -503,22 +510,24 @@ class Course_MembersController extends AuthenticatedController
     public function send_to_course_action()
     {
         if ($target = $this->flash['target_course']) {
-            $msg = $this->members->sendToCourse(
-                $this->flash['users_to_send'],
+            $msg = $this->sendToCourse(
+                (array)$this->flash['users_to_send'],
                 $target,
                 $this->flash['move']
             );
             if ($msg['success']) {
-                if (sizeof($msg['success']) == 1) {
+                if (count($msg['success']) === 1) {
                     $text = _('Eine Person wurde in die Zielveranstaltung eingetragen.');
                 } else {
-                    $text = sprintf(_('%s Person(en) wurde(n) in die Zielveranstaltung eingetragen.'),
-                        sizeof($msg['success']));
+                    $text = sprintf(
+                        _('%s Person(en) wurde(n) in die Zielveranstaltung eingetragen.'),
+                        count($msg['success'])
+                    );
                 }
                 PageLayout::postSuccess($text);
             }
             if ($msg['existing']) {
-                if (sizeof($msg['existing']) == 1) {
+                if (count($msg['existing']) === 1) {
                     $text = _('Eine Person ist bereits in die Zielveranstaltung eingetragen ' .
                                 'und kann daher nicht verschoben/kopiert werden.');
                 } else {
@@ -529,7 +538,7 @@ class Course_MembersController extends AuthenticatedController
                 PageLayout::postInfo($text);
             }
             if ($msg['failed']) {
-                if (sizeof($msg['failed']) == 1) {
+                if (count($msg['failed']) === 1) {
                     $text = _('Eine Person kann nicht in die Zielveranstaltung eingetragen werden.');
                 } else {
                     $text = sprintf(_('%s Person(en) konnten nicht in die Zielveranstaltung eingetragen werden.'),
@@ -540,7 +549,7 @@ class Course_MembersController extends AuthenticatedController
         } else {
             PageLayout::postError(_('Bitte wählen Sie eine Zielveranstaltung.'));
         }
-        $this->redirect('course/members/index');
+        $this->redirect($this->indexURL());
     }
 
     /**
@@ -578,6 +587,7 @@ class Course_MembersController extends AuthenticatedController
             Navigation::activateItem('/course/members/view');
         }
         $datafields = DataField::getDataFields('user', 1 | 2 | 4 | 8, true);
+        $accessible_df = [];
         foreach ($datafields as $df) {
             if ($df->accessAllowed() && in_array($df->getId(), $GLOBALS['TEILNEHMER_IMPORT_DATAFIELDS'])) {
                 $accessible_df[] = $df;
@@ -617,12 +627,13 @@ class Course_MembersController extends AuthenticatedController
                 }
             }
         }
-
+        $csv_count_contingent_full = 0;
+        $csv_count_present = 0;
+        $csv_not_found = [];
+        $consider_contingent = false;
         if (Request::get('csv_import')) {
             // remove duplicate users from csv-import
             $csv_lines = array_unique($csv_request);
-            $csv_count_contingent_full = 0;
-
             foreach ($csv_lines as $csv_line) {
                 $csv_name = preg_split('/[,\t]/', mb_substr($csv_line, 0, 100), -1, PREG_SPLIT_NO_EMPTY);
                 $csv_nachname = trim($csv_name[0]);
@@ -632,25 +643,22 @@ class Course_MembersController extends AuthenticatedController
                     continue;
                 }
 
-                if (Request::option('csv_import_format') == 'realname') {
-                    $csv_users = $this->members->getMemberByIdentification($csv_nachname, $csv_vorname);
-                } elseif (Request::option('csv_import_format') == 'username') {
-                    $csv_users = $this->members->getMemberByUsername($csv_nachname);
-                } elseif (Request::option('csv_import_format') == 'email') {
-                    $csv_users = $this->members->getMemberByEmail($csv_nachname);
+                if (Request::option('csv_import_format') === 'realname') {
+                    $csv_users = CourseMember::getMemberByIdentification($this->course_id, $csv_nachname, $csv_vorname);
+                } elseif (Request::option('csv_import_format') === 'username') {
+                    $csv_users = CourseMember::getMemberByUsername($this->course_id, $csv_nachname);
+                } elseif (Request::option('csv_import_format') === 'email') {
+                    $csv_users = CourseMember::getMemberByEmail($this->course_id, $csv_nachname);
                 } else {
-                    $csv_users = $this->members->getMemberByDatafield($csv_nachname, $datafield_id);
+                    $csv_users = CourseMember::getMemberByDatafield($this->course_id, $csv_nachname, $datafield_id);
                 }
 
                 // if found more then one result to given name
                 if (count($csv_users) > 1) {
-
                     // if user have two accounts
                     foreach ($csv_users as $row) {
-
                         if ($row['is_present']) {
                             $csv_count_double++;
-
                         } else {
                             $csv_mult_founds[$csv_line][] = $row;
                         }
@@ -664,7 +672,7 @@ class Course_MembersController extends AuthenticatedController
                     if (!$row['is_present']) {
                         $consider_contingent = Request::option('consider_contingent_csv');
 
-                        if (insert_seminar_user($this->course_id, $row['user_id'], 'autor', isset($consider_contingent), $consider_contingent)) {
+                        if (CourseMember::insertCourseMember($this->course_id, $row['user_id'], 'autor', isset($consider_contingent), $consider_contingent)) {
                             $csv_count_insert++;
                             setTempLanguage($this->user_id);
 
@@ -689,10 +697,13 @@ class Course_MembersController extends AuthenticatedController
         if (!empty($selected_users) && count($selected_users) > 0) {
             foreach ($selected_users as $selected_user) {
                 if ($selected_user) {
-                    if (insert_seminar_user($this->course_id, get_userid($selected_user), 'autor', isset($consider_contingent), $consider_contingent)) {
+                    if (CourseMember::insertCourseMember($this->course_id, get_userid($selected_user), 'autor', isset($consider_contingent), $consider_contingent)) {
                         $csv_count_insert++;
                         setTempLanguage($this->user_id);
-                        $message = sprintf(_('Sie wurden manuell in die Veranstaltung **%s** eingetragen.'), $this->course_title);
+                        $message = sprintf(_('Sie wurden manu
+
+                        ell in die Veranstaltung **%s** eingetragen.'), $this->course_title);
+
                         restoreLanguage();
                         $messaging->insert_message($message, $selected_user, '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')), TRUE);
                     } elseif (isset($consider_contingent)) {
@@ -742,9 +753,8 @@ class Course_MembersController extends AuthenticatedController
      */
     public function csv_manual_assignment_action()
     {
-        global $perm;
         // Security. If user not autor, then redirect to index
-        if (!$perm->have_studip_perm('tutor', $this->course_id)) {
+        if (!$GLOBALS['perm']->have_studip_perm('tutor', $this->course_id)) {
             throw new AccessDeniedException();
         }
 
@@ -755,27 +765,35 @@ class Course_MembersController extends AuthenticatedController
 
     /**
      * Change the visibilty of an autor
+     * @return void
      */
     public function change_visibility_action($cmd, $mode)
     {
-        global $perm;
         // Security. If user not autor, then redirect to index
-        if ($perm->have_studip_perm('tutor', $this->course_id)) {
+        if ($GLOBALS['perm']->have_studip_perm('tutor', $this->course_id)) {
             throw new AccessDeniedException();
         }
 
         // Check for visibile mode
-        if ($cmd == 'make_visible') {
+        if ($cmd === 'make_visible') {
             $command = 'yes';
         } else {
             $command = 'no';
         }
 
-        if ($mode == 'awaiting') {
-            $result = $this->members->setAdmissionVisibility($this->user_id, $command);
+        if ($mode === 'awaiting') {
+            $model = AdmissionApplication::findOneBySQL(
+                'user_id = ? AND seminar_id = ?',
+                [$this->user_id, $this->course_id]
+            );
         } else {
-            $result = $this->members->setVisibilty($this->user_id, $command);
+            $model = CourseMember::findOneBySQL(
+                'user_id = ? AND Seminar_id = ?',
+                [$this->user_id, $this->course_id]
+            );
         }
+        $model->visible = $command;
+        $result = $model->store();
 
         if ($result > 0) {
             PageLayout::postSuccess(_('Ihre Sichtbarkeit wurde erfolgreich geändert.'));
@@ -800,9 +818,6 @@ class Course_MembersController extends AuthenticatedController
 
         // select the additional method
         switch (Request::get('action_tutor')) {
-            case '':
-                $target = 'course/members/index';
-                break;
             case 'downgrade':
                 $target = 'course/members/downgrade_user/tutor/autor';
                 break;
@@ -834,9 +849,6 @@ class Course_MembersController extends AuthenticatedController
         $this->flash['users'] = Request::getArray('autor');
 
         switch (Request::get('action_autor')) {
-            case '':
-                $target = 'course/members/index';
-                break;
             case 'upgrade':
                 $target = 'course/members/upgrade_user/autor/tutor';
                 break;
@@ -883,9 +895,6 @@ class Course_MembersController extends AuthenticatedController
 
         // select the additional method
         switch (Request::get('action_user')) {
-            case '':
-                $target = 'course/members/index';
-                break;
             case 'upgrade':
                 $target = 'course/members/upgrade_user/user/autor';
                 break;
@@ -929,9 +938,6 @@ class Course_MembersController extends AuthenticatedController
         $waiting_type = Request::option('waiting_type');
         // select the additional method
         switch (Request::get('action_awaiting')) {
-            case '':
-                $target = 'course/members/index';
-                break;
             case 'upgrade_autor':
                 $target = 'course/members/insert_admission/awaiting/collection';
                 break;
@@ -969,9 +975,6 @@ class Course_MembersController extends AuthenticatedController
 
         // select the additional method
         switch (Request::get('action_accepted')) {
-            case '':
-                $target = 'course/members/index';
-                break;
             case 'upgrade':
                 $target = 'course/members/insert_admission/accepted/collection';
                 break;
@@ -1015,12 +1018,17 @@ class Course_MembersController extends AuthenticatedController
         }
 
         if ($users) {
-            $msgs = $this->members->insertAdmissionMember($users, $target_status, Request::get('consider_contingent'), $status == 'accepted');
+            $msgs = $this->insertAdmissionMember(
+                $users,
+                $target_status,
+                Request::bool('consider_contingent'),
+                $status === 'accepted'
+            );
             if ($msgs) {
-                if ($cmd == 'add_user') {
+                if ($cmd === 'add_user') {
                     $message = sprintf(_('%s wurde in die Veranstaltung mit dem Status <b>%s</b> eingetragen.'), htmlReady(join(',', $msgs)), $this->decoratedStatusGroups['autor']);
                 } else {
-                    if ($status == 'awaiting') {
+                    if ($status === 'awaiting') {
                         $message = sprintf(_('%s wurde aus der Anmelde bzw. Warteliste mit dem Status
                             <b>%s</b> in die Veranstaltung eingetragen.'), htmlReady(join(', ', $msgs)), $this->decoratedStatusGroups[$target_status]);
                     } else {
@@ -1053,17 +1061,16 @@ class Course_MembersController extends AuthenticatedController
         if (!$this->is_tutor) {
             throw new AccessDeniedException();
         }
-
+        $course = Seminar::GetInstance($this->course_id);
         if (!Request::submitted('no')) {
-
             if (Request::submitted('yes')) {
                 CSRFProtection::verifyUnsafeRequest();
                 $users = Request::getArray('users');
                 if (!empty($users)) {
                     if (in_array($status, words('accepted awaiting claiming'))) {
-                        $msgs = $this->members->cancelAdmissionSubscription($users, $status);
+                        $msgs = $course->cancelAdmissionSubscription($users, $status);
                     } else {
-                        $msgs = $this->members->cancelSubscription($users);
+                        $msgs = $course->cancelSubscription($users);
                     }
 
                     // deleted authors
@@ -1106,36 +1113,26 @@ class Course_MembersController extends AuthenticatedController
                         htmlReady($this->status_groups[$status])
                     )
                 )->setAcceptURL(
-                    $this->url_for("course/members/cancel_subscription/collection/{$status}"),
+                    $this->cancel_subscriptionURL('collection', $status),
                     compact('users')
                 );
                 $this->flash['checked'] = $users;
             }
         }
-
-
-
-        $this->redirect('course/members/index');
+        $this->redirect($this->indexURL());
     }
 
     /**
      * Upgrade a user to a selected status
-     * @param type $status
-     * @param type $next_status
-     * @param type $username
-     * @param type $cmd
+     * @param string $status
+     * @param string $next_status
      * @throws AccessDeniedException
      */
     public function upgrade_user_action($status, $next_status)
     {
-        global $perm;
-
-        // Security Check
-        if (!$this->is_tutor) {
-            throw new AccessDeniedException();
-        }
-
-        if ($this->is_tutor && $perm->have_studip_perm('tutor', $this->course_id) && $next_status != 'autor' && !$perm->have_studip_perm('dozent', $this->course_id)) {
+         if ($GLOBALS['perm']->have_studip_perm('tutor', $this->course_id)
+            && $next_status !== 'autor'
+            && !$GLOBALS['perm']->have_studip_perm('dozent', $this->course_id)) {
             throw new AccessDeniedException();
         }
 
@@ -1150,7 +1147,7 @@ class Course_MembersController extends AuthenticatedController
 
         if (!empty($users)) {
             // insert admission user to autorlist
-            $msgs = $this->members->setMemberStatus($users, $status, $next_status, 'upgrade');
+            $msgs = $this->setMemberStatus($users, $status, $next_status, 'upgrade');
 
             if ($msgs['success']) {
                 PageLayout::postSuccess(sprintf(
@@ -1168,7 +1165,10 @@ class Course_MembersController extends AuthenticatedController
                 ));
             }
         } else {
-            PageLayout::postError(sprintf(_('Sie haben keine %s zum Hochstufen ausgewählt'), htmlReady($this->status_groups[$status])));
+            PageLayout::postError(sprintf(
+                _('Sie haben keine %s zum Hochstufen ausgewählt'),
+                htmlReady($this->status_groups[$status])
+            ));
         }
 
         $this->redirect('course/members/index');
@@ -1176,10 +1176,8 @@ class Course_MembersController extends AuthenticatedController
 
     /**
      * Downgrade a user to a selected status
-     * @param type $status
-     * @param type $next_status
-     * @param type $username
-     * @param type $cmd
+     * @param string $status
+     * @param string $next_status
      * @throws AccessDeniedException
      */
     public function downgrade_user_action($status, $next_status)
@@ -1188,8 +1186,8 @@ class Course_MembersController extends AuthenticatedController
         if (!$this->is_tutor) {
             throw new AccessDeniedException();
         }
-        // TODO: Check this
-        if ($this->is_tutor && $next_status !== 'user' && !$this->is_dozent) {
+
+        if ($next_status !== 'user' && !$this->is_dozent) {
             throw new AccessDeniedException();
         }
 
@@ -1202,7 +1200,7 @@ class Course_MembersController extends AuthenticatedController
         }
 
         if (!empty($users)) {
-            $msgs = $this->members->setMemberStatus($users, $status, $next_status, 'downgrade');
+            $msgs = $this->setMemberStatus($users, $status, $next_status, 'downgrade');
 
             if ($msgs['success']) {
                 PageLayout::postSuccess(sprintf(
@@ -1238,7 +1236,7 @@ class Course_MembersController extends AuthenticatedController
         }
 
         if (!empty($users)) {
-            $msg = $this->members->moveToWaitlist($users, $which_end);
+            $msg = $this->moveToWaitlist($users, $which_end);
             if (count($msg['success'])) {
                 PageLayout::postSuccess(sprintf(_('%s Person(en) wurden auf die Warteliste verschoben.'),
                     count($msg['success'])),
@@ -1276,7 +1274,7 @@ class Course_MembersController extends AuthenticatedController
         $export_widget = new ExportWidget();
         $export_widget->addLink(
             _('Zusatzangaben exportieren'),
-            $this->url_for('course/members/export_additional'),
+            $this->export_additionalURL(),
             Icon::create('file-excel')
         );
 
@@ -1296,7 +1294,7 @@ class Course_MembersController extends AuthenticatedController
             $course->aux->updateMember($member, Request::getArray($member->user_id));
         }
 
-        $this->redirect('course/members/additional');
+        $this->redirect($this->additionalURL());
     }
 
     /**
@@ -1368,8 +1366,6 @@ class Course_MembersController extends AuthenticatedController
 
     /**
      * Get the visibility of a user in a seminar
-     * @param String $user_id
-     * @param String $seminar_id
      * @return Array
      */
     private function getUserVisibility()
@@ -1381,9 +1377,9 @@ class Course_MembersController extends AuthenticatedController
         $result['visible_mode'] = false;
 
         if ($visibility) {
-            $result['iam_visible'] = ($visibility == 'yes' || $visibility == 'unknown');
+            $result['iam_visible'] = ($visibility === 'yes' || $visibility === 'unknown');
 
-            if ($status == 'user' || $status == 'autor') {
+            if ($status === 'user' || $status === 'autor') {
                 $result['visible_mode'] = 'participant';
             } else {
                 $result['iam_visible'] = true;
@@ -1402,10 +1398,8 @@ class Course_MembersController extends AuthenticatedController
     {
         $result = Seminar::GetInstance($this->course_id)->getNumber();
 
-        $subject = ($result == '') ? sprintf('[%s]', $this->course_title) :
+        return ($result == '') ? sprintf('[%s]', $this->course_title) :
                 sprintf(_('[%s: %s]'), $result, $this->course_title);
-
-        return $subject;
     }
 
     private function createSidebar($filtered_members)
@@ -1646,10 +1640,11 @@ class Course_MembersController extends AuthenticatedController
                 }
                 $widget->addLink(
                     _('Teilnehmendenliste importieren'),
-                    $this->url_for('course/members/import_autorlist'),
+                    $this->import_autorlistURL(),
                     Icon::create('persons'),
                     ['data-dialog' => 1]
                 );
+
             }
 
             if (Config::get()->EXPORT_ENABLE) {
@@ -1756,7 +1751,7 @@ class Course_MembersController extends AuthenticatedController
             $actions = $sidebar->addWidget(new ActionsWidget());
             $actions->addLink(
                 $link_text,
-                $this->url_for('course/members/change_visibility', $modus, $this->my_visibility['visible_mode']),
+                $this->change_visibilityURL($modus, $this->my_visibility['visible_mode']),
                 $icon,
                 ['title' => $text]
             );
@@ -1768,13 +1763,18 @@ class Course_MembersController extends AuthenticatedController
         if (!$this->is_tutor) {
             throw new AccessDeniedException();
         }
-        $filtered_members = $this->members->getMembers($this->sort_status, $this->sort_by . ' ' . $this->order);
-        $filtered_members = array_merge($filtered_members, $this->members->getAdmissionMembers($this->sort_status, $this->sort_by . ' ' . $this->order ));
+        $filtered_members = CourseMember::getMembers($this->sort_status, $this->sort_by . ' ' . $this->order);
+        $filtered_members = array_merge(
+            $filtered_members,
+            AdmissionApplication::getAdmissionMembers(
+                $this->course_id,
+                $this->sort_status,
+                $this->sort_by . ' ' . $this->order )
+        );
         $dozenten = $filtered_members['dozent']->toArray('user_id username vorname nachname visible mkdate');
         $tutoren = $filtered_members['tutor']->toArray('user_id username vorname nachname visible mkdate');
         $autoren = $filtered_members['autor']->toArray('user_id username vorname nachname visible mkdate');
 
-
         $header = [_('Titel'), _('Vorname'), _('Nachname'), _('Titel2'), _('Nutzernamen'), _('Privatadr'), _('Privatnr'), _('E-Mail'), _('Anmeldedatum'), _('Studiengänge')];
         $data = [$header];
         foreach ([$dozenten, $tutoren, $autoren] as $usergroup) {
@@ -1800,7 +1800,7 @@ class Course_MembersController extends AuthenticatedController
 
         $this->config->store('COURSE_STUDENT_MAILING', $state);
 
-        $this->redirect('course/members');
+        $this->redirect($this->indexURL());
     }
 
     public function course_members_hide_action($state)
@@ -1811,7 +1811,7 @@ class Course_MembersController extends AuthenticatedController
 
         $this->config->store('COURSE_MEMBERS_HIDE', $state);
 
-        $this->redirect('course/members');
+        $this->redirect($this->indexURL());
     }
 
 
@@ -1933,7 +1933,6 @@ class Course_MembersController extends AuthenticatedController
                 if ($who_param) {
                     $url_params['who'] = implode(',', $who_param);
                 }
-                //print_r($url_params);die();
 
                 $this->redirect(URLHelper::getURL(
                     'dispatch.php/messages/write',
@@ -1942,4 +1941,318 @@ class Course_MembersController extends AuthenticatedController
             }
         }
     }
+    public function checkUserVisibility()
+    {
+        $membership = CourseMember::findOneBySQL("visible = 'unknown' AND Seminar_id = ?", [$this->course_id]);
+        if ($membership) {
+            CourseMember::findEachBySQL(
+                function(CourseMember $membership) {
+                    $membership->visible = 'yes';
+                    $membership->store();
+                },
+                "status IN ('tutor', 'dozent') AND Seminar_id = ?",
+                [$this->course_id]
+            );
+
+            CourseMember::findEachBySQL(
+                function(CourseMember $membership) {
+                    $user = $membership->user;
+                    if (in_array($user->visible, ['no','never'])
+                        || ($user->visible === 'unknown') && (int)!Config::get()->USER_VISIBILITY_UNKNOWN
+                    ) {
+                        $mode = 'no';
+                    } else {
+                        $mode = 'yes';
+                    }
+                    $membership->visible = $mode;
+                    $membership->store();
+                },
+                "Seminar_id = ? AND visible='unknown'",
+                [$this->course_id]
+            );
+        }
+    }
+
+    private function setMemberStatus($members, $status, $next_status, $direction)
+    {
+        $msgs = [];
+        foreach ($members as $user_id) {
+            $temp_user = User::find($user_id);
+            if ($next_status == 'tutor' && !$GLOBALS['perm']->have_perm('tutor', $user_id)) {
+                $msgs['no_tutor'][$user_id] = $temp_user->getFullName();
+            } else {
+                if ($temp_user) {
+                    $next_pos = 0;
+                    // get the next position of the user
+                    switch ($next_status) {
+                        case 'autor':
+                        case 'user':
+                            // get the current position of the user
+                            $next_pos = $this->getPosition($user_id);
+                            break;
+                        // set the status to tutor
+                        case 'tutor':
+                            // get the next position of the user
+                            $next_pos = CourseMember::getNextPosition($next_status, $this->course_id);
+                            // resort the tutors
+                            CourseMember::resortMembership($this->course_id, $this->getPosition($user_id));
+                            break;
+                    }
+
+                    $membership = CourseMember::findOneBySQL(
+                        'Seminar_id = ? AND user_id = ? AND status = ?',
+                        [$this->course_id, $user_id, $status]
+                    );
+                    $membership->status = $next_status;
+                    $membership->position = $next_pos;
+
+                    if ($membership->store()) {
+                        StudipLog::log('SEM_CHANGED_RIGHTS', $this->course_id, $user_id, $next_status,
+                            $this->getLogLevel($direction, $next_status));
+                        NotificationCenter::postNotification('CourseMemberStatusDidUpdate', $this->course_id, $user_id);
+                        if ($next_status === 'autor') {
+                            CourseMember::resortMembership($this->course_id, $next_pos);
+                        }
+                        $msgs['success'][$user_id] = $temp_user->getFullName();
+                    }
+                }
+            }
+        }
+
+        if (!empty($msgs)) {
+            return $msgs;
+        } else {
+            return false;
+        }
+    }
+
+    public function addMember(string $user_id, bool $accepted = false, bool $consider_contingent = null, &$msg = []): bool
+    {
+        $user = User::find($user_id);
+        $messaging = new messaging;
+
+        $status = 'autor';
+        $msg = [];
+        // insert
+        $copy_course = $accepted || $consider_contingent;
+        $admission_user = CourseMember::insertCourseMember($this->course_id, $user_id, $status, $copy_course, $consider_contingent, true);
+
+        if ($admission_user) {
+            setTempLanguage($user_id);
+            $message = sprintf(
+                _('Sie wurden in die Veranstaltung **%s** eingetragen.'),
+                $this->course_title
+            );
+            restoreLanguage();
+            $messaging->insert_message(
+                $message,
+                $user->username,
+                '____%system%____',
+                false,
+                false,
+                '1',
+                false,
+                sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')),
+                true
+            );
+            $msg['success'] = sprintf(
+                _('%1$s wurde in die Veranstaltung mit dem Status<b>%2$s</b> eingetragen.'),
+                $user->getFullName(),
+                $status
+            );
+        } else if ($consider_contingent) {
+            PageLayout::postError(_('Es stehen keine weiteren Plätze mehr im Teilnehmendenkontingent zur Verfügung.'));
+            return false;
+        } else {
+            PageLayout::postError(
+                _('Beim Eintragen ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut oder wenden Sie sich an die Administrierenden')
+            );
+            return false;
+        }
+        //Warteliste neu sortieren
+        AdmissionApplication::renumberAdmission($this->course_id);
+        return true;
+    }
+
+    /**
+     * Adds the given user to the waitlist of the current course and sends a
+     * corresponding message.
+     *
+     * @param String $user_id The user to add
+     * @return bool Successful operation?
+     */
+    private function addToWaitlist(string $user_id): bool
+    {
+        $course = Seminar::getInstance($this->course_id);
+        // Insert user in waitlist at current position.
+        if ($course->addToWaitlist($user_id, 'last')) {
+            setTempLanguage($user_id);
+            $message = sprintf(_('Sie wurden von einem/einer Veranstaltungsleiter/-in (%1$s) ' .
+                'oder einem/einer Administrator/-in auf die Warteliste der Veranstaltung **%2$s** gesetzt.'),
+                get_title_for_status('dozent', 1), $this->course_title);
+            restoreLanguage();
+            messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'),
+                _('Auf Warteliste gesetzt')), $message);
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Adds the given users to the target course.
+     * @param array $users users to add
+     * @param string $target_course_id which course to add users to
+     * @param bool $move move users (=delete in source course) or just add to target course?
+     * @return array success and failure statuses
+     */
+    private function sendToCourse(array $users, string $target_course_id, bool $move = false): array
+    {
+        $msg = [];
+        foreach ($users as $user) {
+            if (!CourseMember::exists([$target_course_id, $user])) {
+                $target_course = Seminar::GetInstance($target_course_id);
+                if ($target_course->addMember($user)) {
+                    if ($move) {
+                        $remove_from = Seminar::getInstance($this->course_id);
+                        $remove_from->deleteMember($user);
+                    }
+                    $msg['success'][] = $user;
+                } else {
+                    $msg['failed'][] = $user;
+                }
+            } else {
+                $msg['existing'][] = $user;
+            }
+        }
+        return $msg;
+    }
+
+    private function insertAdmissionMember(array $users, string $next_status, bool $consider_contingent, bool $accepted = false, string $cmd = 'add_user'): array
+    {
+        $messaging = new messaging;
+        $status_title = get_title_for_status('dozent', 1);
+        foreach ($users as $user_id => $value) {
+            if ($value) {
+                $user = User::find($user_id);
+                if ($user) {
+                    $admission_user = CourseMember::insertCourseMember(
+                        $this->course_id,
+                        $user_id,
+                        $next_status,
+                        $accepted || $consider_contingent,
+                        $consider_contingent
+                    );
+
+                    // only if user was on the waiting list
+                    if ($admission_user) {
+                        setTempLanguage($user_id);
+                        restoreLanguage();
+
+                        if ($cmd === 'add_user') {
+                            $message = sprintf(
+                                _('Sie wurden in die Veranstaltung **%s** eingetragen.'),
+                                $this->course_title
+                            );
+                        } else {
+                            if (!$accepted) {
+                                $message = sprintf(_('Sie wurden aus der Warteliste in die Veranstaltung **%s** aufgenommen und sind damit zugelassen.'),
+$this->course_title);
+                            } else {
+                                $message = sprintf(_('Sie wurden vom Status **vorläufig akzeptiert** auf **teilnehmend** in der Veranstaltung **%s** hochgestuft und sind damit zugelassen.'), $this->course_title);
+                            }
+                        }
+
+                        $messaging->insert_message(
+                            $message,
+                            $user->username,
+                            '____%system%____',
+                            false,
+                            false,
+                            '1',
+                            false,
+                            sprintf('%s %s', _('Systemnachricht:'), _('Eintragung in Veranstaltung')),
+                            true
+                        );
+                        $msgs[] = $user->getFullName();
+                    }
+                }
+            }
+        }
+
+        // resort admissionlist
+        AdmissionApplication::renumberAdmission($this->course_id);
+
+        return $msgs;
+    }
+
+    /**
+     * Adds given users to the course waitlist, either at list beginning or end.
+     * System messages are sent to affected users.
+     *
+     * @param array $users array of user ids to add
+     * @param String $which_end 'last' or 'first': which list end to append to
+     * @return array Array of messages (stating success and/or errors)
+     */
+    public function moveToWaitlist($users, $which_end)
+    {
+        $course = Seminar::getInstance($this->course_id);
+        $msgs = [];
+        foreach ($users as $user_id) {
+            // Delete member from seminar
+            $temp_user = User::find($user_id);
+            if ($course->deleteMember($user_id)) {
+                setTempLanguage($user_id);
+                $message = sprintf(_('Sie wurden aus der Veranstaltung **%s** abgemeldet. '.
+                    'Sie wurden auf die Warteliste dieser Veranstaltung gesetzt.'),
+                    $this->course_title);
+                restoreLanguage();
+                messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'),
+                    _('Anmeldung aufgehoben, auf Warteliste gesetzt')), $message);
+                if ($course->addToWaitlist($user_id, $which_end)) {
+                    $msgs['success'][] = $temp_user->getFullname('no_title');
+                } else {
+                    $msgs['error'][] = $temp_user->getFullname('no_title');
+                }
+                // Something went wrong on inserting the user in waitlist.
+            } else {
+                $msgs['error'][] = $temp_user->getFullname('no_title');
+            }
+        }
+        return $msgs;
+    }
+
+    /**
+     * Get the position out of the database
+     * @param String $user_id
+     * @return int
+     */
+    private function getPosition($user_id): ?int
+    {
+        $membership = CourseMember::findByUser($user_id);
+        if ($membership) {
+            return (int)$membership->position;
+        }
+        return 0;
+    }
+
+    private function getLogLevel($direction, $status)
+    {
+        if ($direction === 'upgrade') {
+            $directionString = 'hochgestuft';
+        } else {
+            $directionString = 'runtergestuft';
+        }
+
+        switch ($status) {
+            case 'tutor': $log_level = 'zum Tutor';
+                break;
+            case 'autor': $log_level = 'zum Autor';
+                break;
+            case 'dozent': $log_level = 'zum Dozenten';
+                break;
+        }
+
+        return sprintf('%s %s', $directionString, $log_level);
+    }
 }
diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php
index e0ed12e65a4..9d40b8f086b 100644
--- a/app/controllers/course/statusgroups.php
+++ b/app/controllers/course/statusgroups.php
@@ -1348,9 +1348,8 @@ class Course_StatusgroupsController extends AuthenticatedController
         CSRFProtection::verifyUnsafeRequest();
 
         $members = Request::getArray('members');
-        $model = new MembersModel($this->course_id, $this->course_title);
-
-        $removed_names = $model->cancelSubscription($members);
+        $course = Seminar::GetInstance($this->course_id);
+        $removed_names = $course->cancelSubscription($members);
 
         PageLayout::postSuccess(
             _('Die folgenden Personen wurden aus der Veranstaltung ausgetragen'),
diff --git a/app/controllers/course/studygroup.php b/app/controllers/course/studygroup.php
index fc55bf560a1..c1e7b4831c8 100644
--- a/app/controllers/course/studygroup.php
+++ b/app/controllers/course/studygroup.php
@@ -320,10 +320,12 @@ class Course_StudygroupController extends AuthenticatedController
                 if ($admin) {
                     // insert founder(s)
                     foreach ($founders as $user_id) {
-                        $stmt = DBManager::get()->prepare("INSERT INTO seminar_user
-                            (seminar_id, user_id, status, gruppe)
-                            VALUES (?, ?, 'dozent', 8)");
-                        $stmt->execute([$sem->id, $user_id]);
+                        CourseMember::create([
+                            'seminar_id' => $sem->id,
+                            'user_id'    => $user_id,
+                            'status'     => 'dozent',
+                            'gruppe'     => 8
+                        ]);
                     }
 
                     $this->founders          = null;
@@ -331,10 +333,12 @@ class Course_StudygroupController extends AuthenticatedController
                 } else {
                     $user_id = $GLOBALS['auth']->auth['uid'];
                     // insert dozent
-                    $query     = "INSERT INTO seminar_user (seminar_id, user_id, status, gruppe)
-                              VALUES (?, ?, 'dozent', 8)";
-                    $statement = DBManager::get()->prepare($query);
-                    $statement->execute([$sem->id, $user_id]);
+                    CourseMember::create([
+                        'seminar_id' => $sem->id,
+                        'user_id'    => $user_id,
+                        'status'     => 'dozent',
+                        'gruppe'     => 8
+                    ]);
                 }
 
                 $sem->store();
diff --git a/app/controllers/my_courses.php b/app/controllers/my_courses.php
index ee356164973..8e17ead8c50 100644
--- a/app/controllers/my_courses.php
+++ b/app/controllers/my_courses.php
@@ -301,25 +301,31 @@ class MyCoursesController extends AuthenticatedController
         );
         $gruppe = Request::getArray('gruppe');
         if (!empty($gruppe)) {
-            $query = "UPDATE seminar_user SET gruppe = ? WHERE Seminar_id = ? AND user_id = ?";
-            $user_statement = DBManager::get()->prepare($query);
-
-            $query = "UPDATE deputies SET gruppe = ? WHERE range_id = ? AND user_id = ?";
-            $deputy_statement = DBManager::get()->prepare($query);
-
             foreach ($gruppe as $key => $value) {
-                $user_statement->execute([$value,
-                    $key,
-                    $GLOBALS['user']->id
-                ]);
-                $updated = $user_statement->rowCount();
-
-                if ($deputies_enabled && !$updated) {
-                    $deputy_statement->execute([
-                        $value,
+                $updated = CourseMember::findEachBySQL(
+                    function (CourseMember $cm) use ($value) {
+                        $cm->gruppe = $value;
+                        $cm->store();
+                    },
+                    'Seminar_id = ? AND user_id = ?',
+                    [
                         $key,
                         $GLOBALS['user']->id
-                    ]);
+                    ]
+                );
+
+                if ($deputies_enabled && !$updated) {
+                    Deputy::findEachBySQL(
+                        function (Deputy $deputy) use ($value) {
+                            $deputy->gruppe = $value;
+                            $deputy->store();
+                        },
+                        'range_id = ? AND user_id = ?',
+                        [
+                            $key,
+                            $GLOBALS['user']->id
+                        ]
+                    );
                 }
             }
         }
@@ -442,7 +448,7 @@ class MyCoursesController extends AuthenticatedController
                 }
                 $cmd = 'kill';
             } else {
-                if (admission_seminar_user_get_position($GLOBALS['user']->id, $course_id) === false) {
+                if (AdmissionApplication::checkMemberPosition($GLOBALS['user']->id, $course_id) === false) {
                     $message = sprintf(
                         _('Wollen Sie sich von der Anmeldeliste der Veranstaltung "%s" wirklich abmelden?'),
                         htmlReady($current_seminar->name)
@@ -465,10 +471,7 @@ class MyCoursesController extends AuthenticatedController
             return;
         } else {
             if (!LockRules::Check($course_id, 'participants') && $ticket_check && Request::option('cmd') != 'back' && Request::get('cmd') != 'kill_admission') {
-                $query     = "DELETE FROM seminar_user WHERE user_id = ? AND Seminar_id = ?";
-                $statement = DBManager::get()->prepare($query);
-                $statement->execute([$GLOBALS['user']->id, $course_id]);
-                if ($statement->rowCount() == 0) {
+                if (CourseMember::deleteBySQL('user_id = ? AND Seminar_id = ?', [$GLOBALS['user']->id, $course_id]) === 0) {
                     PageLayout::postError(
                         _('In der ausgewählten Veranstaltung wurde die gesuchten Personen nicht gefunden und konnte daher nicht ausgetragen werden.')
                     );
@@ -487,7 +490,7 @@ class MyCoursesController extends AuthenticatedController
                     }
 
                     // Are successor available
-                    update_admission($course_id);
+                    AdmissionApplication::addMembers($course_id);
 
                     // If this course is a child of another course...
                     if ($current_seminar->parent_course) {
@@ -522,16 +525,16 @@ class MyCoursesController extends AuthenticatedController
                 if ($current_seminar->isAdmissionEnabled()) {
                     $prio_delete = AdmissionPriority::unsetPriority($current_seminar->getCourseSet()->getId(), $GLOBALS['user']->id, $course_id);
                 }
-                $query     = "DELETE FROM admission_seminar_user WHERE user_id = ? AND seminar_id = ?";
-                $statement = DBManager::get()->prepare($query);
-                $statement->execute([$GLOBALS['user']->id,
-                    $course_id]);
                 NotificationCenter::postNotification('UserDidLeaveWaitingList', $course_id, $GLOBALS['user']->id);
-                if ($statement->rowCount() || $prio_delete) {
+                $deleted = AdmissionApplication::deleteBySQL(
+                    'user_id = ? AND seminar_id = ?',
+                    [$GLOBALS['user']->id, $course_id]
+                );
+                if ($deleted || $prio_delete) {
                     //Warteliste neu sortieren
-                    renumber_admission($course_id);
+                    AdmissionApplication::renumberAdmission($course_id);
                     //Pruefen, ob es Nachruecker gibt
-                    update_admission($course_id);
+                    AdmissionApplication::addMembers($course_id);
                     PageLayout::postSuccess(sprintf(
                         _("Der Eintrag in der Anmelde- bzw. Warteliste der Veranstaltung <b>%s</b> wurde aufgehoben. Wenn Sie an der Veranstaltung teilnehmen wollen, müssen Sie sich erneut bewerben."),
                         htmlReady($current_seminar->name)
diff --git a/lib/admission.inc.php b/lib/admission.inc.php
index 817dbb50c21..d0728f9d924 100644
--- a/lib/admission.inc.php
+++ b/lib/admission.inc.php
@@ -14,6 +14,8 @@
  * @modulegroup      admission
  * @module       admission.inc.php
  * @package      studip_core
+ * @deprecated since Stud.IP 5.3
+ *
  */
 
 // +---------------------------------------------------------------------------+
diff --git a/lib/classes/AutoInsert.class.php b/lib/classes/AutoInsert.class.php
index d9b1558a87d..8aac873a756 100644
--- a/lib/classes/AutoInsert.class.php
+++ b/lib/classes/AutoInsert.class.php
@@ -150,43 +150,29 @@ class AutoInsert
 
     private function addUser($user_id, $seminar)
     {
-        $query = "INSERT IGNORE INTO seminar_user (Seminar_id, user_id, status, gruppe, mkdate)
-            VALUES (?, ?, 'autor', ?, UNIX_TIMESTAMP())";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$seminar['Seminar_id'], $user_id, select_group($seminar['start_time'])]);
-        $rows = $statement->rowCount();
-        if ($rows > 0) return true;
-
-        return false;
+        $course_member = new CourseMember([$seminar['Seminar_id'], $user_id]);
+        $course_member->setData([
+            'Seminar_id' => $seminar['Seminar_id'],
+            'user_id'    => $user_id,
+            'status'     => 'autor',
+            'gruppe'     => select_group($seminar['start_time']),
+        ]);
+
+        return $course_member->store() > 0;
     }
 
     private function removeUser($user_id, $seminar)
     {
-        $query = "DELETE FROM seminar_user " . "WHERE user_id = ? " . "AND Seminar_id = ? ";
-
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$user_id, $seminar['Seminar_id']]);
-        $rows = $statement->rowCount();
-
-        $query             = "DELETE FROM statusgruppe_user " . "WHERE user_id = ? " . "AND statusgruppe_id IN (SELECT statusgruppe_id FROM statusgruppen WHERE range_id = ?)";
-        $statusgruppe_stmt = DBManager::get()->prepare($query);
-        $statusgruppe_stmt->execute([$user_id, $seminar['Seminar_id']]);
-        $statusgruppe_rows = $statusgruppe_stmt->rowCount();
+        $rows = CourseMember::deleteBySQL(' user_id = ? AND Seminar_id = ?', [$user_id, $seminar['Seminar_id']]);
+        $statusgruppe_rows =  StatusgruppeUser::deleteBySQL(
+            'user_id = ? AND statusgruppe_id IN (SELECT statusgruppe_id FROM statusgruppen WHERE range_id = ?)',
+            [$user_id, $seminar['Seminar_id']]
+        );
         if ($rows > 0 || $statusgruppe_rows > 0) return true;
 
         return false;
     }
 
-    /**
-     *
-     * @param type $user_id
-     */
-    public function deleteUserSeminare($user_id)
-    {
-        $db = DBManager::get();
-        $db->exec("DELETE FROM seminar_user " . "WHERE user_id = " . $db->quote($user_id));
-    }
-
     /**
      * Tests if a seminar already has an autoinsert record
      * @param  string $seminar_id Id of the seminar
diff --git a/lib/classes/MembersModel.php b/lib/classes/MembersModel.php
index 3c787084c19..bf776d99f0f 100644
--- a/lib/classes/MembersModel.php
+++ b/lib/classes/MembersModel.php
@@ -1,4 +1,7 @@
 <?php
+/**
+ * @deprecated since Stud.IP 5.3
+ */
 class MembersModel
 {
 
@@ -100,8 +103,8 @@ class MembersModel
                 $message = sprintf(_('Ihre Anmeldung zur Veranstaltung **%1$s** wurde von Lehrenden  (%2$s) oder Admin aufgehoben.'), $this->course_title, get_title_for_status('dozent', 1));
                 restoreLanguage();
                 $messaging->insert_message($message, $user->username,
-                                '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
-                                        _("Anmeldung aufgehoben")), TRUE);
+                    '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
+                        _("Anmeldung aufgehoben")), TRUE);
                 $msgs[] = $user->getFullName();
             }
         }
@@ -130,8 +133,8 @@ class MembersModel
                 }
                 restoreLanguage();
                 $messaging->insert_message($message, $user->username,
-                                '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
-                                        _("nicht zugelassen in Veranstaltung")), TRUE);
+                    '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
+                        _("nicht zugelassen in Veranstaltung")), TRUE);
                 StudipLog::log('SEM_USER_DEL', $this->course_id, $user_id, 'Wurde aus der Veranstaltung entfernt');
                 NotificationCenter::postNotification('UserDidLeaveCourse', $this->course_id, $user_id);
 
@@ -149,7 +152,7 @@ class MembersModel
                 $user = User::find($user_id);
                 if ($user) {
                     $admission_user = insert_seminar_user($this->course_id, $user_id, $next_status,
-                            ($accepted || $consider_contingent ? TRUE : FALSE), $consider_contingent);
+                        ($accepted || $consider_contingent ? TRUE : FALSE), $consider_contingent);
 
                     // only if user was on the waiting list
                     if ($admission_user) {
@@ -163,7 +166,7 @@ class MembersModel
                             if (!$accepted) {
                                 $message = sprintf(_('Sie wurden von %1$s oder Admin
                                     aus der Warteliste in die Veranstaltung **%2$s** aufgenommen und sind damit zugelassen.'),
-                                        get_title_for_status('dozent', 1), $this->course_title);
+                                    get_title_for_status('dozent', 1), $this->course_title);
                             } else {
                                 $message = sprintf(_('Sie wurden von einem/einer %1$s oder Admin
                                     vom Status **vorläufig akzeptiert** auf **teilnehmend** in der Veranstaltung **%2$s**
@@ -172,8 +175,8 @@ class MembersModel
                         }
 
                         $messaging->insert_message($message, $user->username,
-                                '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
-                                        _('Eintragung in Veranstaltung')), TRUE);
+                            '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
+                                _('Eintragung in Veranstaltung')), TRUE);
                         $msgs[] = $user->getFullName();
                     }
                 }
@@ -208,7 +211,7 @@ class MembersModel
                 if (!$accepted) {
                     $message = sprintf(_('Sie wurden vom einem/einer %1$s oder Admin
                         aus der Warteliste in die Veranstaltung **%2$s** aufgenommen und sind damit zugelassen.'),
-                            get_title_for_status('dozent', 1), $this->course_title);
+                        get_title_for_status('dozent', 1), $this->course_title);
                 } else {
                     $message = sprintf(_('Sie wurden von einem/einer %1$s oder Admin vom Status
                         **vorläufig akzeptiert** auf "**teilnehmend** in der Veranstaltung **%2$s**
@@ -217,8 +220,8 @@ class MembersModel
             }
             restoreLanguage();
             $messaging->insert_message($message, $user->username,
-                    '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
-                            _('Eintragung in Veranstaltung')), TRUE);
+                '____%system%____', FALSE, FALSE, '1', FALSE, sprintf('%s %s', _('Systemnachricht:'),
+                    _('Eintragung in Veranstaltung')), TRUE);
         }
 
         //Warteliste neu sortieren
@@ -265,7 +268,7 @@ class MembersModel
                 get_title_for_status('dozent', 1), $this->course_title);
             restoreLanguage();
             messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'),
-                    _('Auf Warteliste gesetzt')), $message);
+                _('Auf Warteliste gesetzt')), $message);
 
             return true;
         }
@@ -483,36 +486,36 @@ class MembersModel
      * @param String $which_end 'last' or 'first': which list end to append to
      * @return mixed Array of messages (stating success and/or errors)
      */
-     public function moveToWaitlist($users, $which_end)
-     {
-         $course = Seminar::getInstance($this->course_id);
-         foreach ($users as $user_id) {
-             // Delete member from seminar
-             if ($course->deleteMember($user_id)) {
-                 setTempLanguage($user_id);
-                 $message = sprintf(_('Sie wurden von der Veranstaltung **%s** von '.
-                     '%s oder der Administration abgemeldet, '.
-                     'Sie wurden auf die Warteliste dieser Veranstaltung gesetzt.'),
-                     $this->course_title, get_title_for_status('dozent', 1));
-                 restoreLanguage();
-                 messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'),
-                     _('Anmeldung aufgehoben, auf Warteliste gesetzt')), $message);
-                 // Insert user in waitlist at current position.
-                 if ($course->addToWaitlist($user_id, $which_end)) {
-                     $temp_user = User::find($user_id);
-                     $msgs['success'][] = $temp_user->getFullname('no_title');
-                     $curpos++;
-                     // Something went wrong on removing the user from course.
-                 } else {
-                     $msgs['error'][] = $temp_user->getFullname('no_title');
-                 }
-                 // Something went wrong on inserting the user in waitlist.
-             } else {
-                 $msgs['error'][] = $temp_user->getFullname('no_title');
-             }
-         }
-         return $msgs;
-     }
+    public function moveToWaitlist($users, $which_end)
+    {
+        $course = Seminar::getInstance($this->course_id);
+        foreach ($users as $user_id) {
+            // Delete member from seminar
+            if ($course->deleteMember($user_id)) {
+                setTempLanguage($user_id);
+                $message = sprintf(_('Sie wurden von der Veranstaltung **%s** von '.
+                    '%s oder der Administration abgemeldet, '.
+                    'Sie wurden auf die Warteliste dieser Veranstaltung gesetzt.'),
+                    $this->course_title, get_title_for_status('dozent', 1));
+                restoreLanguage();
+                messaging::sendSystemMessage($user_id, sprintf('%s %s', _('Systemnachricht:'),
+                    _('Anmeldung aufgehoben, auf Warteliste gesetzt')), $message);
+                // Insert user in waitlist at current position.
+                if ($course->addToWaitlist($user_id, $which_end)) {
+                    $temp_user = User::find($user_id);
+                    $msgs['success'][] = $temp_user->getFullname('no_title');
+                    $curpos++;
+                    // Something went wrong on removing the user from course.
+                } else {
+                    $msgs['error'][] = $temp_user->getFullname('no_title');
+                }
+                // Something went wrong on inserting the user in waitlist.
+            } else {
+                $msgs['error'][] = $temp_user->getFullname('no_title');
+            }
+        }
+        return $msgs;
+    }
 
     /**
      * Get the positon out of the database
diff --git a/lib/classes/Seminar.class.php b/lib/classes/Seminar.class.php
index 4190def3c18..4b8e6e4e441 100644
--- a/lib/classes/Seminar.class.php
+++ b/lib/classes/Seminar.class.php
@@ -20,7 +20,6 @@
  * @category    Stud.IP
  */
 
-require_once 'lib/admission.inc.php';
 require_once 'lib/dates.inc.php';
 
 class Seminar
@@ -1530,17 +1529,13 @@ class Seminar
         // Delete that Seminar.
 
         // Alle Benutzer aus dem Seminar rauswerfen.
-        $query = "DELETE FROM seminar_user WHERE Seminar_id = ?";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$s_id]);
-        if (($db_ar = $statement->rowCount()) > 0) {
+        $db_ar = CourseMember::deleteBySQL('Seminar_id = ?', [$s_id]);
+        if ($db_ar > 0) {
             $this->createMessage(sprintf(_("%s Teilnehmende und Lehrende archiviert."), $db_ar));
         }
 
         // Alle Benutzer aus Wartelisten rauswerfen
-        $query = "DELETE FROM admission_seminar_user WHERE seminar_id = ?";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$s_id]);
+        AdmissionApplication::deleteBySQL('seminar_id = ?', [$s_id]);
 
         // Alle beteiligten Institute rauswerfen
         $query = "DELETE FROM seminar_inst WHERE Seminar_id = ?";
@@ -1596,10 +1591,8 @@ class Seminar
 
 
         // Freie Seite zu diesem Seminar löschen
-        $query = "DELETE FROM scm WHERE range_id = ?";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$s_id]);
-        if (($db_ar = $statement->rowCount()) > 0) {
+        $db_ar = StudipScmEntry::deleteBySQL('range_id = ?', [$s_id]);
+        if ($db_ar > 0) {
             $this->createMessage(_("Freie Seite der Veranstaltung archiviert."));
         }
 
@@ -1614,10 +1607,8 @@ class Seminar
         DataFieldEntry::removeAll($s_id);
 
         //kill all wiki-pages
-        $query = "DELETE FROM wiki WHERE range_id = ?";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$s_id]);
-        if (($db_wiki = $statement->rowCount()) > 0) {
+        $db_wiki = WikiPage::deleteBySQL('range_id = ?', [$s_id]);
+        if ($db_wiki > 0) {
             $this->createMessage(sprintf(_("%s Wiki-Seiten archiviert."), $db_wiki));
         }
 
@@ -1892,51 +1883,34 @@ class Seminar
                 return false;
             }
         }
-
-        if (!$force) {
-            $query = "SELECT status FROM seminar_user WHERE user_id = ? AND Seminar_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$user_id, $this->id]);
-            $old_status = $statement->fetchColumn();
-        }
-
-        $query = "SELECT MAX(position) + 1 FROM seminar_user WHERE status = ? AND Seminar_id = ?";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$status, $this->id]);
-        $new_position = $statement->fetchColumn();
-
-        $query = "SELECT COUNT(*) FROM seminar_user WHERE Seminar_id = ? AND status = 'dozent'";
-        $statement = DBManager::get()->prepare($query);
-        $statement->execute([$this->id]);
-        $numberOfTeachers = $statement->fetchColumn();
-
-        if (!$old_status) {
-            $query = "INSERT INTO seminar_user (Seminar_id, user_id, status, position, gruppe, visible, mkdate)
-                      VALUES (?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP())";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([
-                $this->id,
-                $user_id,
-                $status,
-                $new_position ?: 0,
-                (int)select_group($this->getSemesterStartTime()),
-                in_array($status, words('tutor dozent')) ? 'yes' : 'unknown',
+        $course_member = CourseMember::findOneBySQL('user_id = ? AND Seminar_id = ?', [$user_id, $this->id]);
+        $new_position = (int) DBManager::get()->fetchColumn(
+            "SELECT MAX(position) + 1 FROM seminar_user WHERE status = ? AND Seminar_id = ?",
+            [$status, $this->id]
+        );
+        $numberOfTeachers = CourseMember::countBySql("Seminar_id = ? AND status = 'dozent'", [$this->id]);
+
+        if (!$course_member && !$force) {
+            CourseMember::create([
+                'Seminar_id' => $this->id,
+                'user_id'    => $user_id,
+                'status'     => $status,
+                'position'   => $new_position?:0,
+                'gruppe'     => (int) select_group($this->getSemesterStartTime()),
+                'visible'    => in_array($status, ['tutor', 'dozent']) ? 'yes' : 'unknown',
             ]);
             // delete the entries, user is now in the seminar
-            $stmt = DBManager::get()->prepare('DELETE FROM admission_seminar_user
-                                            WHERE user_id = ? AND seminar_id = ?');
-            $stmt->execute([$user_id, $this->getId()]);
-            if ($stmt->rowCount()) {
+            if (AdmissionApplication::deleteBySQL('user_id = ? AND seminar_id = ?', [$user_id, $this->getId()])) {
                 //renumber the waiting/accepted/lot list, a user was deleted from it
-                renumber_admission($this->getId());
+                AdmissionApplication::renumberAdmission($this->getId());
             }
             $cs = $this->getCourseSet();
             if ($cs) {
-                $prio_delete = AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId());
+                AdmissionPriority::unsetPriority($cs->getId(), $user_id, $this->getId());
             }
 
             CalendarScheduleModel::deleteSeminarEntries($user_id, $this->getId());
-            NotificationCenter::postNotification("CourseDidGetMember", $this, $user_id);
+            NotificationCenter::postNotification('CourseDidGetMember', $this, $user_id);
             NotificationCenter::postNotification('UserDidEnterCourse', $this->id, $user_id);
             StudipLog::log('SEM_USER_ADD', $this->id, $user_id, $status, 'Wurde in die Veranstaltung eingetragen');
             $this->course->resetRelation('members');
@@ -1949,67 +1923,158 @@ class Seminar
             }
 
             return $this;
-        } elseif (($force || $rangordnung[$old_status] < $rangordnung[$status])
-            && ($old_status !== "dozent" || $numberOfTeachers > 1)) {
-            $query = "UPDATE seminar_user
-                      SET status = ?, visible = IFNULL(?, visible), position = ?
-                      WHERE Seminar_id = ? AND user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([
-                $status,
-                in_array($status, words('tutor dozent')) ? 'yes' : null,
-                $new_position,
-                $this->id,
-                $user_id,
-            ]);
-
-            if ($old_status === 'dozent') {
-                $query = "SELECT termin_id FROM termine WHERE range_id = ?";
-                $statement = DBManager::get()->prepare($query);
-                $statement->execute([$this->id]);
-                $termine = $statement->fetchAll(PDO::FETCH_COLUMN);
-
-                $query = "DELETE FROM termin_related_persons WHERE range_id = ? AND user_id = ?";
-                $statement = DBManager::get()->prepare($query);
+        } elseif (
+            ($force || $rangordnung[$course_member->status] < $rangordnung[$status])
+            && ($course_member->status !== 'dozent' || $numberOfTeachers > 1)
+        ) {
+            $visibility = $course_member->visible;
+            if (in_array($status, ['tutor', 'dozent'])) {
+                $visibility = 'yes';
+            }
+            $course_member->status = $status;
+            $course_member->visible = $visibility;
+            $course_member->position = $new_position;
+            $course_member->store();
+
+            if ($course_member->status === 'dozent') {
+                $termine = DBManager::get()->fetchFirst(
+                    "SELECT termin_id FROM termine WHERE range_id = ?",
+                    [$this->id]
+                );
 
-                foreach ($termine as $termin_id) {
-                    $statement->execute([$termin_id, $user_id]);
-                }
+                DBManager::get()->execute(
+                    "DELETE FROM termin_related_persons WHERE range_id IN (?) AND user_id = ?",
+                    [$termine, $user_id]
+                );
             }
-            NotificationCenter::postNotification("CourseDidChangeMember", $this, $user_id);
+            NotificationCenter::postNotification('CourseDidChangeMember', $this, $user_id);
             $this->course->resetRelation('members');
             $this->course->resetRelation('admission_applicants');
             return $this;
         } else {
-            if ($old_status === "dozent" && $numberOfTeachers <= 1) {
-                $this->createError(sprintf(_("Die Veranstaltung muss wenigstens <b>einen/eine</b> VeranstaltungsleiterIn (%s) eingetragen haben!"),
+            if ($course_member->status === 'dozent' && $numberOfTeachers <= 1) {
+                $this->createError(sprintf(_('Die Person kann nicht herabgestuft werden, ' .
+'da mindestens ein/eine Veranstaltungsleiter/-in (%s) in die Veranstaltung eingetragen sein muss!'),
                         get_title_for_status('dozent', 1, $this->status)) .
-                    ' ' . _("Tragen Sie zunächst einen anderen ein, um diesen herabzustufen."));
+                    ' ' . sprintf(_('Tragen Sie zunächst eine weitere Person als Veranstaltungsleiter/-in (%s) ein.'),
+get_title_for_status('dozent', 1, $this->status)));
             }
 
             return false;
         }
     }
 
+    /**
+     * Cancels a subscription to an admission.
+     *
+     * @param array $users
+     * @param string $status
+     * @return array
+     * @throws NotificationVetoException
+     */
+    public function cancelAdmissionSubscription(array $users, string $status): array
+    {
+        $msgs = [];
+        $messaging = new messaging;
+        $course_set = $this->getCourseSet();
+        $users = User::findMany($users);
+        foreach ($users as $user) {
+            $prio_delete = false;
+            if ($course_set) {
+                $prio_delete = AdmissionPriority::unsetPriority($course_set->getId(), $user->id, $this->getId());
+            }
+            $result = AdmissionApplication::deleteBySQL(
+                'seminar_id = ? AND user_id = ? AND status = ?',
+                [$this->getId(), $user->id, $status]
+            );
+            if ($result || $prio_delete) {
+                setTempLanguage($user->id);
+                if ($status !== 'accepted') {
+                    $message = sprintf(
+                        _('Sie wurden von der Warteliste der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'),
+                        $this->getFullName()
+                    );
+                } else {
+                    $message = sprintf(
+                        _('Sie wurden aus der Veranstaltung **%s** gestrichen und sind damit __nicht__ zugelassen worden.'),
+                        $this->getFullName()
+                    );
+                }
+                restoreLanguage();
+                $messaging->insert_message(
+                    $message,
+                    $user->username,
+                    '____%system%____',
+                    false,
+                    false,
+                    '1',
+                    false,
+                    sprintf('%s %s', _('Systemnachricht:'), _('nicht zugelassen in Veranstaltung')),
+                    true
+                );
+                StudipLog::log('SEM_USER_DEL', $this->getId(), $user->id, 'Wurde aus der Veranstaltung entfernt');
+                NotificationCenter::postNotification('UserDidLeaveCourse', $this->getId(), $user->id);
+
+                $msgs[] = $user->getFullName();
+            }
+        }
+        return $msgs;
+    }
+
+    /**
+     * Cancels a subscription to a course
+     * @param array $users
+     * @return array
+     * @throws Exception
+     */
+    public function cancelSubscription(array $users): array
+    {
+        $msgs = [];
+        $messaging = new messaging;
+        $users = User::findMany($users);
+        foreach ($users as $user) {
+            // delete member from seminar
+            if ($this->deleteMember($user->id)) {
+                setTempLanguage($user->id);
+                $message = sprintf(
+                    _('Ihre Anmeldung zur Veranstaltung **%s** wurde aufgehoben.'),
+                    $this->getFullName()
+                );
+                restoreLanguage();
+                $messaging->insert_message(
+                    $message,
+                    $user->username,
+                    '____%system%____',
+                    false,
+                    false,
+                    '1',
+                    false,
+                    sprintf('%s %s', _('Systemnachricht:'), _("Anmeldung aufgehoben")),
+                    true
+                );
+                $msgs[] = $user->getFullName();
+            }
+        }
+
+        return $msgs;
+    }
+
     /**
      * deletes a user from the seminar by respecting the rule that at least one
      * user with status "dozent" must stay there
-     * @param user_id string:   user_id of the user to delete
-     * @param return:   false or $this for chaining
+     * @param string $user_id  user_id of the user to delete
+     * @return boolean
      */
-    public function deleteMember($user_id)
+    public function deleteMember($user_id): bool
     {
-        $dozenten = $this->getMembers('dozent');
+        $dozenten = $this->getMembers();
         if (count($dozenten) >= 2 || !$dozenten[$user_id]) {
-            $query = "DELETE FROM seminar_user WHERE Seminar_id = ? AND user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->id, $user_id]);
-            if ($statement->rowCount() === 0) {
-                return $this;
+            $result = CourseMember::deleteBySQL('Seminar_id = ? AND user_id = ?', [$this->id, $user_id]);
+            if ($result === 0) {
+                return true;
             }
             // If this course is a child of another course...
             if ($this->parent_course) {
-
                 // ... check if user is member in another sibling ...
                 $other = CourseMember::countBySQL(
                     "`user_id` = :user AND `Seminar_id` IN (:courses) AND `Seminar_id` != :this",
@@ -2018,7 +2083,7 @@ class Seminar
 
                 // ... and delete from parent course if this was the only
                 // course membership in this family.
-                if ($other == 0) {
+                if ($other === 0) {
                     $s = new Seminar($this->parent);
                     $s->deleteMember($user_id);
                 }
@@ -2070,7 +2135,7 @@ class Seminar
             NotificationCenter::postNotification('UserDidLeaveCourse', $this->id, $user_id);
             StudipLog::log('SEM_USER_DEL', $this->id, $user_id, 'Wurde aus der Veranstaltung entfernt');
             $this->course->resetRelation('members');
-            return $this;
+            return true;
         } else {
             $this->createError(
                 sprintf(
@@ -2085,40 +2150,23 @@ class Seminar
 
     /**
      * sets the almost never used column position in the table seminar_user
-     * @param members array: array of user_id's - wrong IDs will be ignored
-     * @return $this
+     * @param array $members members array: array of user_id's - wrong IDs will be ignored
+     * @return Seminar
      */
-    public function setMemberPriority($members)
-    {
-        $query = "UPDATE seminar_user
-                  SET position = ?
-                  WHERE Seminar_id = ? AND user_id = ?";
-        $statement = DBManager::get()->prepare($query);
-
-        foreach(array_values($members) as $num => $member) {
-            $statement->execute([$num, $this->id, $member]);
-        }
-
+    public function setMemberPriority($members): Seminar
+    {
+        $counter = 0;
+        CourseMember::findEachBySQL(
+            function (CourseMember $membership) use (&$counter) {
+                $membership->position = $counter++;
+                $membership->store();
+            },
+            "Seminar_id = ? AND user_id = IN (?)",
+            [$this->course_id, $members]
+        );
         return $this;
     }
 
-    public function setLabel($user_id, $label) {
-        if ($GLOBALS['perm']->have_studip_perm('tutor', $this->getId(), $user_id)) {
-            $statement = DBManager::get()->prepare(
-                "UPDATE seminar_user " .
-                "SET label = :label " .
-                "WHERE user_id = :user_id " .
-                "AND Seminar_id = :seminar_id " .
-                "");
-            $statement->execute([
-                'user_id' => $user_id,
-                'seminar_id' => $this->getId(),
-                'label' => $label
-            ]);
-            NotificationCenter::postNotification("CourseDidChangeMemberLabel", $this);
-        }
-    }
-
     /**
      * returns array with information about enrolment to this course for given user_id
      * ['enrolment_allowed'] : true or false
@@ -2386,10 +2434,7 @@ class Seminar
             case 'first':
             default:
                 // Move all others on the waitlist up by the number of people to add.
-                DBManager::get()->execute("UPDATE `admission_seminar_user`
-                        SET `position`=`position`+1
-                        WHERE `seminar_id`=?
-                            AND `status`='awaiting'", [$this->id]);
+                AdmissionApplication::renumberAdmission($this->id);
                 $waitpos = 1;
         }
         $new_admission_member = new AdmissionApplication();
diff --git a/lib/classes/UserManagement.class.php b/lib/classes/UserManagement.class.php
index 236318bbb3b..e44a7139529 100644
--- a/lib/classes/UserManagement.class.php
+++ b/lib/classes/UserManagement.class.php
@@ -20,7 +20,6 @@
  */
 
 // Imports
-require_once 'lib/admission.inc.php';   // remove user from waiting lists
 require_once 'lib/statusgruppe.inc.php';    // remove user from statusgroups
 require_once 'lib/messaging.inc.php';   // remove messages send or recieved by user
 require_once 'lib/object.inc.php';
@@ -126,25 +125,29 @@ class UserManagement
                     $this->user->visible = 'yes';
                 }
                 if ($nperms[$this->user->perms] < $nperms[$this->user->getPristineValue('perms')]) {
-                    $query = "UPDATE seminar_user
-                              INNER JOIN seminare ON (seminare.Seminar_id = seminar_user.Seminar_id)
-                              SET seminar_user.status = :new_max_status
-                              WHERE seminar_user.user_id = :user_id
-                                AND seminar_user.status IN (:old_status)
-                                AND seminare.status NOT IN (:studygroups)";
-                    $downgrade = DBManager::get()->prepare($query);
                     $old_status = [];
                     foreach ($nperms as $status => $n) {
                         if ($n > $nperms[$this->user->perms] && $n <= $nperms[$this->user->getPristineValue('perms')]) {
                             $old_status[] = $status;
                         }
                     }
-                    $downgrade->execute([
-                        'user_id'        => $this->user->id,
-                        'old_status'     => $old_status,
-                        'studygroups'    => studygroup_sem_types(),
-                        'new_max_status' => $this->user->perms,
-                    ]);
+                    $new_status =  $this->user->perms;
+                    CourseMember::findEachBySQL(
+                        function (CourseMember $cm) use ($new_status) {
+                            $cm->status = $new_status;
+                            $cm->store();
+                        },
+                        'INNER JOIN seminare ON (seminare.Seminar_id = seminar_user.Seminar_id)
+                        WHERE seminar_user.user_id = ?
+                                AND seminar_user.status IN (?)
+                                AND seminare.status NOT IN (?)
+                        ',
+                        [
+                            $this->user->id,
+                            $old_status,
+                            studygroup_sem_types()
+                        ]
+                    );
                 }
             }
         }
@@ -551,36 +554,35 @@ class UserManagement
             $this->re_sort_position_in_seminar_user();
 
             // delete all seminar entries
-            $query = "SELECT seminar_id FROM seminar_user WHERE user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->user_data['auth_user_md5.user_id']]);
-            $seminar_ids = $statement->fetchAll(PDO::FETCH_COLUMN);
-
-            $query = "DELETE FROM seminar_user WHERE user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->user_data['auth_user_md5.user_id']]);
-            if ($count = $statement->rowCount()) {
+            $course_member = SimpleCollection::createFromArray(
+                CourseMember::findByUser($this->user_data['auth_user_md5.user_id'])
+            );
+            $seminar_ids = $course_member->pluck('seminar_id');
+            $count = 0;
+            foreach($course_member as $member) {
+                $member->delete();
+                $count++;
+            }
+            if ($count) {
                 $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§';
-                array_map('update_admission', $seminar_ids);
+                array_map('AdmissionApplication::addMembers', $seminar_ids);
             }
             // delete all entries from waiting lists
-            $query = "SELECT seminar_id FROM admission_seminar_user WHERE user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->user_data['auth_user_md5.user_id']]);
-            $seminar_ids = $statement->fetchAll(PDO::FETCH_COLUMN);
-
-            $query = "DELETE FROM admission_seminar_user WHERE user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->user_data['auth_user_md5.user_id']]);
-            if ($count = $statement->rowCount()) {
+            $admission_members = SimpleCollection::createFromArray(
+                AdmissionApplication::findByUser($this->user_data['auth_user_md5.user_id'])
+            );
+            $seminar_ids = $admission_members->pluck('seminar_id');
+            $count = 0;
+            foreach ($admission_members as $admission_member) {
+                $admission_member->delete();
+                $count++;
+            }
+            if ($count) {
                 $this->msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§';
-                array_map('update_admission', $seminar_ids);
+                array_map('AdmissionApplication::addMembers', $seminar_ids);
             }
             // delete 'Studiengaenge'
-            $query = "DELETE FROM user_studiengang WHERE user_id = ?";
-            $statement = DBManager::get()->prepare($query);
-            $statement->execute([$this->user_data['auth_user_md5.user_id']]);
-            if ($count = $statement->rowCount()) {
+            if ($count = UserStudyCourse::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) {
                 $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§';
             }
             // delete all private appointments of this user
@@ -1113,7 +1115,7 @@ class UserManagement
         $statement->execute([$user_id]);
         if ($count = $statement->rowCount()) {
             $msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§';
-            array_map('update_admission', $seminar_ids);
+            array_map('AdmissionApplication::addMembers', $seminar_ids);
         }
 
         // delete all personal news from this user
@@ -1277,9 +1279,9 @@ class UserManagement
         $statement->execute([$this->user_data['auth_user_md5.user_id']]);
         while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
             if ($row['status'] === 'tutor') {
-                re_sort_tutoren($row['Seminar_id'], $row['position']);
+                CourseMember::resortMembership($row['Seminar_id'], (int)$row['position']);
             } else if ($row['status'] === 'dozent') {
-                re_sort_dozenten($row['Seminar_id'], $row['position']);
+                CourseMember::resortMembership($row['Seminar_id'], (int)$row['position'], 'dozent');
             }
         }
     }
@@ -1292,8 +1294,6 @@ class UserManagement
     */
     public function changePassword($password)
     {
-        global $perm;
-
         $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password);
         $this->storeToDatabase();
 
diff --git a/lib/functions.php b/lib/functions.php
index b47bcec2c07..bce4215064b 100644
--- a/lib/functions.php
+++ b/lib/functions.php
@@ -522,6 +522,7 @@ function get_config($key)
  *
  * @param string $s_id     the seminar to work on
  * @param int    $position the position to start with
+ * @deprecated since Stud.IP 5.3
  *
  * @return void
  */
@@ -540,6 +541,7 @@ function re_sort_dozenten($s_id, $position)
  *
  * @param string $s_id     the seminar to work on
  * @param int    $position the position to start with
+ * @deprecated since Stud.IP 5.3
  *
  * @return void
  */
@@ -558,6 +560,7 @@ function re_sort_tutoren($s_id, $position)
  *
  * @param string $status     can be on of 'tutor', 'dozent', ...
  * @param string $seminar_id the seminar to work on
+ * @deprecated since Stud.IP 5.3
  *
  * @return int  the next available position
  */
@@ -569,7 +572,7 @@ function get_next_position($status, $seminar_id)
     $statement = DBManager::get()->prepare($query);
     $statement->execute([$seminar_id, $status]);
 
-   return $statement->fetchColumn() ?: 0;
+    return $statement->fetchColumn() ?: 0;
 }
 
 /**
diff --git a/lib/models/AdmissionApplication.class.php b/lib/models/AdmissionApplication.class.php
index 21cd1f46e33..4da0c6b83a8 100644
--- a/lib/models/AdmissionApplication.class.php
+++ b/lib/models/AdmissionApplication.class.php
@@ -102,4 +102,178 @@ class AdmissionApplication extends SimpleORMap implements PrivacyObject
             }
         }
     }
+
+    /**
+     * @param string $course_id
+     * @param string $sort_status
+     * @param string $order_by
+     * @return array
+     */
+    public static function getAdmissionMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array
+    {
+        [$order, $asc] = explode(' ', $order_by);
+        if ($order === 'nachname') {
+            $order_by = "nachname {$asc},vorname {$asc}";
+        }
+
+        $cs = CourseSet::getSetForCourse($course_id);
+        $claiming = [];
+        if (is_object($cs) && !$cs->hasAlgorithmRun()) {
+            foreach (AdmissionPriority::getPrioritiesByCourse($cs->getId(), $course_id) as $user_id => $p) {
+                $user = User::find($user_id);
+                $data = $user->toArray('user_id username vorname nachname email');
+                $data['fullname'] = $user->getFullname('full_rev');
+                $data['position'] = $cs->hasAdmissionRule('LimitedAdmission') ? $p : '-';
+                $data['visible'] = 'unknown';
+                $data['status'] = 'claiming';
+                $claiming[] = $data;
+            }
+        }
+
+        $query = "SELECT asu.user_id, username, Vorname, Nachname, Email, status,
+                         position, asu.mkdate, asu.visible, asu.comment,
+                         {$GLOBALS['_fullname_sql']['full_rev']} AS fullname
+                  FROM admission_seminar_user AS asu
+                  JOIN auth_user_md5 USING (user_id)
+                  JOIN user_info USING (user_id)
+                  WHERE seminar_id = ?
+                  ORDER BY position, Nachname";
+        $st = DBManager::get()->prepare($query);
+        $st->execute([$course_id]);
+        $application_members = SimpleCollection::createFromArray(array_merge($claiming, $st->fetchAll(PDO::FETCH_ASSOC)));
+        $filtered_members = [];
+        foreach (['awaiting', 'accepted', 'claiming'] as $status) {
+            $filtered_members[$status] = $application_members->findBy('status', $status);
+            if ($status === $sort_status) {
+                $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING);
+            }
+        }
+        return $filtered_members;
+    }
+
+    /**
+     * returns the position for a user on a waiting list
+     *
+     * if the user is not found false is returned, return true if the user is found but
+     * no position is available
+     *
+     * @param        string $user_id user_id
+     * @param        string $seminar_id seminar_id
+     * @return       bool position in waiting list or false if not found
+     *
+     */
+    public static function checkMemberPosition(string $user_id, string $seminar_id): bool
+    {
+        $position = DBManager::get()->fetchColumn("SELECT IFNULL(position, 'na')
+              FROM admission_seminar_user
+              WHERE user_id = ? AND seminar_id = ? AND status = 'awaiting'",
+            [$user_id, $seminar_id]
+        );
+
+        return $position === 'na';
+    }
+
+    /**
+     * @param string $seminar_id
+     * @param string $send_message
+     * @return void
+     * @throws NotificationVetoException
+     */
+    public static function addMembers(string $seminar_id, bool $send_message = true): void
+    {
+        $messaging = new messaging;
+
+        //Daten holen / Abfrage ob ueberhaupt begrenzt
+        $seminar = Seminar::GetInstance($seminar_id, true);
+
+        if($seminar->isAdmissionEnabled()){
+            $sem_preliminary = ($seminar->admission_prelim == 1);
+            $cs = $seminar->getCourseSet();
+            //Veranstaltung einfach auffuellen (nach Lostermin und Ende der Kontingentierung)
+            if (!$seminar->admission_disable_waitlist_move && $cs->hasAlgorithmRun()) {
+                $count = (int)$seminar->getFreeAdmissionSeats();
+                $memberships = self::findBySQL(
+                    "seminar_id = ? AND status = 'awaiting' ORDER BY position LIMIT {$count}",
+                    [$seminar_id]
+                );
+                foreach ($memberships as $membership) {
+                    //ok, here ist the "colored-group" meant (for grouping on meine_seminare), not the grouped seminars as above!
+                    $group = select_group($seminar->getSemesterStartTime());
+                    if (!$sem_preliminary) {
+                        $course_membership = new CourseMember([$seminar_id, $membership->id]);
+                        $course_membership->setData([
+                            'status'     => 'autor',
+                            'gruppe'     => $group,
+                        ]);
+                        $affected = $course_membership->store();
+
+                        NotificationCenter::postNotification('UserDidEnterCourse', $seminar->getId(), $membership->user_id);
+                    } else {
+                        $membership->status = 'accepted';
+                        $affected = $membership->store();
+                    }
+                    if ($affected > 0) {
+                        $log_message = 'Wurde automatisch aus der Warteliste in die Veranstaltung eingetragen.';
+                        StudipLog::log('SEM_USER_ADD', $seminar->getId(), $membership->user_id, $sem_preliminary ? 'accepted' : 'autor', $log_message);
+                        if (!$sem_preliminary) {
+                            $affected = $membership->delete();
+                        } else {
+                            $affected = 0;
+                        }
+                        //User benachrichtigen
+                        if (($sem_preliminary || $affected > 0) && $send_message) {
+                            setTempLanguage($membership->user_id);
+                            if (!$sem_preliminary) {
+                                $message = sprintf (_('Sie sind in die Veranstaltung **%s (%s)** eingetragen worden, da für Sie ein Platz frei geworden ist. Damit sind Sie für die Teilnahme an der Veranstaltung zugelassen. Ab sofort finden Sie die Veranstaltung in der Übersicht Ihrer Veranstaltungen.'), $seminar->getName(), $seminar->getFormattedTurnus(true));
+                            } else {
+                                $message = sprintf (_('Sie haben den Status vorläufig akzeptiert in der Veranstaltung **%s (%s)** erhalten, da für Sie ein Platz frei geworden ist.'), $seminar->getName(), $seminar->getFormattedTurnus(true));
+                            }
+                            $subject = sprintf(_("Teilnahme an der Veranstaltung %s"), $seminar->getName());
+                            restoreLanguage();
+
+                            $messaging->insert_message($message, $membership->username, '____%system%____', false, false, '1', false, $subject, true);
+                        }
+                    }
+                }
+                //Warteposition der restlichen User neu eintragen
+                AdmissionApplication::renumberAdmission($seminar_id, FALSE);
+            }
+            $seminar->restore();
+        }
+    }
+
+    /**
+     * Renumber admissions
+     * @param string $seminar_id
+     * @param bool $send_message
+     * @return void
+     */
+    public static function renumberAdmission (string $seminar_id, bool $send_message = true): void
+    {
+        $messaging = new messaging;
+        $seminar = Seminar::GetInstance($seminar_id);
+        if ($seminar->isAdmissionEnabled()) {
+            $admission_users = self::findBySQL(
+                "seminar_id = ? AND status = 'awaiting' ORDER BY position",
+                [$seminar->id]
+            );
+            $position = 1;
+            foreach ($admission_users as $admission) {
+                $admission->position = $position;
+                if ($admission->store() && Config::get()->NOTIFY_ON_WAITLIST_ADVANCE && $send_message) {
+                    $username = $admission->user->username;
+                    setTempLanguage($admission->user_id);
+                    $message = sprintf(_('Sie sind auf der Warteliste der Veranstaltung **%s (%s)** hochgestuft worden. Sie stehen zur Zeit auf Position %s.'),
+                        $seminar->name,
+                        $seminar->getFormattedTurnus(),
+                        $position);
+                    $subject = sprintf(_('Ihre Position auf der Warteliste der Veranstaltung %s wurde verändert'), $seminar->name);
+                    restoreLanguage();
+
+                    $messaging->insert_message($message, $username, '____%system%____', FALSE, FALSE, '1', FALSE, $subject);
+                }
+                $position += 1;
+            }
+        }
+    }
 }
diff --git a/lib/models/CourseMember.class.php b/lib/models/CourseMember.class.php
index 4546e09969b..f0ebaaec257 100644
--- a/lib/models/CourseMember.class.php
+++ b/lib/models/CourseMember.class.php
@@ -155,6 +155,145 @@ class CourseMember extends SimpleORMap implements PrivacyObject
         CourseMemberNotification::deleteBySQL('user_id = ?', [$this->user_id]);
     }
 
+    /**
+     * Get members of a course
+     *
+     * @param string $course_id
+     * @param string $sort_status
+     * @param string $order_by
+     * @return array
+     */
+    public function getMembers(string $course_id, string $sort_status = 'autor', string $order_by = 'nachname asc'): array
+    {
+        [$order, $asc] = explode(' ', $order_by);
+        if ($order === 'nachname') {
+            $order_by = "Nachname {$asc},Vorname {$asc}";
+        }
+
+        $query = "SELECT su.user_id, username, Vorname, Nachname, Email, status,
+                         position, su.mkdate, su.visible, su.comment,
+                         {$GLOBALS['_fullname_sql']['full_rev']} AS fullname
+                  FROM seminar_user AS su
+                  INNER JOIN auth_user_md5 USING (user_id)
+                  INNER JOIN user_info USING (user_id)
+                  WHERE seminar_id = ?
+                  ORDER BY position, Nachname ASC";
+        $st = DBManager::get()->prepare($query);
+        $st->execute([$course_id]);
+        $members = SimpleCollection::createFromArray($st->fetchAll(PDO::FETCH_ASSOC));
+        $filtered_members = [];
+
+        foreach (['user', 'autor', 'tutor', 'dozent'] as $status) {
+            $filtered_members[$status] = $members->findBy('status', $status);
+            if ($status === $sort_status) {
+                $filtered_members[$status]->orderBy($order_by, $order !== 'nachname' ? SORT_NUMERIC : SORT_LOCALE_STRING);
+            } else {
+                $filtered_members[$status]->orderBy(in_array($status, ['tutor', 'dozent']) ? 'position,Nachname,Vorname' : 'Nachname,Vorname');
+            }
+        }
+        return $filtered_members;
+    }
+
+    /**
+     * Get user informations by first and last name for csv-import
+     * @param string $course_id
+     * @param string $nachname
+     * @param string $vorname
+     * @return array
+     */
+    public function getMemberByIdentification(string $course_id, string $nachname, string $vorname = null): array
+    {
+        return DBManager::get()->fetchAll("SELECT
+                    auth_user_md5.user_id,
+                    auth_user_md5.username,
+                    auth_user_md5.perms,
+                    seminar_user.Seminar_id AS is_present,
+                    {$GLOBALS['_fullname_sql']['full_rev']} AS fullname
+                 FROM auth_user_md5
+                 LEFT JOIN user_info USING (user_id)
+                 LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?)
+                 WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent')
+                 AND auth_user_md5.visible <> 'never'
+                 AND auth_user_md5.Nachname LIKE ? AND (? IS NULL OR auth_user_md5.Vorname LIKE ?)
+                 ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname",
+            [$course_id, $nachname, $vorname, $vorname]);
+    }
+
+    /**
+     * Get user informations by username for csv-import
+     * @param string $course_id
+     * @param string $username
+     * @return Array
+     */
+    public function getMemberByUsername(string $course_id, string $username): array
+    {
+        return DBManager::get()->fetchAll(
+            "SELECT auth_user_md5.user_id,
+                    auth_user_md5.username,
+                    auth_user_md5.perms,
+                    seminar_user.Seminar_id AS is_present,
+                    {$GLOBALS['_fullname_sql']['full_rev']} AS fullname
+             FROM auth_user_md5
+             LEFT JOIN user_info USING (user_id)
+             LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?)
+             WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent')
+             AND auth_user_md5.visible <> 'never'
+               AND auth_user_md5.username LIKE ?
+             ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname",
+            [$course_id, $username]
+        );
+    }
+
+    /**
+     * Get user informations by email for csv-import
+     * @param String $course_id
+     * @param String $email
+     * @return Array
+     */
+    public function getMemberByEmail($course_id, $email): array
+    {
+        return DBManager::get()->fetchAll(
+            "SELECT a.user_id, username,
+                    perms, b.Seminar_id AS is_present
+             FROM auth_user_md5 AS a
+             LEFT JOIN user_info USING (user_id)
+             LEFT JOIN seminar_user AS b ON (b.user_id = a.user_id AND b.Seminar_id = ?)
+             WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent')
+             AND a.visible <> 'never'
+               AND email LIKE ?
+             ORDER BY Nachname, Vorname",
+            [$course_id, $email]
+        );
+    }
+
+    /**
+     * Get user informations by generic datafields for csv-import
+     * @param string $course_id
+     * @param string $nachname
+     * @param string $datafield_id
+     * @return Array
+     */
+    public function getMemberByDatafield(string $course_id, string $nachname,  string $datafield_id): array
+    {
+        // TODO Fullname
+        return DBManager::get()->fetchAll(
+            "SELECT
+                auth_user_md5.user_id,
+                auth_user_md5.username,
+                seminar_user.Seminar_id AS is_present,
+                {$GLOBALS['_fullname_sql']['full_rev']} AS fullname
+             FROM datafields_entries
+             LEFT JOIN auth_user_md5 ON (auth_user_md5.user_id = datafields_entries.range_id)
+             LEFT JOIN user_info USING (user_id)
+             LEFT JOIN seminar_user ON (seminar_user.user_id = auth_user_md5.user_id AND seminar_user.Seminar_id = ?)
+             WHERE auth_user_md5.perms IN ('autor', 'tutor', 'dozent')
+             AND auth_user_md5.visible <> 'never'
+               AND datafields_entries.datafield_id = ? AND datafields_entries.content = ?
+             ORDER BY auth_user_md5.Nachname, auth_user_md5.Vorname",
+            [$course_id, $datafield_id, $nachname]
+        );
+    }
+
     /**
      * Export available data of a given user into a storage object
      * (an instance of the StoredUserData class) for that user.
@@ -174,4 +313,130 @@ class CourseMember extends SimpleORMap implements PrivacyObject
             }
         }
     }
+
+    /**
+     * return the highest position-number increased by one for the
+     * passed user-group in the passed seminar
+     *
+     * @param string $status     can be on of 'tutor', 'dozent', ...
+     * @param string $seminar_id the seminar to work on
+     *
+     * @return int  the next available position
+     */
+    public static function getNextPosition(string $status, string $seminar_id): int
+    {
+        return (int) DBManager::get()->fetchColumn(
+            "SELECT MAX(position) + 1
+              FROM seminar_user
+              WHERE Seminar_id = ? AND status = ?",
+            [$seminar_id, $status]
+        );
+    }
+
+    /**
+     * reset the order-positions for the given status in the passed seminar,
+     * starting at the passed position
+     *
+     * @param string $course_id     the seminar to work on
+     * @param int    $position the position to start with
+     *
+     * @return void
+     */
+    public static function resortMembership(string $course_id, int $position, string $status = 'tutor')
+    {
+        self::findEachBySQL(
+            function (CourseMember $membership) {
+                $membership->position = $membership->position - 1;
+                $membership->store();
+            },
+            "Seminar_id = ? AND position > ? AND status = ? ",
+            [$course_id, $position, $status]
+        );
+    }
+    /**
+     * Insert a user into a seminar with optional log-message and contingent
+     *
+     * @param string   $seminar_id
+     * @param string   $user_id
+     * @param string   $status       status of user in the seminar (user, autor, tutor, dozent)
+     * @param boolean  $copy_studycourse  if true, the studycourse is copied from admission_seminar_user
+     *                                    to seminar_user. Overrides the $contingent-parameter
+     * @param string   $contingent   optional studiengang_id, if no id is given, no contingent is considered
+     * @param string   $log_message  optional log-message. if no log-message is given a default one is used
+     * @return bool
+     */
+    public static function insertCourseMember($seminar_id, $user_id, $status, $copy_studycourse = false, $contingent = false, $log_message = false): bool
+    {
+        if (!$user_id) {
+            return false;
+        }
+        // get the seminar-object
+        $sem = Seminar::GetInstance($seminar_id);
+
+        $admission_status = '';
+        $admission_comment = '';
+        $mkdate = time();
+
+        $admission_user = AdmissionApplication::find([$seminar_id, $user_id]);
+        if ($admission_user) {
+            // copy the studycourse from admission_seminar_user
+            if ($copy_studycourse && $admission_user->studiengang_id) {
+                $contingent = $admission_user->studiengang_id;
+            }
+            $admission_status = $admission_user->status;
+            $admission_comment = $admission_user->comment ?? '';
+            $mkdate = $admission_user->mkdate;
+        }
+
+        // check if there are places left in the submitted contingent (if any)
+        //ignore if preliminary
+        if ($admission_status !== 'accepted' && $contingent && $sem->isAdmissionEnabled() && !$sem->getFreeAdmissionSeats()) {
+            return false;
+        }
+
+        // get coloured group as used on meine_seminare
+        $colour_group = $sem->getDefaultGroup();
+
+        // LOGGING
+        // if no log message is submitted use a default one
+        if (!$log_message) {
+            $log_message = 'Wurde in die Veranstaltung eingetragen, admission_status: '. $admission_status . ' Kontingent: ' . $contingent;
+        }
+        StudipLog::log('SEM_USER_ADD', $seminar_id, $user_id, $status, $log_message);
+        $membership = new self([$seminar_id, $user_id]);
+        $membership->setData([
+            'Seminar_id' => $seminar_id,
+            'user_id'    => $user_id,
+            'status'     => $status,
+            'comment'    => $admission_comment,
+            'gruppe'     => $colour_group,
+            'mkdate'     => $mkdate,
+        ]);
+        $membership->store();
+
+        NotificationCenter::postNotification('UserDidEnterCourse', $seminar_id, $user_id);
+
+        if ($admission_status) {
+            $admission_user->delete();
+
+            //renumber the waiting/accepted/lot list, a user was deleted from it
+            AdmissionApplication::renumberAdmission($seminar_id);
+        }
+        $cs = $sem->getCourseSet();
+        if ($cs) {
+            AdmissionPriority::unsetPriority($cs->getId(), $user_id, $sem->getId());
+        }
+
+        CalendarScheduleModel::deleteSeminarEntries($user_id, $seminar_id);
+
+        // reload the seminar, the contingents have changed
+        $sem->restore();
+
+        // Check if a parent course exists and insert user there.
+        if ($sem->parent_course) {
+            self::insertCourseMember($sem->parent_course, $user_id, $status, $copy_studycourse, $contingent, $log_message);
+        }
+
+        return true;
+    }
 }
diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php
index 8feb13ea66e..e05f87fed79 100644
--- a/tests/functional/_bootstrap.php
+++ b/tests/functional/_bootstrap.php
@@ -10,6 +10,7 @@ require 'lib/classes/StudipAutoloader.php';
 require 'lib/functions.php';
 require_once 'lib/language.inc.php';
 require 'lib/visual.inc.php';
+require 'lib/messaging.inc.php';
 
 $STUDIP_BASE_PATH = realpath(dirname(__FILE__) . '/../..');
 
-- 
GitLab