From 7abf792f02366b517c243b963cbc3768e64bfe54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20Sch=C3=BCttl=C3=B6ffel?=
 <schuettloeffel@zqs.uni-hannover.de>
Date: Tue, 26 Nov 2024 09:30:35 +0000
Subject: [PATCH] =?UTF-8?q?Resolve=20"VA=20automatisch=20l=C3=B6schen=20we?=
 =?UTF-8?q?nn=20sie=20keine=20Mitglieder=20mehr=20hat"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #4289

Merge request studip/studip!3144
---
 db/migrations/6.0.34_add_dummy_teacher.php | 57 +++++++++++++
 lib/classes/UserManagement.php             | 99 +++++++++++++---------
 tests/jsonapi/UsersIndexTest.php           |  4 +-
 3 files changed, 119 insertions(+), 41 deletions(-)
 create mode 100644 db/migrations/6.0.34_add_dummy_teacher.php

diff --git a/db/migrations/6.0.34_add_dummy_teacher.php b/db/migrations/6.0.34_add_dummy_teacher.php
new file mode 100644
index 00000000000..d987dc20cfd
--- /dev/null
+++ b/db/migrations/6.0.34_add_dummy_teacher.php
@@ -0,0 +1,57 @@
+<?php
+class AddDummyTeacher extends Migration
+{
+    public function description()
+    {
+        return 'Adds a dummy teacher (N.N.). Adds config to set dummy teachers id. @see tic #4289';
+    }
+
+    protected function up()
+    {
+        // Add dummy teacher
+        $stm = DBManager::get()->prepare("SELECT * FROM `auth_user_md5` WHERE `username` = 'N.N.'");
+        $stm->execute();
+        if ($stm->rowCount() > 0) {
+            $res = $stm->fetch();
+            $user_id = $res['user_id'];
+        } else {
+            $user_id = '2afaa0dce05f0b12a7318075e52879e2';
+            DBManager::get()->execute(
+                "INSERT INTO `auth_user_md5` (user_id, username, perms, Vorname, Nachname, visible) VALUES (:user_id, :username, :perms, :Vorname, :Nachname, :visible)",
+                [
+                    'user_id' => $user_id,
+                    'username' => 'N.N.',
+                    'perms' => 'dozent',
+                    'Vorname' => 'N.',
+                    'Nachname' => 'N.',
+                    'visible' => 'never'
+                ]
+            );
+        }
+
+        // Add config
+        DBManager::get()->execute(
+            "INSERT IGNORE INTO `config` VALUES (:field, :value, :type, :range, :section, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :desc)",
+            [
+                'field' => 'DUMMY_TEACHER_ID',
+                'value' => $user_id,
+                'type' => 'string',
+                'range' => 'global',
+                'section' => 'global',
+                'desc' => 'ID of user that should be added to course if no teacher is left'
+            ]
+        );
+    }
+
+    protected function down()
+    {
+        // Dont delete dummy user, maybe it existed before this migration
+
+        // Delete config
+        $query = "DELETE `config`, `config_values`
+                  FROM `config`
+                  LEFT JOIN `config_values` USING (`field`)
+                  WHERE `field` = 'DUMMY_TEACHER_ID'";
+        DBManager::get()->exec($query);
+    }
+}
diff --git a/lib/classes/UserManagement.php b/lib/classes/UserManagement.php
index 8e7821044a6..f4c47108e55 100644
--- a/lib/classes/UserManagement.php
+++ b/lib/classes/UserManagement.php
@@ -812,39 +812,61 @@ class UserManagement
             }
         }
 
