]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
Merge pull request #5626 from annando/notices-oh-yeah
[friendica.git] / src / Model / User.php
1 <?php
2 /**
3  * @file src/Model/User.php
4  * @brief This file includes the User class with user related database functions
5  */
6 namespace Friendica\Model;
7
8 use DivineOmega\PasswordExposed\PasswordStatus;
9 use Exception;
10 use Friendica\Core\Addon;
11 use Friendica\Core\Config;
12 use Friendica\Core\L10n;
13 use Friendica\Core\PConfig;
14 use Friendica\Core\Protocol;
15 use Friendica\Core\System;
16 use Friendica\Core\Worker;
17 use Friendica\Database\DBA;
18 use Friendica\Object\Image;
19 use Friendica\Util\Crypto;
20 use Friendica\Util\DateTimeFormat;
21 use Friendica\Util\Network;
22 use LightOpenID;
23 use function password_exposed;
24
25 require_once 'boot.php';
26 require_once 'include/dba.php';
27 require_once 'include/enotify.php';
28 require_once 'include/text.php';
29 /**
30  * @brief This class handles User related functions
31  */
32 class User
33 {
34         /**
35          * @brief Get owner data by user id
36          *
37          * @param int $uid
38          * @return boolean|array
39          */
40         public static function getOwnerDataById($uid) {
41                 $r = DBA::fetchFirst("SELECT
42                         `contact`.*,
43                         `user`.`prvkey` AS `uprvkey`,
44                         `user`.`timezone`,
45                         `user`.`nickname`,
46                         `user`.`sprvkey`,
47                         `user`.`spubkey`,
48                         `user`.`page-flags`,
49                         `user`.`account-type`,
50                         `user`.`prvnets`
51                         FROM `contact`
52                         INNER JOIN `user`
53                                 ON `user`.`uid` = `contact`.`uid`
54                         WHERE `contact`.`uid` = ?
55                         AND `contact`.`self`
56                         LIMIT 1",
57                         $uid
58                 );
59                 if (!DBA::isResult($r)) {
60                         return false;
61                 }
62                 return $r;
63         }
64
65         /**
66          * @brief Get owner data by nick name
67          *
68          * @param int $nick
69          * @return boolean|array
70          */
71         public static function getOwnerDataByNick($nick)
72         {
73                 $user = DBA::selectFirst('user', ['uid'], ['nickname' => $nick]);
74                 if (!DBA::isResult($user)) {
75                         return false;
76                 }
77                 return self::getOwnerDataById($user['uid']);
78         }
79
80         /**
81          * @brief Returns the default group for a given user and network
82          *
83          * @param int $uid User id
84          * @param string $network network name
85          *
86          * @return int group id
87          */
88         public static function getDefaultGroup($uid, $network = '')
89         {
90                 $default_group = 0;
91
92                 if ($network == Protocol::OSTATUS) {
93                         $default_group = PConfig::get($uid, "ostatus", "default_group");
94                 }
95
96                 if ($default_group != 0) {
97                         return $default_group;
98                 }
99
100                 $user = DBA::selectFirst('user', ['def_gid'], ['uid' => $uid]);
101
102                 if (DBA::isResult($user)) {
103                         $default_group = $user["def_gid"];
104                 }
105
106                 return $default_group;
107         }
108
109
110         /**
111          * Authenticate a user with a clear text password
112          *
113          * @brief Authenticate a user with a clear text password
114          * @param mixed $user_info
115          * @param string $password
116          * @return int|boolean
117          * @deprecated since version 3.6
118          * @see User::getIdFromPasswordAuthentication()
119          */
120         public static function authenticate($user_info, $password)
121         {
122                 try {
123                         return self::getIdFromPasswordAuthentication($user_info, $password);
124                 } catch (Exception $ex) {
125                         return false;
126                 }
127         }
128
129         /**
130          * Returns the user id associated with a successful password authentication
131          *
132          * @brief Authenticate a user with a clear text password
133          * @param mixed $user_info
134          * @param string $password
135          * @return int User Id if authentication is successful
136          * @throws Exception
137          */
138         public static function getIdFromPasswordAuthentication($user_info, $password)
139         {
140                 $user = self::getAuthenticationInfo($user_info);
141
142                 if (strpos($user['password'], '$') === false) {
143                         //Legacy hash that has not been replaced by a new hash yet
144                         if (self::hashPasswordLegacy($password) === $user['password']) {
145                                 self::updatePassword($user['uid'], $password);
146
147                                 return $user['uid'];
148                         }
149                 } elseif (!empty($user['legacy_password'])) {
150                         //Legacy hash that has been double-hashed and not replaced by a new hash yet
151                         //Warning: `legacy_password` is not necessary in sync with the content of `password`
152                         if (password_verify(self::hashPasswordLegacy($password), $user['password'])) {
153                                 self::updatePassword($user['uid'], $password);
154
155                                 return $user['uid'];
156                         }
157                 } elseif (password_verify($password, $user['password'])) {
158                         //New password hash
159                         if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
160                                 self::updatePassword($user['uid'], $password);
161                         }
162
163                         return $user['uid'];
164                 }
165
166                 throw new Exception(L10n::t('Login failed'));
167         }
168
169         /**
170          * Returns authentication info from various parameters types
171          *
172          * User info can be any of the following:
173          * - User DB object
174          * - User Id
175          * - User email or username or nickname
176          * - User array with at least the uid and the hashed password
177          *
178          * @param mixed $user_info
179          * @return array
180          * @throws Exception
181          */
182         private static function getAuthenticationInfo($user_info)
183         {
184                 $user = null;
185
186                 if (is_object($user_info) || is_array($user_info)) {
187                         if (is_object($user_info)) {
188                                 $user = (array) $user_info;
189                         } else {
190                                 $user = $user_info;
191                         }
192
193                         if (!isset($user['uid'])
194                                 || !isset($user['password'])
195                                 || !isset($user['legacy_password'])
196                         ) {
197                                 throw new Exception(L10n::t('Not enough information to authenticate'));
198                         }
199                 } elseif (is_int($user_info) || is_string($user_info)) {
200                         if (is_int($user_info)) {
201                                 $user = DBA::selectFirst('user', ['uid', 'password', 'legacy_password'],
202                                         [
203                                                 'uid' => $user_info,
204                                                 'blocked' => 0,
205                                                 'account_expired' => 0,
206                                                 'account_removed' => 0,
207                                                 'verified' => 1
208                                         ]
209                                 );
210                         } else {
211                                 $fields = ['uid', 'password', 'legacy_password'];
212                                 $condition = ["(`email` = ? OR `username` = ? OR `nickname` = ?)
213                                         AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
214                                         $user_info, $user_info, $user_info];
215                                 $user = DBA::selectFirst('user', $fields, $condition);
216                         }
217
218                         if (!DBA::isResult($user)) {
219                                 throw new Exception(L10n::t('User not found'));
220                         }
221                 }
222
223                 return $user;
224         }
225
226         /**
227          * Generates a human-readable random password
228          *
229          * @return string
230          */
231         public static function generateNewPassword()
232         {
233                 return autoname(6) . mt_rand(100, 9999);
234         }
235
236         /**
237          * Checks if the provided plaintext password has been exposed or not
238          *
239          * @param string $password
240          * @return bool
241          */
242         public static function isPasswordExposed($password)
243         {
244                 return password_exposed($password) === PasswordStatus::EXPOSED;
245         }
246
247         /**
248          * Legacy hashing function, kept for password migration purposes
249          *
250          * @param string $password
251          * @return string
252          */
253         private static function hashPasswordLegacy($password)
254         {
255                 return hash('whirlpool', $password);
256         }
257
258         /**
259          * Global user password hashing function
260          *
261          * @param string $password
262          * @return string
263          */
264         public static function hashPassword($password)
265         {
266                 if (!trim($password)) {
267                         throw new Exception(L10n::t('Password can\'t be empty'));
268                 }
269
270                 return password_hash($password, PASSWORD_DEFAULT);
271         }
272
273         /**
274          * Updates a user row with a new plaintext password
275          *
276          * @param int    $uid
277          * @param string $password
278          * @return bool
279          */
280         public static function updatePassword($uid, $password)
281         {
282                 return self::updatePasswordHashed($uid, self::hashPassword($password));
283         }
284
285         /**
286          * Updates a user row with a new hashed password.
287          * Empties the password reset token field just in case.
288          *
289          * @param int    $uid
290          * @param string $pasword_hashed
291          * @return bool
292          */
293         private static function updatePasswordHashed($uid, $pasword_hashed)
294         {
295                 $fields = [
296                         'password' => $pasword_hashed,
297                         'pwdreset' => null,
298                         'pwdreset_time' => null,
299                         'legacy_password' => false
300                 ];
301                 return DBA::update('user', $fields, ['uid' => $uid]);
302         }
303
304         /**
305          * @brief Checks if a nickname is in the list of the forbidden nicknames
306          *
307          * Check if a nickname is forbidden from registration on the node by the
308          * admin. Forbidden nicknames (e.g. role namess) can be configured in the
309          * admin panel.
310          *
311          * @param string $nickname The nickname that should be checked
312          * @return boolean True is the nickname is blocked on the node
313          */
314         public static function isNicknameBlocked($nickname)
315         {
316                 $forbidden_nicknames = Config::get('system', 'forbidden_nicknames', '');
317
318                 // if the config variable is empty return false
319                 if (empty($forbidden_nicknames)) {
320                         return false;
321                 }
322
323                 // check if the nickname is in the list of blocked nicknames
324                 $forbidden = explode(',', $forbidden_nicknames);
325                 $forbidden = array_map('trim', $forbidden);
326                 if (in_array(strtolower($nickname), $forbidden)) {
327                         return true;
328                 }
329
330                 // else return false
331                 return false;
332         }
333
334         /**
335          * @brief Catch-all user creation function
336          *
337          * Creates a user from the provided data array, either form fields or OpenID.
338          * Required: { username, nickname, email } or { openid_url }
339          *
340          * Performs the following:
341          * - Sends to the OpenId auth URL (if relevant)
342          * - Creates new key pairs for crypto
343          * - Create self-contact
344          * - Create profile image
345          *
346          * @param array $data
347          * @return string
348          * @throw Exception
349          */
350         public static function create(array $data)
351         {
352                 $a = get_app();
353                 $return = ['user' => null, 'password' => ''];
354
355                 $using_invites = Config::get('system', 'invitation_only');
356                 $num_invites   = Config::get('system', 'number_invites');
357
358                 $invite_id  = !empty($data['invite_id'])  ? notags(trim($data['invite_id']))  : '';
359                 $username   = !empty($data['username'])   ? notags(trim($data['username']))   : '';
360                 $nickname   = !empty($data['nickname'])   ? notags(trim($data['nickname']))   : '';
361                 $email      = !empty($data['email'])      ? notags(trim($data['email']))      : '';
362                 $openid_url = !empty($data['openid_url']) ? notags(trim($data['openid_url'])) : '';
363                 $photo      = !empty($data['photo'])      ? notags(trim($data['photo']))      : '';
364                 $password   = !empty($data['password'])   ? trim($data['password'])           : '';
365                 $password1  = !empty($data['password1'])  ? trim($data['password1'])          : '';
366                 $confirm    = !empty($data['confirm'])    ? trim($data['confirm'])            : '';
367                 $blocked    = !empty($data['blocked'])    ? intval($data['blocked'])          : 0;
368                 $verified   = !empty($data['verified'])   ? intval($data['verified'])         : 0;
369                 $language   = !empty($data['language'])   ? notags(trim($data['language']))   : 'en';
370
371                 $publish = !empty($data['profile_publish_reg']) && intval($data['profile_publish_reg']) ? 1 : 0;
372                 $netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0;
373
374                 if ($password1 != $confirm) {
375                         throw new Exception(L10n::t('Passwords do not match. Password unchanged.'));
376                 } elseif ($password1 != '') {
377                         $password = $password1;
378                 }
379
380                 if ($using_invites) {
381                         if (!$invite_id) {
382                                 throw new Exception(L10n::t('An invitation is required.'));
383                         }
384
385                         if (!DBA::exists('register', ['hash' => $invite_id])) {
386                                 throw new Exception(L10n::t('Invitation could not be verified.'));
387                         }
388                 }
389
390                 if (empty($username) || empty($email) || empty($nickname)) {
391                         if ($openid_url) {
392                                 if (!Network::isUrlValid($openid_url)) {
393                                         throw new Exception(L10n::t('Invalid OpenID url'));
394                                 }
395                                 $_SESSION['register'] = 1;
396                                 $_SESSION['openid'] = $openid_url;
397
398                                 $openid = new LightOpenID($a->get_hostname());
399                                 $openid->identity = $openid_url;
400                                 $openid->returnUrl = System::baseUrl() . '/openid';
401                                 $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
402                                 $openid->optional = ['namePerson/first', 'media/image/aspect11', 'media/image/default'];
403                                 try {
404                                         $authurl = $openid->authUrl();
405                                 } catch (Exception $e) {
406                                         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);
407                                 }
408                                 goaway($authurl);
409                                 // NOTREACHED
410                         }
411
412                         throw new Exception(L10n::t('Please enter the required information.'));
413                 }
414
415                 if (!Network::isUrlValid($openid_url)) {
416                         $openid_url = '';
417                 }
418
419                 $err = '';
420
421                 // collapse multiple spaces in name
422                 $username = preg_replace('/ +/', ' ', $username);
423
424                 if (mb_strlen($username) > 48) {
425                         throw new Exception(L10n::t('Please use a shorter name.'));
426                 }
427                 if (mb_strlen($username) < 3) {
428                         throw new Exception(L10n::t('Name too short.'));
429                 }
430
431                 // So now we are just looking for a space in the full name.
432                 $loose_reg = Config::get('system', 'no_regfullname');
433                 if (!$loose_reg) {
434                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
435                         if (!strpos($username, ' ')) {
436                                 throw new Exception(L10n::t("That doesn't appear to be your full \x28First Last\x29 name."));
437                         }
438                 }
439
440                 if (!Network::isEmailDomainAllowed($email)) {
441                         throw new Exception(L10n::t('Your email domain is not among those allowed on this site.'));
442                 }
443
444                 if (!valid_email($email) || !Network::isEmailDomainValid($email)) {
445                         throw new Exception(L10n::t('Not a valid email address.'));
446                 }
447                 if (self::isNicknameBlocked($nickname)) {
448                         throw new Exception(L10n::t('The nickname was blocked from registration by the nodes admin.'));
449                 }
450
451                 if (Config::get('system', 'block_extended_register', false) && DBA::exists('user', ['email' => $email])) {
452                         throw new Exception(L10n::t('Cannot use that email.'));
453                 }
454
455                 // Disallow somebody creating an account using openid that uses the admin email address,
456                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
457                 if (Config::get('config', 'admin_email') && strlen($openid_url)) {
458                         $adminlist = explode(',', str_replace(' ', '', strtolower(Config::get('config', 'admin_email'))));
459                         if (in_array(strtolower($email), $adminlist)) {
460                                 throw new Exception(L10n::t('Cannot use that email.'));
461                         }
462                 }
463
464                 $nickname = $data['nickname'] = strtolower($nickname);
465
466                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
467                         throw new Exception(L10n::t('Your nickname can only contain a-z, 0-9 and _.'));
468                 }
469
470                 // Check existing and deleted accounts for this nickname.
471                 if (DBA::exists('user', ['nickname' => $nickname])
472                         || DBA::exists('userd', ['username' => $nickname])
473                 ) {
474                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
475                 }
476
477                 $new_password = strlen($password) ? $password : User::generateNewPassword();
478                 $new_password_encoded = self::hashPassword($new_password);
479
480                 $return['password'] = $new_password;
481
482                 $keys = Crypto::newKeypair(4096);
483                 if ($keys === false) {
484                         throw new Exception(L10n::t('SERIOUS ERROR: Generation of security keys failed.'));
485                 }
486
487                 $prvkey = $keys['prvkey'];
488                 $pubkey = $keys['pubkey'];
489
490                 // Create another keypair for signing/verifying salmon protocol messages.
491                 $sres = Crypto::newKeypair(512);
492                 $sprvkey = $sres['prvkey'];
493                 $spubkey = $sres['pubkey'];
494
495                 $insert_result = DBA::insert('user', [
496                         'guid'     => generate_user_guid(),
497                         'username' => $username,
498                         'password' => $new_password_encoded,
499                         'email'    => $email,
500                         'openid'   => $openid_url,
501                         'nickname' => $nickname,
502                         'pubkey'   => $pubkey,
503                         'prvkey'   => $prvkey,
504                         'spubkey'  => $spubkey,
505                         'sprvkey'  => $sprvkey,
506                         'verified' => $verified,
507                         'blocked'  => $blocked,
508                         'language' => $language,
509                         'timezone' => 'UTC',
510                         'register_date' => DateTimeFormat::utcNow(),
511                         'default-location' => ''
512                 ]);
513
514                 if ($insert_result) {
515                         $uid = DBA::lastInsertId();
516                         $user = DBA::selectFirst('user', [], ['uid' => $uid]);
517                 } else {
518                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
519                 }
520
521                 if (!$uid) {
522                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
523                 }
524
525                 // if somebody clicked submit twice very quickly, they could end up with two accounts
526                 // due to race condition. Remove this one.
527                 $user_count = DBA::count('user', ['nickname' => $nickname]);
528                 if ($user_count > 1) {
529                         DBA::delete('user', ['uid' => $uid]);
530
531                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
532                 }
533
534                 $insert_result = DBA::insert('profile', [
535                         'uid' => $uid,
536                         'name' => $username,
537                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
538                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
539                         'publish' => $publish,
540                         'is-default' => 1,
541                         'net-publish' => $netpublish,
542                         'profile-name' => L10n::t('default')
543                 ]);
544                 if (!$insert_result) {
545                         DBA::delete('user', ['uid' => $uid]);
546
547                         throw new Exception(L10n::t('An error occurred creating your default profile. Please try again.'));
548                 }
549
550                 // Create the self contact
551                 if (!Contact::createSelfFromUserId($uid)) {
552                         DBA::delete('user', ['uid' => $uid]);
553
554                         throw new Exception(L10n::t('An error occurred creating your self contact. Please try again.'));
555                 }
556
557                 // Create a group with no members. This allows somebody to use it
558                 // right away as a default group for new contacts.
559                 $def_gid = Group::create($uid, L10n::t('Friends'));
560                 if (!$def_gid) {
561                         DBA::delete('user', ['uid' => $uid]);
562
563                         throw new Exception(L10n::t('An error occurred creating your default contact group. Please try again.'));
564                 }
565
566                 $fields = ['def_gid' => $def_gid];
567                 if (Config::get('system', 'newuser_private') && $def_gid) {
568                         $fields['allow_gid'] = '<' . $def_gid . '>';
569                 }
570
571                 DBA::update('user', $fields, ['uid' => $uid]);
572
573                 // if we have no OpenID photo try to look up an avatar
574                 if (!strlen($photo)) {
575                         $photo = Network::lookupAvatarByEmail($email);
576                 }
577
578                 // unless there is no avatar-addon loaded
579                 if (strlen($photo)) {
580                         $photo_failure = false;
581
582                         $filename = basename($photo);
583                         $img_str = Network::fetchUrl($photo, true);
584                         // guess mimetype from headers or filename
585                         $type = Image::guessType($photo, true);
586
587                         $Image = new Image($img_str, $type);
588                         if ($Image->isValid()) {
589                                 $Image->scaleToSquare(175);
590
591                                 $hash = Photo::newResource();
592
593                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 4);
594
595                                 if ($r === false) {
596                                         $photo_failure = true;
597                                 }
598
599                                 $Image->scaleDown(80);
600
601                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 5);
602
603                                 if ($r === false) {
604                                         $photo_failure = true;
605                                 }
606
607                                 $Image->scaleDown(48);
608
609                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 6);
610
611                                 if ($r === false) {
612                                         $photo_failure = true;
613                                 }
614
615                                 if (!$photo_failure) {
616                                         DBA::update('photo', ['profile' => 1], ['resource-id' => $hash]);
617                                 }
618                         }
619                 }
620
621                 Addon::callHooks('register_account', $uid);
622
623                 $return['user'] = $user;
624                 return $return;
625         }
626
627         /**
628          * @brief Sends pending registration confiƕmation email
629          *
630          * @param string $email
631          * @param string $sitename
632          * @param string $username
633          * @return NULL|boolean from notification() and email() inherited
634          */
635         public static function sendRegisterPendingEmail($email, $sitename, $username)
636         {
637                 $body = deindent(L10n::t('
638                         Dear %1$s,
639                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
640                 '));
641
642                 $body = sprintf($body, $username, $sitename);
643
644                 return notification([
645                         'type' => SYSTEM_EMAIL,
646                         'to_email' => $email,
647                         'subject'=> L10n::t('Registration at %s', $sitename),
648                         'body' => $body]);
649         }
650
651         /**
652          * @brief Sends registration confirmation
653          *
654          * It's here as a function because the mail is sent from different parts
655          *
656          * @param string $email
657          * @param string $sitename
658          * @param string $siteurl
659          * @param string $username
660          * @param string $password
661          * @return NULL|boolean from notification() and email() inherited
662          */
663         public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password, $user)
664         {
665                 $preamble = deindent(L10n::t('
666                         Dear %1$s,
667                                 Thank you for registering at %2$s. Your account has been created.
668                 '));
669                 $body = deindent(L10n::t('
670                         The login details are as follows:
671
672                         Site Location:  %3$s
673                         Login Name:             %1$s
674                         Password:               %5$s
675
676                         You may change your password from your account "Settings" page after logging
677                         in.
678
679                         Please take a few moments to review the other account settings on that page.
680
681                         You may also wish to add some basic information to your default profile
682                         ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
683
684                         We recommend setting your full name, adding a profile photo,
685                         adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and
686                         perhaps what country you live in; if you do not wish to be more specific
687                         than that.
688
689                         We fully respect your right to privacy, and none of these items are necessary.
690                         If you are new and do not know anybody here, they may help
691                         you to make some new and interesting friends.
692
693                         If you ever want to delete your account, you can do so at %3$s/removeme
694
695                         Thank you and welcome to %2$s.'));
696
697                 $preamble = sprintf($preamble, $username, $sitename);
698                 $body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
699
700                 return notification([
701                         'uid' => $user['uid'],
702                         'language' => $user['language'],
703                         'type' => SYSTEM_EMAIL,
704                         'to_email' => $email,
705                         'subject'=> L10n::t('Registration details for %s', $sitename),
706                         'preamble'=> $preamble,
707                         'body' => $body]);
708         }
709
710         /**
711          * @param object $uid user to remove
712          * @return void
713          */
714         public static function remove($uid)
715         {
716                 if (!$uid) {
717                         return;
718                 }
719
720                 logger('Removing user: ' . $uid);
721
722                 $user = DBA::selectFirst('user', [], ['uid' => $uid]);
723
724                 Addon::callHooks('remove_user', $user);
725
726                 // save username (actually the nickname as it is guaranteed
727                 // unique), so it cannot be re-registered in the future.
728                 DBA::insert('userd', ['username' => $user['nickname']]);
729
730                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
731                 DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utcNow()], ['uid' => $uid]);
732                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
733
734                 // Send an update to the directory
735                 $self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
736                 Worker::add(PRIORITY_LOW, "Directory", $self['url']);
737
738                 if ($uid == local_user()) {
739                         unset($_SESSION['authenticated']);
740                         unset($_SESSION['uid']);
741                         goaway(System::baseUrl());
742                 }
743         }
744 }