X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FUser.php;h=39c544c3a8786f62053093ec1c4ff734d5eb4c4f;hb=9ea6d4b26d4375502c567276705dff2ff678122e;hp=72e3aea939dde21bee049afdc5106def4b2f0cc5;hpb=2685c9bb1c2680999134e9683bb663c36fe39e78;p=friendica.git diff --git a/src/Model/User.php b/src/Model/User.php index 72e3aea939..39c544c3a8 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -1,28 +1,33 @@ $uid]); + + if (DBM::is_result($user)) { + $default_group = $user["def_gid"]; + } + + return $default_group; + } + + + /** + * Authenticate a user with a clear text password + * * @brief Authenticate a user with a clear text password + * @param mixed $user_info + * @param string $password + * @return int|boolean + * @deprecated since version 3.6 + * @see User::getIdFromPasswordAuthentication() + */ + public static function authenticate($user_info, $password) + { + try { + return self::getIdFromPasswordAuthentication($user_info, $password); + } catch (Exception $ex) { + return false; + } + } + + /** + * Returns the user id associated with a successful password authentication + * + * @brief Authenticate a user with a clear text password + * @param mixed $user_info + * @param string $password + * @return int User Id if authentication is successful + * @throws Exception + */ + public static function getIdFromPasswordAuthentication($user_info, $password) + { + $user = self::getAuthenticationInfo($user_info); + + if (strpos($user['password'], '$') === false) { + //Legacy hash that has not been replaced by a new hash yet + if (self::hashPasswordLegacy($password) === $user['password']) { + self::updatePassword($user['uid'], $password); + + return $user['uid']; + } + } elseif (!empty($user['legacy_password'])) { + //Legacy hash that has been double-hashed and not replaced by a new hash yet + //Warning: `legacy_password` is not necessary in sync with the content of `password` + if (password_verify(self::hashPasswordLegacy($password), $user['password'])) { + self::updatePassword($user['uid'], $password); + + return $user['uid']; + } + } elseif (password_verify($password, $user['password'])) { + //New password hash + if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) { + self::updatePassword($user['uid'], $password); + } + + return $user['uid']; + } + + throw new Exception(L10n::t('Login failed')); + } + + /** + * Returns authentication info from various parameters types * * User info can be any of the following: * - User DB object @@ -39,53 +164,136 @@ class User * - User array with at least the uid and the hashed password * * @param mixed $user_info - * @param string $password - * @return boolean + * @return array + * @throws Exception */ - public static function authenticate($user_info, $password) + private static function getAuthenticationInfo($user_info) { - if (is_object($user_info)) { - $user = (array) $user_info; - } elseif (is_int($user_info)) { - $user = dba::select('user', - ['uid', 'password'], - [ - 'uid' => $user_info, - 'blocked' => 0, - 'account_expired' => 0, - 'account_removed' => 0, - 'verified' => 1 - ], - ['limit' => 1] - ); - } elseif (is_string($user_info)) { - $user = dba::fetch_first('SELECT `uid`, `password` - FROM `user` - WHERE (`email` = ? OR `username` = ? OR `nickname` = ?) - AND `blocked` = 0 - AND `account_expired` = 0 - AND `account_removed` = 0 - AND `verified` = 1 - LIMIT 1', - $user_info, - $user_info, - $user_info - ); - } else { - $user = $user_info; - } + $user = null; - if (!DBM::is_result($user) || !isset($user['uid']) || !isset($user['password'])) { - return false; + if (is_object($user_info) || is_array($user_info)) { + if (is_object($user_info)) { + $user = (array) $user_info; + } else { + $user = $user_info; + } + + if (!isset($user['uid']) + || !isset($user['password']) + || !isset($user['legacy_password']) + ) { + throw new Exception(L10n::t('Not enough information to authenticate')); + } + } elseif (is_int($user_info) || is_string($user_info)) { + if (is_int($user_info)) { + $user = dba::selectFirst('user', ['uid', 'password', 'legacy_password'], + [ + 'uid' => $user_info, + 'blocked' => 0, + 'account_expired' => 0, + 'account_removed' => 0, + 'verified' => 1 + ] + ); + } else { + $user = dba::fetch_first('SELECT `uid`, `password`, `legacy_password` + FROM `user` + WHERE (`email` = ? OR `username` = ? OR `nickname` = ?) + AND `blocked` = 0 + AND `account_expired` = 0 + AND `account_removed` = 0 + AND `verified` = 1 + LIMIT 1', + $user_info, + $user_info, + $user_info + ); + } + + if (!DBM::is_result($user)) { + throw new Exception(L10n::t('User not found')); + } } - $password_hashed = hash('whirlpool', $password); + return $user; + } - if ($password_hashed !== $user['password']) { - return false; + /** + * Generates a human-readable random password + * + * @return string + */ + public static function generateNewPassword() + { + return autoname(6) . mt_rand(100, 9999); + } + + /** + * Checks if the provided plaintext password has been exposed or not + * + * @param string $password + * @return bool + */ + public static function isPasswordExposed($password) + { + return password_exposed($password) === PasswordStatus::EXPOSED; + } + + /** + * Legacy hashing function, kept for password migration purposes + * + * @param string $password + * @return string + */ + private static function hashPasswordLegacy($password) + { + return hash('whirlpool', $password); + } + + /** + * Global user password hashing function + * + * @param string $password + * @return string + */ + public static function hashPassword($password) + { + if (!trim($password)) { + throw new Exception(L10n::t('Password can\'t be empty')); } - return $user['uid']; + return password_hash($password, PASSWORD_DEFAULT); + } + + /** + * Updates a user row with a new plaintext password + * + * @param int $uid + * @param string $password + * @return bool + */ + public static function updatePassword($uid, $password) + { + return self::updatePasswordHashed($uid, self::hashPassword($password)); + } + + /** + * Updates a user row with a new hashed password. + * Empties the password reset token field just in case. + * + * @param int $uid + * @param string $pasword_hashed + * @return bool + */ + private static function updatePasswordHashed($uid, $pasword_hashed) + { + $fields = [ + 'password' => $pasword_hashed, + 'pwdreset' => null, + 'pwdreset_time' => null, + 'legacy_password' => false + ]; + return dba::update('user', $fields, ['uid' => $uid]); } /** @@ -102,11 +310,12 @@ class User * * @param array $data * @return string + * @throw Exception */ public static function create(array $data) { $a = get_app(); - $result = array('success' => false, 'user' => null, 'password' => '', 'message' => ''); + $return = ['user' => null, 'password' => '']; $using_invites = Config::get('system', 'invitation_only'); $num_invites = Config::get('system', 'number_invites'); @@ -122,60 +331,53 @@ class User $confirm = x($data, 'confirm') ? trim($data['confirm']) : ''; $blocked = x($data, 'blocked') ? intval($data['blocked']) : 0; $verified = x($data, 'verified') ? intval($data['verified']) : 0; + $language = x($data, 'language') ? notags(trim($data['language'])) : 'en'; $publish = x($data, 'profile_publish_reg') && intval($data['profile_publish_reg']) ? 1 : 0; $netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0; if ($password1 != $confirm) { - $result['message'] .= t('Passwords do not match. Password unchanged.') . EOL; - return $result; - } elseif ($password1 != "") { + throw new Exception(L10n::t('Passwords do not match. Password unchanged.')); + } elseif ($password1 != '') { $password = $password1; } - $tmp_str = $openid_url; - if ($using_invites) { if (!$invite_id) { - $result['message'] .= t('An invitation is required.') . EOL; - return $result; + throw new Exception(L10n::t('An invitation is required.')); } - $r = q("SELECT * FROM `register` WHERE `hash` = '%s' LIMIT 1", dbesc($invite_id)); - if (!results($r)) { - $result['message'] .= t('Invitation could not be verified.') . EOL; - return $result; + + if (!dba::exists('register', ['hash' => $invite_id])) { + throw new Exception(L10n::t('Invitation could not be verified.')); } } if (!x($username) || !x($email) || !x($nickname)) { if ($openid_url) { - if (!validate_url($tmp_str)) { - $result['message'] .= t('Invalid OpenID url') . EOL; - return $result; + if (!Network::isUrlValid($openid_url)) { + throw new Exception(L10n::t('Invalid OpenID url')); } $_SESSION['register'] = 1; $_SESSION['openid'] = $openid_url; - $openid = new LightOpenID; + $openid = new LightOpenID($a->get_hostname()); $openid->identity = $openid_url; $openid->returnUrl = System::baseUrl() . '/openid'; - $openid->required = array('namePerson/friendly', 'contact/email', 'namePerson'); - $openid->optional = array('namePerson/first', 'media/image/aspect11', 'media/image/default'); + $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson']; + $openid->optional = ['namePerson/first', 'media/image/aspect11', 'media/image/default']; try { $authurl = $openid->authUrl(); } catch (Exception $e) { - $result['message'] .= t("We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.") . EOL . EOL . t("The error message was:") . $e->getMessage() . EOL; - return $result; + throw new Exception(L10n::t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . L10n::t('The error message was:') . $e->getMessage(), 0, $e); } goaway($authurl); // NOTREACHED } - notice(t('Please enter the required information.') . EOL); - return; + throw new Exception(L10n::t('Please enter the required information.')); } - if (!validate_url($tmp_str)) { + if (!Network::isUrlValid($openid_url)) { $openid_url = ''; } @@ -185,246 +387,203 @@ class User $username = preg_replace('/ +/', ' ', $username); if (mb_strlen($username) > 48) { - $result['message'] .= t('Please use a shorter name.') . EOL; + throw new Exception(L10n::t('Please use a shorter name.')); } if (mb_strlen($username) < 3) { - $result['message'] .= t('Name too short.') . EOL; + throw new Exception(L10n::t('Name too short.')); } // So now we are just looking for a space in the full name. - $loose_reg = Config::get('system', 'no_regfullname'); if (!$loose_reg) { $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8'); if (!strpos($username, ' ')) { - $result['message'] .= t("That doesn't appear to be your full \x28First Last\x29 name.") . EOL; + throw new Exception(L10n::t("That doesn't appear to be your full \x28First Last\x29 name.")); } } + if (!Network::isEmailDomainAllowed($email)) { + throw new Exception(L10n::t('Your email domain is not among those allowed on this site.')); + } - if (!allowed_email($email)) { - $result['message'] .= t('Your email domain is not among those allowed on this site.') . EOL; + if (!valid_email($email) || !Network::isEmailDomainValid($email)) { + throw new Exception(L10n::t('Not a valid email address.')); } - if (!valid_email($email) || !validate_email($email)) { - $result['message'] .= t('Not a valid email address.') . EOL; + if (Config::get('system', 'block_extended_register', false) && dba::exists('user', ['email' => $email])) { + throw new Exception(L10n::t('Cannot use that email.')); } // Disallow somebody creating an account using openid that uses the admin email address, // since openid bypasses email verification. We'll allow it if there is not yet an admin account. - - $adminlist = explode(",", str_replace(" ", "", strtolower($a->config['admin_email']))); - - //if((x($a->config,'admin_email')) && (strcasecmp($email,$a->config['admin_email']) == 0) && strlen($openid_url)) { - if (x($a->config, 'admin_email') && in_array(strtolower($email), $adminlist) && strlen($openid_url)) { - $r = q("SELECT * FROM `user` WHERE `email` = '%s' LIMIT 1", - dbesc($email) - ); - if (DBM::is_result($r)) { - $result['message'] .= t('Cannot use that email.') . EOL; + if (x($a->config, 'admin_email') && strlen($openid_url)) { + $adminlist = explode(',', str_replace(' ', '', strtolower($a->config['admin_email']))); + if (in_array(strtolower($email), $adminlist)) { + throw new Exception(L10n::t('Cannot use that email.')); } } $nickname = $data['nickname'] = strtolower($nickname); - if (!preg_match("/^[a-z0-9][a-z0-9\_]*$/", $nickname)) { - $result['message'] .= t('Your "nickname" can only contain "a-z", "0-9" and "_".') . EOL; - } - - $r = q("SELECT `uid` FROM `user` - WHERE `nickname` = '%s' LIMIT 1", - dbesc($nickname) - ); - if (DBM::is_result($r)) { - $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; + if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) { + throw new Exception(L10n::t('Your nickname can only contain a-z, 0-9 and _.')); } - // Check deleted accounts that had this nickname. Doesn't matter to us, - // but could be a security issue for federated platforms. - - $r = q("SELECT * FROM `userd` - WHERE `username` = '%s' LIMIT 1", - dbesc($nickname) - ); - if (DBM::is_result($r)) { - $result['message'] .= t('Nickname was once registered here and may not be re-used. Please choose another.') . EOL; + // Check existing and deleted accounts for this nickname. + if (dba::exists('user', ['nickname' => $nickname]) + || dba::exists('userd', ['username' => $nickname]) + ) { + throw new Exception(L10n::t('Nickname is already registered. Please choose another.')); } - if (strlen($result['message'])) { - return $result; - } + $new_password = strlen($password) ? $password : User::generateNewPassword(); + $new_password_encoded = self::hashPassword($new_password); - $new_password = strlen($password) ? $password : autoname(6) . mt_rand(100, 9999); - $new_password_encoded = hash('whirlpool', $new_password); - - $result['password'] = $new_password; - - $keys = new_keypair(4096); + $return['password'] = $new_password; + $keys = Crypto::newKeypair(4096); if ($keys === false) { - $result['message'] .= t('SERIOUS ERROR: Generation of security keys failed.') . EOL; - return $result; + throw new Exception(L10n::t('SERIOUS ERROR: Generation of security keys failed.')); } $prvkey = $keys['prvkey']; $pubkey = $keys['pubkey']; // Create another keypair for signing/verifying salmon protocol messages. - $sres = new_keypair(512); + $sres = Crypto::newKeypair(512); $sprvkey = $sres['prvkey']; $spubkey = $sres['pubkey']; - $r = q("INSERT INTO `user` (`guid`, `username`, `password`, `email`, `openid`, `nickname`, - `pubkey`, `prvkey`, `spubkey`, `sprvkey`, `register_date`, `verified`, `blocked`, `timezone`, `default-location`) - VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, 'UTC', '')", - dbesc(generate_user_guid()), - dbesc($username), - dbesc($new_password_encoded), - dbesc($email), - dbesc($openid_url), - dbesc($nickname), - dbesc($pubkey), - dbesc($prvkey), - dbesc($spubkey), - dbesc($sprvkey), - dbesc(datetime_convert()), - intval($verified), - intval($blocked) - ); - - if ($r) { - $r = q("SELECT * FROM `user` - WHERE `username` = '%s' AND `password` = '%s' LIMIT 1", - dbesc($username), - dbesc($new_password_encoded) - ); - if (DBM::is_result($r)) { - $u = $r[0]; - $newuid = intval($r[0]['uid']); - } + $insert_result = dba::insert('user', [ + 'guid' => generate_user_guid(), + 'username' => $username, + 'password' => $new_password_encoded, + 'email' => $email, + 'openid' => $openid_url, + 'nickname' => $nickname, + 'pubkey' => $pubkey, + 'prvkey' => $prvkey, + 'spubkey' => $spubkey, + 'sprvkey' => $sprvkey, + 'verified' => $verified, + 'blocked' => $blocked, + 'language' => $language, + 'timezone' => 'UTC', + 'register_date' => DateTimeFormat::utcNow(), + 'default-location' => '' + ]); + + if ($insert_result) { + $uid = dba::lastInsertId(); + $user = dba::selectFirst('user', [], ['uid' => $uid]); } else { - $result['message'] .= t('An error occurred during registration. Please try again.') . EOL; - return $result; + throw new Exception(L10n::t('An error occurred during registration. Please try again.')); } - /** - * if somebody clicked submit twice very quickly, they could end up with two accounts - * due to race condition. Remove this one. - */ - $r = q("SELECT `uid` FROM `user` - WHERE `nickname` = '%s' ", - dbesc($nickname) - ); - if (DBM::is_result($r) && count($r) > 1 && $newuid) { - $result['message'] .= t('Nickname is already registered. Please choose another.') . EOL; - dba::delete('user', array('uid' => $newuid)); - return $result; - } - - if (x($newuid) !== false) { - $r = q("INSERT INTO `profile` ( `uid`, `profile-name`, `is-default`, `name`, `photo`, `thumb`, `publish`, `net-publish` ) - VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, %d ) ", - intval($newuid), - t('default'), - 1, - dbesc($username), - dbesc(System::baseUrl() . "/photo/profile/{$newuid}.jpg"), - dbesc(System::baseUrl() . "/photo/avatar/{$newuid}.jpg"), - intval($publish), - intval($netpublish) - ); - if ($r === false) { - $result['message'] .= t('An error occurred creating your default profile. Please try again.') . EOL; - // Start fresh next time. - dba::delete('user', array('uid' => $newuid)); - return $result; - } + if (!$uid) { + throw new Exception(L10n::t('An error occurred during registration. Please try again.')); + } - // Create the self contact - Contact::createSelfFromUserId($newuid); + // if somebody clicked submit twice very quickly, they could end up with two accounts + // due to race condition. Remove this one. + $user_count = dba::count('user', ['nickname' => $nickname]); + if ($user_count > 1) { + dba::delete('user', ['uid' => $uid]); - // Create a group with no members. This allows somebody to use it - // right away as a default group for new contacts. + throw new Exception(L10n::t('Nickname is already registered. Please choose another.')); + } - group_add($newuid, t('Friends')); + $insert_result = dba::insert('profile', [ + 'uid' => $uid, + 'name' => $username, + 'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg", + 'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg", + 'publish' => $publish, + 'is-default' => 1, + 'net-publish' => $netpublish, + 'profile-name' => L10n::t('default') + ]); + if (!$insert_result) { + dba::delete('user', ['uid' => $uid]); + + throw new Exception(L10n::t('An error occurred creating your default profile. Please try again.')); + } - $r = q("SELECT `id` FROM `group` WHERE `uid` = %d AND `name` = '%s'", - intval($newuid), - dbesc(t('Friends')) - ); - if (DBM::is_result($r)) { - $def_gid = $r[0]['id']; + // Create the self contact + if (!Contact::createSelfFromUserId($uid)) { + dba::delete('user', ['uid' => $uid]); - q("UPDATE `user` SET `def_gid` = %d WHERE `uid` = %d", - intval($r[0]['id']), - intval($newuid) - ); - } + throw new Exception(L10n::t('An error occurred creating your self contact. Please try again.')); + } - if (Config::get('system', 'newuser_private') && $def_gid) { - q("UPDATE `user` SET `allow_gid` = '%s' WHERE `uid` = %d", - dbesc("<" . $def_gid . ">"), - intval($newuid) - ); - } + // Create a group with no members. This allows somebody to use it + // right away as a default group for new contacts. + $def_gid = Group::create($uid, L10n::t('Friends')); + if (!$def_gid) { + dba::delete('user', ['uid' => $uid]); + + throw new Exception(L10n::t('An error occurred creating your default contact group. Please try again.')); } + $fields = ['def_gid' => $def_gid]; + if (Config::get('system', 'newuser_private') && $def_gid) { + $fields['allow_gid'] = '<' . $def_gid . '>'; + } + + dba::update('user', $fields, ['uid' => $uid]); + // if we have no OpenID photo try to look up an avatar if (!strlen($photo)) { - $photo = avatar_img($email); + $photo = Network::lookupAvatarByEmail($email); } - // unless there is no avatar-plugin loaded + // unless there is no avatar-addon loaded if (strlen($photo)) { $photo_failure = false; $filename = basename($photo); - $img_str = fetch_url($photo, true); + $img_str = Network::fetchUrl($photo, true); // guess mimetype from headers or filename - $type = Photo::guessImageType($photo, true); + $type = Image::guessType($photo, true); + $Image = new Image($img_str, $type); + if ($Image->isValid()) { + $Image->scaleToSquare(175); - $img = new Photo($img_str, $type); - if ($img->isValid()) { - $img->scaleImageSquare(175); + $hash = Photo::newResource(); - $hash = photo_new_resource(); - - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 4); + $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 4); if ($r === false) { $photo_failure = true; } - $img->scaleImage(80); + $Image->scaleDown(80); - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 5); + $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 5); if ($r === false) { $photo_failure = true; } - $img->scaleImage(48); + $Image->scaleDown(48); - $r = $img->store($newuid, 0, $hash, $filename, t('Profile Photos'), 6); + $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 6); if ($r === false) { $photo_failure = true; } if (!$photo_failure) { - q("UPDATE `photo` SET `profile` = 1 WHERE `resource-id` = '%s' ", - dbesc($hash) - ); + dba::update('photo', ['profile' => 1], ['resource-id' => $hash]); } } } - call_hooks('register_account', $newuid); + Addon::callHooks('register_account', $uid); - $result['success'] = true; - $result['user'] = $u; - return $result; + $return['user'] = $user; + return $return; } /** @@ -437,18 +596,18 @@ class User */ public static function sendRegisterPendingEmail($email, $sitename, $username) { - $body = deindent(t(' + $body = deindent(L10n::t(' Dear %1$s, Thank you for registering at %2$s. Your account is pending for approval by the administrator. ')); $body = sprintf($body, $username, $sitename); - return notification(array( + return notification([ 'type' => SYSTEM_EMAIL, 'to_email' => $email, - 'subject'=> sprintf( t('Registration at %s'), $sitename), - 'body' => $body)); + 'subject'=> L10n::t('Registration at %s', $sitename), + 'body' => $body]); } /** @@ -465,15 +624,16 @@ class User */ public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password) { - $preamble = deindent(t(' + $preamble = deindent(L10n::t(' Dear %1$s, Thank you for registering at %2$s. Your account has been created. ')); - $body = deindent(t(' + $body = deindent(L10n::t(' The login details are as follows: - Site Location: %3$s - Login Name: %1$s - Password: %5$s + + Site Location: %3$s + Login Name: %1$s + Password: %5$s You may change your password from your account "Settings" page after logging in. @@ -481,10 +641,10 @@ class User Please take a few moments to review the other account settings on that page. You may also wish to add some basic information to your default profile - (on the "Profiles" page) so that other people can easily find you. + ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you. We recommend setting your full name, adding a profile photo, - adding some profile "keywords" (very useful in making new friends) - and + adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and perhaps what country you live in; if you do not wish to be more specific than that. @@ -492,18 +652,19 @@ class User If you are new and do not know anybody here, they may help you to make some new and interesting friends. + If you ever want to delete your account, you can do so at %3$s/removeme Thank you and welcome to %2$s.')); $preamble = sprintf($preamble, $username, $sitename); $body = sprintf($body, $email, $sitename, $siteurl, $username, $password); - return notification(array( + return notification([ 'type' => SYSTEM_EMAIL, 'to_email' => $email, - 'subject'=> sprintf( t('Registration details for %s'), $sitename), + 'subject'=> L10n::t('Registration details for %s', $sitename), 'preamble'=> $preamble, - 'body' => $body)); + 'body' => $body]); } /** @@ -518,16 +679,16 @@ class User logger('Removing user: ' . $uid); - $user = dba::select('user', [], ['uid' => $uid], ['limit' => 1]); + $user = dba::selectFirst('user', [], ['uid' => $uid]); - call_hooks('remove_user', $user); + Addon::callHooks('remove_user', $user); // save username (actually the nickname as it is guaranteed // unique), so it cannot be re-registered in the future. dba::insert('userd', ['username' => $user['nickname']]); // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php) - dba::update('user', ['account_removed' => true, 'account_expires_on' => datetime_convert()], ['uid' => $uid]); + dba::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utcNow()], ['uid' => $uid]); Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid); // Send an update to the directory