-        // active dozent?
-        $query = "SELECT COUNT(*)
-                  FROM (
-                      SELECT 1
-                      FROM `seminar_user` AS `su1`
-                      -- JOIN seminar_user to check for other teachers
-                      INNER JOIN `seminar_user` AS `su2`
-                        ON (`su1`.`seminar_id` = `su2`.`seminar_id` AND `su2`.`status` = 'dozent')
-                      -- JOIN seminare to check the status for studygroup mode
-                      INNER JOIN `seminare`
-                        ON (`su1`.`seminar_id` = `seminare`.`seminar_id`)
-                      WHERE `su1`.`user_id` = :user_id
-                        AND `su1`.`status` = 'dozent'
-                        AND `seminare`.`status` NOT IN (
-                            -- Select all status ids for studygroups
-                            SELECT `id`
-                            FROM `sem_classes`
-                            WHERE `studygroup_mode` = 1
-                        )
-                      GROUP BY `su1`.`seminar_id`
-                      HAVING COUNT(*) = 1
-                      ORDER BY NULL
-                  ) AS `sub`";
-        $statement = DBManager::get()->prepare($query);
-        $statement->bindValue(':user_id', $this->user_data['auth_user_md5.user_id']);
-        $statement->execute();
-        $active_count = $statement->fetchColumn() ?: 0;
-
-        if ($active_count && $delete_memberships) {
-            $this->msg .= 'error§' . sprintf(_('<em>%s</em> ist Lehrkraft in %s aktiven Veranstaltungen und kann daher nicht gelöscht werden.'), $this->user_data['auth_user_md5.username'], $active_count) . '§';
+        // Check if dummy teacher exists
+        if (!Config::get()->DUMMY_TEACHER_ID || !User::find(Config::get()->DUMMY_TEACHER_ID)) {
+            $this->msg .= 'error§' . sprintf(
+                _('Dummy-Dozent (id: %s) nicht gefunden. Bitte DUMMY_TEACHER_ID in Konfiguration setzen.'),
+                Config::get()->DUMMY_TEACHER_ID) . '§';
             return false;
-        //founder of studygroup?
-        } elseif (Config::get()->STUDYGROUPS_ENABLE) {
+        }
+
+        // Delete courses where user is the only one left (besides the dummy teacher)
+        $members = CourseMember::findBySQL(
+            "LEFT JOIN seminare USING(Seminar_id)
+            WHERE seminare.status NOT IN (?) AND user_id != ?
+            GROUP BY Seminar_id
+            HAVING COUNT(DISTINCT user_id) = 1 AND user_id = ?",
+            [
+                studygroup_sem_types(),
+                Config::get()->DUMMY_TEACHER_ID,
+                $this->user->id
+            ]
+        );
+        foreach ($members as $member) {
+            $this->msg .= 'info§' . sprintf(
+                _('User ist einziges Mitglied in Veranstaltung (%s), lösche Veranstaltung.'),
+                $member->course->id
+            ) . '§';
+            $member->course->delete();
+        }
+
+        // Add dummy teacher to courses when only the user is left as teacher
+        $members = CourseMember::findBySQL(
+            "LEFT JOIN seminare USING(Seminar_id)
+            WHERE seminare.status NOT IN (?) AND user_id != ? AND seminar_user.status = 'dozent'
+            GROUP BY Seminar_id
+            HAVING COUNT(DISTINCT user_id) = 1 AND user_id = ?",
+            [
+                studygroup_sem_types(),
+                Config::get()->DUMMY_TEACHER_ID,
+                $this->user->id
+            ]
+        );
+        foreach ($members as $member) {
+            $this->msg .= 'info§' . sprintf(
+                _('User ist einziger Dozent in Veranstaltung (id: %s), füge Dummy-Dozent hinzu.'),
+                $member->course->id
+            ) . '§';
+            CourseMember::insertCourseMember(
+                $member->course->id,
+                Config::get()->DUMMY_TEACHER_ID,
+                'dozent'
+            );
+
+        }
+
+        // Founder of studygroup?
+        if (Config::get()->STUDYGROUPS_ENABLE) {
             $status = studygroup_sem_types();
 
             if (empty($status)) {
@@ -896,6 +918,11 @@ class UserManagement
         // delete user from instituts
         $this->logInstUserDel($this->user_data['auth_user_md5.user_id']);
 
+        // Delete from all courses and studygroups
+        if ($count = CourseMember::deleteByUser_id($this->user->id)) {
+            $this->msg .= 'info§' . sprintf(_('Aus %s Veranstaltungen/Studiengruppen ausgetragen.'), $count) . '§';
+        }
+
         if ($delete_memberships) {
             $query = "DELETE FROM user_inst WHERE user_id = ?";
             $statement = DBManager::get()->prepare($query);
@@ -927,14 +954,6 @@ class UserManagement
 
             $this->re_sort_position_in_seminar_user();
 
-            // delete user from seminars (postings will be preserved)
-            $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()) {
-                $this->msg .= 'info§' . sprintf(_('%s Einträge aus Veranstaltungen gelöscht.'), $count) . '§';
-            }
-
             $query = "DELETE FROM `termin_related_persons` WHERE `user_id` = ?";
             $statement = DBManager::get()->prepare($query);
             $statement->execute([$this->user_data['auth_user_md5.user_id']]);
diff --git a/tests/jsonapi/UsersIndexTest.php b/tests/jsonapi/UsersIndexTest.php
index adffee632ce..6c79cae8fd6 100644
--- a/tests/jsonapi/UsersIndexTest.php
+++ b/tests/jsonapi/UsersIndexTest.php
@@ -28,7 +28,9 @@ class UsersIndexTest extends \Codeception\Test\Unit
         $response = $this->getUsers($credentials);
         $this->tester->assertTrue($response->isSuccessfulDocument([200]));
 
-        $numberOfAllUsers = \User::countBySQL();
+        $vis_query = get_vis_query(context: 'search');
+        $condition = "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) WHERE {$vis_query}";
+        $numberOfAllUsers = \User::countBySQL($condition);
         $this->tester->assertSame($numberOfAllUsers, count($response->document()->primaryResources()));
 
         $this->assertValidResourceObject($response, 'users');
-- 
GitLab