diff options
| author | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
|---|---|---|
| committer | Philipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de> | 2024-09-24 10:53:31 +0200 |
| commit | 4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch) | |
| tree | 5c07151ae61276d334e88f6309c30d439a85c12e /lib/classes/UserManagement.php | |
| parent | da0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff) | |
| parent | 97a188592c679890a25c37ab78463add76a52ff7 (diff) | |
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/classes/UserManagement.php')
| -rw-r--r-- | lib/classes/UserManagement.php | 1353 |
1 files changed, 1353 insertions, 0 deletions
diff --git a/lib/classes/UserManagement.php b/lib/classes/UserManagement.php new file mode 100644 index 0000000..4bd47ec --- /dev/null +++ b/lib/classes/UserManagement.php @@ -0,0 +1,1353 @@ +<?php +# Lifter007: TODO +/** + * UserManagement.php + * + * Management for the Stud.IP global users + * + * LICENSE + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * @author Stefan Suchi <suchi@data-quest> + * @author Suchi & Berg GmbH <info@data-quest.de> + * @copyright 2009 Stud.IP + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL Licence 2 + * @category Stud.IP + */ + +// Imports +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'; + +/** + * UserManagement.php + * + * Management for the Stud.IP global users + * + */ +class UserManagement +{ + private $user; + private $validator; + private $user_data; + + public $msg; + + private static $pwd_hasher; + + public static function getPwdHasher() + { + if (self::$pwd_hasher === null) { + self::$pwd_hasher = new PasswordHash(8, Config::get()->PHPASS_USE_PORTABLE_HASH); + } + return self::$pwd_hasher; + } + + /** + * Constructor + * + * Pass nothing to create a new user, or the user_id from an existing user to change or delete + * @param string $user_id the user which should be retrieved + */ + public function __construct($user_id = false) + { + $this->validator = new email_validation_class(); + $this->validator->timeout = 10; // How long do we wait for response of mailservers? + $this->getFromDatabase($user_id); + } + + public function __get($attr) + { + if ($attr === 'user_data') { + return $this->user_data; + } + } + + public function __set($attr, $value) + { + if ($attr === 'user_data') { + if (!is_array($value)) { + throw new InvalidArgumentException('user_data only accepts array'); + } + return $this->user_data->setData($value, true); + } + } + + /** + * load user data from database into internal array + * + * @param string $user_id the user which should be retrieved + */ + public function getFromDatabase($user_id) + { + $this->user = User::toObject($user_id); + if (!$this->user) { + $this->user = new User(); + } + $this->user_data = new UserDataAdapter($this->user); + } + + /** + * store user data from internal array into database + * + * @access private + * @return bool all data stored? + */ + private function storeToDatabase() + { + if ($this->user->isNew()) { + if ($this->user->store()) { + StudipLog::log('USER_CREATE', $this->user->id, null, implode(';', $this->user->toArray('username vorname nachname perms email'))); + return true; + } else { + return false; + } + } + + $nperms = [ + 'user' => 0, + 'autor' => 1, + 'tutor' => 2, + 'dozent' => 3 + ]; + + if ($this->user->isDirty('perms')) { + if ($this->user->perms === 'dozent' && in_array($this->user->getPristineValue('perms'), ['user','autor','tutor'])) { + $this->logInstUserDel($this->user->id, "inst_perms = 'user'"); + $this->user->institute_memberships->unsetBy('inst_perms', 'user'); + // make user visible globally if dozent may not be invisible (StEP 00158) + if (Config::get()->DOZENT_ALWAYS_VISIBLE && $this->user->visible !== 'never') { + $this->user->visible = 'yes'; + } + if ($nperms[$this->user->perms] < $nperms[$this->user->getPristineValue('perms')]) { + $old_status = []; + foreach ($nperms as $status => $n) { + if ($n > $nperms[$this->user->perms] && $n <= $nperms[$this->user->getPristineValue('perms')]) { + $old_status[] = $status; + } + } + $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() + ] + ); + } + } + } + foreach (words('username vorname nachname perms email title_front title_rear password') as $field) { + // logging + if ($this->user->isFieldDirty($field)) { + $old_value = $this->user->getPristineValue($field); + $value = $this->user->getValue($field); + switch ($field) { + case 'username': + StudipLog::log('USER_CHANGE_USERNAME', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'vorname': + StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Vorname: {$old_value} -> {$value}"); + break; + case 'nachname': + StudipLog::log('USER_CHANGE_NAME', $this->user->id, null, "Nachname: {$old_value} -> {$value}"); + break; + case 'perms': + StudipLog::log('USER_CHANGE_PERMS', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'email': + StudipLog::log('USER_CHANGE_EMAIL', $this->user->id, null, "{$old_value} -> {$value}"); + break; + case 'title_front': + StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_front: {$old_value} -> {$value}"); + break; + case 'title_rear': + StudipLog::log('USER_CHANGE_TITLE', $this->user->id, null, "title_rear: {$old_value} -> {$value}"); + case 'password': + StudipLog::log('USER_CHANGE_PASSWORD', $this->user->id, null, "password: {$old_value} -> {$value}"); + break; + } + } + } + + $changed = $this->user->store(); + return (bool) $changed; + } + + + /** + * generate a secure password of $length characters [a-z0-9] + * + * @param integer $length number of characters + * @return string password + */ + public function generate_password($length) + { + $pass = ""; + mt_srand((double) microtime() * 1000000); + for ($i = 1; $i <= $length; $i++) { + $temp = mt_rand() % 36; + if ($temp < 10) { + $temp += 48; // 0 = chr(48), 9 = chr(57) + } else { + $temp += 87; // a = chr(97), z = chr(122) + } + $pass .= chr($temp); + } + return $pass; + } + + + /** + * Check if Email-Adress is valid and reachable + * + * @param string Email-Adress to check + * @return bool Email-Adress valid and reachable? + */ + private function checkMail($Email) + { + // Adress correct? + if (!$this->validator->ValidateEmailAddress($Email)) { + $this->msg .= 'error§' . _('E-Mail-Adresse syntaktisch falsch!') . '§'; + return false; + } + + // E-Mail reachable? + if (!$this->validator->ValidateEmailHost($Email)) { + // Mailserver nicht erreichbar, ablehnen + $this->msg .= 'error§' . _('Mailserver ist nicht erreichbar!') . '§'; + return false; + } + + if (!$this->validator->ValidateEmailBox($Email)) { + // Nutzer unbekannt, ablehnen + $this->msg .= 'error§' . sprintf(_('E-Mail an <em>%s</em> ist nicht zustellbar!'), $Email) . '§'; + return false; + } + + return true; + } + + /** + * Create a new studip user with the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Creation successful? + */ + public function createNewUser($newuser) + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; + return false; + } + + if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, <em>Admin-Accounts</em> anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, <em>Root-Accounts</em> anzulegen.') . '§'; + return false; + } + + // Do we have all necessary data? + if (empty($newuser['auth_user_md5.username']) || empty($newuser['auth_user_md5.perms']) || empty($newuser['auth_user_md5.Email'])) { + $this->msg .= 'error§' . _('Bitte geben Sie <em>Username</em>, <em>Status</em> und <em>E-Mail</em> an!') . '§'; + return false; + } + + // Is the username correct? + if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + + // Can we reach the email? + if (!$this->checkMail($newuser['auth_user_md5.Email'])) { + return false; + } + + if (!$newuser['auth_user_md5.auth_plugin']) { + $newuser['auth_user_md5.auth_plugin'] = 'standard'; + } + + // Store new values in internal array + $this->getFromDatabase(null); + $this->user_data->setData($newuser); + + if ($this->user_data['auth_user_md5.auth_plugin'] === 'standard') { + $password = $this->generate_password(8); + $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); + } + + // Does the user already exist? + // NOTE: This should be a transaction, but it is not... + $temp = User::findByUsername($newuser['auth_user_md5.username']); + if ($temp) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn <em>%s</em> ist schon vorhanden!'), $newuser['auth_user_md5.username']) . '§'; + return false; + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $newuser['auth_user_md5.username']) . '§'; + return false; + } + + $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" angelegt.'), $newuser['auth_user_md5.username']) . '§'; + + // Automated entering new users, based on their status (perms) + $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $this->user_data['auth_user_md5.perms']); + + foreach ($result['added'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung <em>%s</em> wurde durchgeführt.'), $item) . '§'; + } + foreach ($result['removed'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung <em>%s</em> wurde durchgeführt.'), $item) . '§'; + } + + // include language-specific subject and mailbody + $user_language = $this->user_data['user_info.preferred_language'] ?: Config::get()->DEFAULT_LANGUAGE; + + // send mail with password generation link + self::sendPasswordMail($this->user, true); + $this->msg .= 'msg§' . _('Es wurde eine Mail mit Anweisungen zum Setzen des Passworts durch die/den Nutzer/in verschickt.') . '§'; + + // add default visibility settings + Visibility::createDefaultCategories($this->user_data['auth_user_md5.user_id']); + + return true; + } + + /** + * Create a new preliminary studip user with the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Creation successful? + */ + public function createPreliminaryUser($newuser) + { + global $perm; + + $this->getFromDatabase(null); + $this->user_data->setData($newuser); + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts anzulegen.') . '§'; + return false; + } + if (in_array($this->user->perms, words('root admin'))) { + $this->msg .= 'error§' . _('Es können keine vorläufigen Administrationsaccounts angelegt werden.') . '§'; + return false; + } + if (!$this->user->id) { + $this->user->setId($this->user->getNewId()); + } + if (!$this->user->username) { + $this->user->username = $this->user->id; + } + $this->user->auth_plugin = null; + $this->user->visible = 'never'; + + // Do we have all necessary data? + if (empty ($this->user->perms) || empty ($this->user->vorname) || empty ($this->user->nachname)) { + $this->msg .= 'error§' . _('Bitte geben Sie <em>Status</em>, <em>Vorname</em> und <em>Nachname</em> an!') . '§'; + return false; + } + + // Is the username correct? + if (!$this->validator->ValidateUsername($this->user->username)) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + + // Does the user already exist? + // NOTE: This should be a transaction, but it is not... + $temp = User::findByUsername($this->user->username); + if ($temp) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn <em>%s</em> ist schon vorhanden!'), $this->user->username) . '§'; + return false; + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'error§' . sprintf(_('BenutzerIn "%s" konnte nicht angelegt werden.'), $this->user->username) . '§'; + return false; + } + + $this->msg .= 'msg§' . sprintf(_('BenutzerIn "%s" (vorläufig) angelegt.'), $this->user->username) . '§'; + + // add default visibility settings + Visibility::createDefaultCategories($this->user->id); + + return true; + } + + /** + * Change an existing studip user according to the given parameters + * + * @param array structure: array('string table_name.field_name'=>'string value') + * @return bool Change successful? + */ + public function changeUser($newuser) + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; + return false; + } + + if (!$perm->is_fak_admin() && $newuser['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, <em>Admin-Accounts</em> anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root') && $newuser['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung, <em>Root-Accounts</em> anzulegen.') . '§'; + return false; + } + + if (!$perm->have_perm('root')) { + if (!$perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung <em>Admin-Accounts</em> zu verändern.') . '§'; + return false; + } + + if ($this->user_data['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung <em>Root-Accounts</em> zu verändern.') . '§'; + return false; + } + + if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + if (!$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; + return false; + } + } + } + + // active dozent? (ignore the studygroup guys) + $status = studygroup_sem_types(); + + if (empty($status)) { + $count = 0; + } else { + $query = "SELECT COUNT(*) + FROM seminar_user AS su + LEFT JOIN seminare AS s USING (Seminar_id) + WHERE su.user_id = ? + AND s.status NOT IN (?) + AND su.status = 'dozent' + AND (SELECT COUNT(*) FROM seminar_user su2 WHERE Seminar_id = su.Seminar_id AND su2.status = 'dozent') = 1 + GROUP BY user_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $this->user_data['auth_user_md5.user_id'], + $status, + ]); + $count = $statement->fetchColumn(); + } + if ($count && isset($newuser['auth_user_md5.perms']) && $newuser['auth_user_md5.perms'] !== 'dozent') { + $this->msg .= 'error§' . sprintf(_('Der Benutzer <em>%s</em> ist alleiniger Lehrperson in %s aktiven Veranstaltungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; + return false; + } + + // active admin? + if ($this->user_data['auth_user_md5.perms'] === 'admin' && $newuser['auth_user_md5.perms'] !== 'admin') { + // count number of institutes where the user is admin + $query = "SELECT COUNT(*) + FROM user_inst + WHERE user_id = ? AND inst_perms = 'admin' + GROUP BY Institut_id"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + + // if there are institutes with admin-perms, add error-message and deny change + if ($count = $statement->fetchColumn()) { + $this->msg .= 'error§'. sprintf(_('Der Benutzer <em>%s</em> ist Admin in %s Einrichtungen und kann daher nicht in einen anderen Status versetzt werden!'), $this->user_data['auth_user_md5.username'], $count) . '§'; + return false; + } + } + + // Is the username correct? + if (isset($newuser['auth_user_md5.username'])) { + if ($this->user_data['auth_user_md5.username'] != $newuser['auth_user_md5.username']) { + if (!$this->validator->ValidateUsername($newuser['auth_user_md5.username'])) { + $this->msg .= 'error§' . _('Der gewählte Benutzername ist zu kurz oder enthält unzulässige Zeichen!') . '§'; + return false; + } + $check_uname = StudipAuthAbstract::CheckUsername($newuser['auth_user_md5.username']); + if ($check_uname['found']) { + $this->msg .= 'error§' . _('Der Benutzername wird bereits von einem anderen Benutzer verwendet. Bitte wählen Sie einen anderen Benutzernamen!') . '§'; + return false; + } else { + //$this->msg .= "info§" . $check_uname['error'] ."§"; + } + } else + unset($newuser['auth_user_md5.username']); + } + + // Can we reach the email? + if (isset($newuser['auth_user_md5.Email'])) { + if (!$this->checkMail($newuser['auth_user_md5.Email'])) { + return false; + } + } + + // Store changed values in internal array if allowed + $old_perms = $this->user_data['auth_user_md5.perms']; + $auth_plugin = $this->user_data['auth_user_md5.auth_plugin']; + foreach ($newuser as $key => $value) { + if (!StudipAuthAbstract::CheckField($key, $auth_plugin)) { + $this->user_data[$key] = $value; + } else if ($this->user_data[$key] !== $value) { + $this->msg .= 'error§' . sprintf(_('Das Feld <em>%s</em> können Sie nicht ändern!'), $key) . '§'; + return false; + } + } + + if (!$this->storeToDatabase()) { + $this->msg .= 'info§' . _('Es wurden keine Veränderungen der Grunddaten vorgenommen.') . '§'; + return true; + } + + $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" verändert.'), $this->user_data['auth_user_md5.username']) . '§'; + if ($auth_plugin !== null) { + // Automated entering new users, based on their status (perms) + $result = AutoInsert::instance()->saveUser($this->user_data['auth_user_md5.user_id'], $newuser['auth_user_md5.perms']); + foreach ($result['added'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Eintragen in die Veranstaltung <em>%s</em> wurde durchgeführt.'), $item) . '§'; + } + foreach ($result['removed'] as $item) { + $this->msg .= 'msg§' . sprintf(_('Das automatische Austragen aus der Veranstaltung <em>%s</em> wurde durchgeführt.'), $item) . '§'; + } + // include language-specific subject and mailbody + $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); + $Zeit = strftime('%x, %X'); + + // TODO: This should be refactored so that the included file returns an array + include "locale/{$user_language}/LC_MAILS/change_mail.inc.php"; // Defines $subject and $mailbody + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); + } + // Upgrade to admin or root? + if (in_array($newuser['auth_user_md5.perms'], ['admin', 'root'])) { + $this->re_sort_position_in_seminar_user(); + + // delete all seminar entries + $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('AdmissionApplication::addMembers', $seminar_ids); + } + // delete all entries from waiting lists + $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('AdmissionApplication::addMembers', $seminar_ids); + } + // delete 'Studiengaenge' + 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) . '§'; + } + } + + if ($newuser['auth_user_md5.perms'] === 'admin') { + + $this->logInstUserDel($this->user_data['auth_user_md5.user_id'], "inst_perms != 'admin'"); + $query = "DELETE FROM user_inst WHERE user_id = ? AND inst_perms != 'admin'"; + $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 MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + } + if ($newuser['auth_user_md5.perms'] === 'root') { + $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); + + $query = "DELETE FROM user_inst 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 MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + } + + return true; + } + + private function logInstUserDel($user_id, $condition = null) + { + $query = "SELECT Institut_id FROM user_inst WHERE user_id = ?"; + if (isset($condition)) { + $query .= ' AND ' . $condition; + } + + $statement = DBManager::get()->prepare($query); + $statement->execute([$user_id]); + while ($institute_id = $statement->fetchColumn()) { + StudipLog::log('INST_USER_DEL', $institute_id, $user_id); + } + } + + /** + * Mail a password generation link to the user + * + * @return bool Password change successful? + */ + public function setPassword() + { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu verändern.') . '§'; + return false; + } + + if (!$perm->have_perm('root')) { + if ($this->user_data['auth_user_md5.perms'] === "root") { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung <em>Root-Accounts</em> zu verändern.') . '§'; + return false; + } + if ($perm->is_fak_admin() && $this->user_data['auth_user_md5.perms'] === 'admin') { + if (!$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu verändern.') . '§'; + return false; + } + } + } + + // Can we reach the email? + if (!$this->checkMail($this->user_data['auth_user_md5.Email'])) { + return false; + } + + self::sendPasswordMail($this->user); + + return true; + } + + /** + * Send a mail to the user denoted by the passed user-object with a link + * to reset the password. For admin, root and non-standard-auth a notification + * is sent instead. + * + * @param User $user + * + * @return void + */ + public static function sendPasswordMail($user, $new = false) + { + setTempLanguage($user->user_id); + + // always generate a token, so root, admin and all other users profit from the abuse protection + if ($new) { + $expiration_in_hours = 24; + $spoken_expiration = _('24 Stunden'); + } else { + $expiration_in_hours = 7 * 24; + $spoken_expiration = _('eine Woche'); + } + $token = Token::create($expiration_in_hours * 60 * 60, $user->id, true); + + // new users alawys receive a link to generate a password + if ($new) { + $subject = sprintf( + _("[Stud.IP - %s] Es wurde ein Zugang für sie erstellt - Setzen sie ein Passwort"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" + ."Es wurde für sie ein Zugang zum System erstellt, Ihr Nutzername lautet:\n\n" + ."%2\$s\n\n" + ."Um den Zugang nutzen zu können, müssen sie ein Passwort setzen.\n" + ."Öffnen Sie dafür bitte folgenden Link\n\n" + ."%3\$s\n\n" + ."in Ihrem Browser.\n\n" + ."Der Link ist %4\$s (bis %5\$s) gültig.\n\n" + ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" + ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" + ."\"Location\" oder \"URL\" kopieren.\n\n" + ), + Config::get()->UNI_NAME_CLEAN, + $user->username, + $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', + $spoken_expiration, + strftime('%x %X', $token->expiration) + ); + } else + + // only users with auth-type standard cann reset their password + if ($user->auth_plugin !== 'standard') { + + // inform user, that their password cannot be reset via mail + $subject = sprintf( + _("[Stud.IP - %s] Passwortänderung angefordert"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Informationsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" + . "Sie haben einen Link angefordert\n" + . "um das Passwort zurückzusetzen.\n" + . "Dies ist aber für den mit dieser Mail \n" + . "verknüpften Account so nicht möglich.\n\n" + . "Wenden sie sich bitte stattdessen an\n%s" + ), + Config::get()->UNI_NAME_CLEAN, + $GLOBALS['UNI_CONTACT'] + ); + + } else { + + $subject = sprintf( + _("[Stud.IP - %s] Neues Passwort setzen"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Bestätigungsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %1\$s -\n\n" + ."Sie haben um die Zurücksetzung des Passwortes zu Ihrem Benutzernamen %5\$s gebeten.\n\n" + ."Diese E-Mail wurde Ihnen zugesandt um sicherzustellen,\n" + ."dass die angegebene E-Mail-Adresse tatsächlich Ihnen gehört.\n\n" + ."Wenn Sie um die Zurücksetzung Ihres Passwortes gebeten haben,\n" + ."dann öffnen Sie bitte folgenden Link\n\n" + ."%2\$s\n\n" + ."in Ihrem Browser. Auf der Seite können Sie ein neues Passwort setzen.\n\n" + ."Der Link ist %3\$s (bis %4\$s) gültig.\n\n" + ."Wahrscheinlich unterstützt Ihr E-Mail-Programm ein einfaches Anklicken des Links.\n" + ."Ansonsten müssen Sie Ihren Browser öffnen und den Link komplett in die Zeile\n" + ."\"Location\" oder \"URL\" kopieren.\n\n" + ."Falls Sie nicht diese Mail nicht angefordert haben\n" + ."oder überhaupt nicht wissen, wovon hier die Rede ist,\n" + ."dann hat jemand Ihre E-Mail-Adresse fälschlicherweise verwendet!\n" + ."Ignorieren Sie in diesem Fall diese E-Mail. Es werden dann keine\n" + ."Änderungen an Ihren Zugangsdaten vorgenommen.\n\n" + ), + Config::get()->UNI_NAME_CLEAN, + $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/new_password/set/'. $token->token .'?cancel_login=1', + $spoken_expiration, + strftime('%x %X', $token->expiration), + $user->username + ); + } + + StudipMail::sendMessage($user->email, $subject, $mailbody); + + restoreLanguage(); + } + + /** + * Delete an existing user from the database and tidy up + * + * @param $delete_documents bool delete all documents in course context belonging to the user + * @param $delete_content_from_course bool delete all course content belonging to the user + * @param $delete_personal_documents bool delete all personal documents belonging to the user + * @param $delete_personal_content bool delete all personal content belonging to the user + * @param $delete_names bool delete all names identifying the user + * @param $delete_memberships bool delete all memberships of the user + * @param bool $send_email_notification bool send an email that the account has been deleted + * @return bool Removal successful? + */ + public function deleteUser( + bool $delete_documents = true, + bool $delete_content_from_course = true, + bool $delete_personal_documents = true, + bool $delete_personal_content = true, + bool $delete_names = true, + bool $delete_memberships = true, + bool $send_email_notification = true, + bool $delete_courseware = true + ): bool { + global $perm; + + // Do we have permission to do so? + if (!$perm->have_perm('admin')) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung Accounts zu löschen.') . '§'; + return FALSE; + } + + if (!$perm->have_perm('root')) { + if ($this->user_data['auth_user_md5.perms'] === 'root') { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung <em>Root-Accounts</em> zu löschen.') . '§'; + return false; + } + if ($this->user_data['auth_user_md5.perms'] === 'admin' && !$this->adminOK()) { + $this->msg .= 'error§' . _('Sie haben keine Berechtigung diesen Admin-Account zu löschen.') . '§'; + return false; + } + } + + // 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) . '§'; + return false; + //founder of studygroup? + } elseif (Config::get()->STUDYGROUPS_ENABLE) { + $status = studygroup_sem_types(); + + if (empty($status)) { + $group_ids = []; + } else { + $query = "SELECT Seminar_id + FROM seminare AS s + LEFT JOIN seminar_user AS su USING (Seminar_id) + WHERE su.status = 'dozent' AND su.user_id = ? AND s.status IN (?)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $this->user_data['auth_user_md5.user_id'], + $status, + ]); + $group_ids = $statement->fetchAll(PDO::FETCH_COLUMN); + } + + foreach ($group_ids as $group_id) { + $sem = Seminar::GetInstance($group_id); + if (StudygroupModel::countMembers($group_id) > 1) { + // check whether there are tutors or even autors that can be promoted + $tutors = $sem->getMembers('tutor'); + $autors = $sem->getMembers('autor'); + if (count($tutors) > 0) { + $new_founder = current($tutors); + StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + continue; + } + // if not promote an autor + elseif (count($autors) > 0) { + $new_founder = current($autors); + StudygroupModel::promote_user($new_founder['username'], $sem->getId(), 'dozent'); + continue; + } + // since no suitable successor was found, we are allowed to remove the studygroup + } else { + $sem->delete(); + } + unset($sem); + } + } + + // store user preferred language for sending mail + $user_language = getUserLanguagePath($this->user_data['auth_user_md5.user_id']); + + // Load privacy plugins to ensure all event handlers can react to the + // UserDataDidRemove event + PluginEngine::getPlugins(PrivacyPlugin::class); + + // delete user from instituts + $this->logInstUserDel($this->user_data['auth_user_md5.user_id']); + + if ($delete_memberships) { + $query = "DELETE FROM user_inst 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 MitarbeiterInnenlisten gelöscht.'), $count) . '§'; + } + + // delete user from Statusgruppen + if ($count = StatusgruppeUser::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']])) { + $this->msg .= 'info§' . sprintf(_('%s Einträge aus Funktionen / Gruppen gelöscht.'), $count) . '§'; + } + + // delete user from archiv + $query = "DELETE FROM archiv_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 den Zugriffsberechtigungen für das Archiv gelöscht.'), $count) . '§'; + } + + // 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()) { + $this->msg .= 'info§' . sprintf(_('%s Zuordnungen zu Studiengängen gelöscht.'), $count) . '§'; + } + + $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']]); + if ($count = $statement->rowCount()) { + $this->msg .= 'info§' . sprintf(_('%u Terminzuordnungen gelöscht.'), $count) . '§'; + } + + // delete visibility settings + Visibility::removeUserPrivacySettings($this->user_data['auth_user_md5.user_id']); + + // delete deputy entries if necessary + $query = "DELETE FROM deputies WHERE ? IN (user_id, range_id)"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + $deputyEntries = $statement->rowCount(); + if ($deputyEntries) { + $this->msg .= 'info§' . sprintf(_('%s Einträge in den Vertretungseinstellungen gelöscht.'), $deputyEntries) . '§'; + } + + // delete all remaining user data + $queries = [ + "DELETE FROM user_userdomains WHERE user_id = ?", + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'memberships'); + } + + // delete documents of this user + if ($delete_documents) { + $db_filecount = FileRef::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + if ($db_filecount > 0) { + $this->msg .= 'info§' . sprintf(_('%s Dateien aus Veranstaltungen und Einrichtungen gelöscht.'), $db_filecount) . '§'; + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_documents'); + } + + // always delete personal courseware elements of this user + \Courseware\Unit::deleteBySQL('range_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\UserDataField::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\UserProgress::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Bookmark::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Task::deleteBySQL( + '`solver_id` = ? AND `solver_type`= "autor"', + [$this->user_data['auth_user_md5.user_id']] + ); + // delete courseware elements in courses of this user + if ($delete_courseware) { + \Courseware\Unit::deleteBySQL('creator_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElement::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElementFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\StructuralElementComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Container::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\Block::deleteBySQL('owner_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\BlockFeedback::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + \Courseware\BlockComment::deleteBySQL('user_id = ?', [$this->user_data['auth_user_md5.user_id']]); + } + + // delete all remaining user data in course context if option selected + if ($delete_content_from_course) { + $queries = [ + "DELETE FROM questionnaires WHERE user_id = ?", + "DELETE FROM questionnaire_answers WHERE user_id = ?", + "DELETE FROM questionnaire_assignments WHERE user_id = ?", + "DELETE FROM questionnaire_anonymous_answers WHERE user_id = ?", + "DELETE FROM etask_assignment_attempts WHERE user_id = ?", + "DELETE FROM etask_responses WHERE user_id = ?", + "DELETE FROM etask_tasks WHERE user_id = ?", + "DELETE FROM etask_tests WHERE user_id = ?", + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$this->user_data['auth_user_md5.user_id']]); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'course_contents'); + } + + if ($delete_personal_documents) { + $user_folder = Folder::findTopFolder($this->user->id); + if ($user_folder) { + $this->msg .= 'info§' . _('Persönlicher Dateibereich gelöscht.') . '§'; + $user_folder->delete(); + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_documents'); + } + + if ($delete_personal_content) { + $this->msg .= $this->deletePersonalData($this->user_data['auth_user_md5.user_id']); + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'personal_contents'); + } + + if ($delete_names) { + $query = "UPDATE auth_user_md5 + SET username = ?, Vorname = '', Nachname = '', Email = '' + WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + md5($this->user_data['auth_user_md5.username'].uniqid('delete_user')), + $this->user_data['auth_user_md5.user_id'] + ]); + if ($statement->rowCount() > 0) { + $this->msg .= 'info§' . _('Benutzername anonymisiert.') . '§'; + } + NotificationCenter::postNotification('UserDataDidRemove', $this->user_data['auth_user_md5.user_id'], 'names'); + } + + if ($delete_personal_documents && $delete_personal_content && $delete_names && $delete_memberships) { + // Delete the user from resource property entries of type "user": + ResourceProperty::deleteBySQL( + "`property_id` IN ( + SELECT `property_id` + FROM `resource_property_definitions` + WHERE `type` = 'user' + ) + AND `state` = :user_id", + ['user_id' => $this->user_data['auth_user_md5.user_id']] + ); + + // delete Stud.IP account + $query = "DELETE FROM user_info WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + + $query = "DELETE FROM auth_user_md5 WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + if (!$statement->rowCount()) { + $this->msg .= 'error§<em>' . _('Fehler:') . '</em> ' . $query . '§'; + return false; + } else { + $this->msg .= 'msg§' . sprintf(_('Benutzer "%s" gelöscht.'), $this->user_data['auth_user_md5.username']) . '§'; + } + StudipLog::log('USER_DEL', $this->user_data['auth_user_md5.user_id'], null, sprintf('%s %s (%s)', $this->user_data['auth_user_md5.Vorname'], $this->user_data['auth_user_md5.Nachname'], $this->user_data['auth_user_md5.username'])); //log with Vorname Nachname (username) as info string + + // Can we reach the email? + if ( + $send_email_notification + && $this->checkMail($this->user_data['auth_user_md5.Email']) + ) { + // include language-specific subject and mailbody + $Zeit = strftime('%x, %X'); + + // TODO: This should be refactored so that the included file returns an array + include "locale/$user_language/LC_MAILS/delete_mail.inc.php"; // Defines $subject and $mailbody + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject ?? '', $mailbody ?? ''); + } + + // Remove plugin associations/activations + PluginManager::getInstance()->deactivateAllPluginsForRange( + 'user', + $this->user_data['auth_user_md5.user_id'] + ); + + // Trigger delete on sorm object which will fire notifications + // + // TODO: Remove everything from this method that would also be + // deleted in User::delete() (TODO!!!) + $this->user->delete(); + + unset($this->user_data); + } + + return true; + } + + /** + * Delete personal userdata + * + * @param string $user_id the user which should be retrieved + * @return string Removal messages + */ + private function deletePersonalData($user_id) + { + $msg = ''; + + // delete the datafields + $localEntries = DataFieldEntry::removeAll($user_id); + + // delete user from waiting lists + $query = "SELECT seminar_id FROM admission_seminar_user WHERE user_id = ?"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$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([$user_id]); + if ($count = $statement->rowCount()) { + $msg .= 'info§' . sprintf(_('%s Einträge aus Wartelisten gelöscht.'), $count) . '§'; + array_map('AdmissionApplication::addMembers', $seminar_ids); + } + + // delete all personal news from this user + if ($count = StudipNews::DeleteNewsByAuthor($user_id)) { + $msg .= 'info§' . sprintf(_('%s Einträge aus den Ankündigungen gelöscht.'), $count) . '§'; + } + if ($count = StudipNews::DeleteNewsRanges($user_id)) { + $msg .= 'info§' . sprintf(_('%s Verweise auf Ankündigungen gelöscht.'), $count) . '§'; + } + + //delete entry in news_rss_range + StudipNews::UnsetRssId($user_id); + + // delete all private appointments of this user + if (Config::get()->CALENDAR_ENABLE) { + // delete private appointments (omit group appointments) + $count = CalendarDate::deleteBySQL( + '`id` IN ( + SELECT `id` + FROM ( + SELECT `id`, COUNT(*) + FROM `calendar_dates` + JOIN `calendar_date_assignments` + ON `calendar_dates`.`id` = `calendar_date_assignments`.`calendar_date_id` + WHERE `calendar_dates`.`author_id` = :user_id + GROUP BY `id` + HAVING COUNT(*) = 1 + ORDER BY NULL + ) AS `cal_date_delete` + )', + [':user_id' => $user_id] + ); + // delete assignments to group appointments + $count += CalendarDateAssignment::deleteBySQL('`range_id` = ?', [$user_id]); + if ($count) { + $msg .= 'info§' . sprintf(_('%s Einträge aus den Terminen gelöscht.'), $count) . '§'; + } + } + + // delete all messages send or received by this user + $messaging = new messaging(); + $messaging->delete_all_messages($user_id); + + // delete user from all foreign adressbooks and empty own adressbook + $count = Contact::deleteBySQL('user_id = ?', [$user_id]); + if ($count > 0) { + $msg .= 'info§' . sprintf(_('%s Einträge aus Adressbüchern gelöscht.'), $count) . '§'; + } + $count = Contact::deleteBySQL('owner_id = ?', [$user_id]); + if ($count) { + $msg .= 'info§' . sprintf(_('Adressbuch mit %d Einträgen gelöscht.'), $count) . '§'; + } + + // delete users groups + Statusgruppen::deleteBySQL('range_id = ?', [$user_id]); + + // remove user from any groups + StatusgruppeUser::deleteBySQL('user_id = ?', [$user_id]); + + // delete user config values + ConfigValue::deleteBySQL('range_id = ?', [$user_id]); + + // delete all remaining user data + $queries = [ + "DELETE FROM kategorien WHERE range_id = ?", + "DELETE FROM user_visibility WHERE user_id = ?", + "DELETE FROM user_online WHERE user_id = ?", + "DELETE FROM auto_insert_user WHERE user_id = ?", + "DELETE FROM roles_user WHERE userid = ?", + "DELETE FROM schedule WHERE user_id = ?", + "DELETE FROM schedule_seminare WHERE user_id = ?", + "DELETE FROM termin_related_persons WHERE user_id = ?", + "DELETE FROM priorities WHERE user_id = ?", + "DELETE FROM api_oauth_user_mapping WHERE user_id = ?", + "DELETE FROM api_user_permissions WHERE user_id = ?", + "DELETE FROM help_tour_user WHERE user_id = ?", + "DELETE FROM personal_notifications_user WHERE user_id = ?", + "DELETE FROM forum_abo_users WHERE user_id = ?", + "DELETE FROM forum_favorites WHERE user_id = ?", + + "DELETE FROM comments WHERE user_id = ?", + "DELETE questionnaires FROM questionnaires LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE questionnaire_answers FROM questionnaire_answers LEFT JOIN questionnaire_questions USING (`question_id`) LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE questionnaire_anonymous_answers FROM questionnaire_anonymous_answers LEFT JOIN questionnaire_assignments qa USING (`questionnaire_id`) WHERE qa.range_id = ?", + "DELETE FROM questionnaire_assignments WHERE user_id = ?", + "DELETE etask_assignment_attempts FROM etask_assignment_attempts LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_responses FROM etask_responses LEFT JOIN etask_assignments ea ON (`assignment_id` = ea.id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_tasks FROM etask_tasks LEFT JOIN etask_test_tasks tt ON (etask_tasks.id = tt.task_id) LEFT JOIN etask_assignments ea ON (tt.`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", + "DELETE etask_tests FROM etask_tests LEFT JOIN etask_assignments ea ON (`test_id` = ea.test_id) WHERE ea.range_type = 'user' AND user_id = ?", + + "UPDATE forum_entries SET author = '' WHERE user_id = ?", + "UPDATE auth_user_md5 SET visible = 'never' WHERE user_id = ?", + + "REPLACE INTO `user_info` (`user_id`, `hobby`, `lebenslauf`, `publi`, `schwerp`, `Home`, `privatnr`, `privatcell`, `privadr`, `score`, `geschlecht`, `mkdate`, `chdate`, `title_front`, `title_rear`, `preferred_language`, `smsforward_copy`, `smsforward_rec`, `email_forward`, `motto`, `lock_rule`) VALUES(?, '', '', '', '', '', '', '', '', 0, 0, 0, 0, '', '', NULL, 1, '', 0, '', '');" + ]; + foreach ($queries as $query) { + DBManager::get()->execute($query, [$user_id]); + } + + // Clean up orphaned items + $queries = [ + "DELETE FROM personal_notifications WHERE personal_notification_id NOT IN ( + SELECT personal_notification_id FROM personal_notifications_user + )", + ]; + foreach ($queries as $query) { + DBManager::get()->exec($query); + } + + object_kill_visits($user_id); + object_kill_views($user_id); + + // delete picture + $avatar = Avatar::getAvatar($user_id); + if ($avatar->is_customized()) { + $avatar->reset(); + $msg .= 'info§' . _('Bild gelöscht.') . '§'; + } + + //delete connected users + if (Config::get()->ELEARNING_INTERFACE_ENABLE) { + if (ELearningUtils::initElearningInterfaces()) { + foreach ($GLOBALS['connected_cms'] as $cms){ + if ($cms->auth_necessary && $cms->user instanceOf ConnectedUser) { + $user_auto_create = $cms->USER_AUTO_CREATE; + $cms->USER_AUTO_CREATE = false; + $userclass = mb_strtolower(get_class($cms->user)); + $connected_user = new $userclass($cms->cms_type, $user_id); + if ($connected_user->deleteUser() && $connected_user->is_connected) { + $msg .= 'info§' . sprintf(_('Der verknüpfte Nutzer %s wurde im System %s gelöscht.'), $connected_user->login, $connected_user->cms_type) . '§'; + } + $cms->USER_AUTO_CREATE = $user_auto_create; + } + } + } + } + + return $msg; + } + + private function adminOK() + { + static $ok = null; + + if ($ok === null) { + $query = "SELECT COUNT(a.Institut_id) = COUNT(c.inst_perms) + FROM user_inst AS a + LEFT JOIN Institute b ON (a.Institut_id = b.Institut_id AND b.Institut_id != b.fakultaets_id) + LEFT JOIN user_inst AS c ON (b.fakultaets_id = c.Institut_id AND c.user_id = ? + AND c.inst_perms = 'admin') + WHERE a.user_id = ? AND a.inst_perms = 'admin'"; + $statement = DBManager::get()->prepare($query); + $statement->execute([ + $GLOBALS['auth']->auth['uid'], + $this->user_data['auth_user_md5.user_id'], + ]); + $ok = $statement->fetchColumn(); + } + + return $ok; + } + + private function re_sort_position_in_seminar_user() + { + $query = "SELECT Seminar_id, position, status + FROM seminar_user + WHERE user_id = ? AND status IN ('tutor', 'dozent')"; + $statement = DBManager::get()->prepare($query); + $statement->execute([$this->user_data['auth_user_md5.user_id']]); + while ($row = $statement->fetch(PDO::FETCH_ASSOC)) { + if ($row['status'] === 'tutor') { + CourseMember::resortMembership($row['Seminar_id'], (int)$row['position']); + } else if ($row['status'] === 'dozent') { + CourseMember::resortMembership($row['Seminar_id'], (int)$row['position'], 'dozent'); + } + } + } + + /** + * Change an existing user password + * + * @param string $password + * @return bool change successful? + */ + public function changePassword($password) + { + $this->user_data['auth_user_md5.password'] = self::getPwdHasher()->HashPassword($password); + $this->storeToDatabase(); + + $this->msg .= 'msg§' . _('Das Passwort wurde neu gesetzt.') . '§'; + + // include language-specific subject and mailbody + setTempLanguage($this->user_data['auth_user_md5.user_id']); + + $subject = sprintf( + _("[Stud.IP - %s] Passwortänderung"), + Config::get()->UNI_NAME_CLEAN + ); + + $mailbody = sprintf( + _("Dies ist eine Informationsmail des Stud.IP-Systems\n" + ."(Studienbegleitender Internetsupport von Präsenzlehre)\n- %s -\n\n" + ."Ihr Passwort wurde soeben von Ihnen oder einem/einer Administrator/in geändert.\n" + ), + Config::get()->UNI_NAME_CLEAN + ); + + // send mail + StudipMail::sendMessage($this->user_data['auth_user_md5.Email'], $subject, $mailbody); + + restoreLanguage(); + + StudipLog::log('USER_NEWPWD', $this->user_data['auth_user_md5.user_id']); + + return true; + } +} |
