]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
added forgotten trim
[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 Friendica\Core\Addon;
10 use Friendica\Core\Config;
11 use Friendica\Core\L10n;
12 use Friendica\Core\PConfig;
13 use Friendica\Core\System;
14 use Friendica\Core\Worker;
15 use Friendica\Database\DBM;
16 use Friendica\Model\Contact;
17 use Friendica\Model\Group;
18 use Friendica\Model\Photo;
19 use Friendica\Object\Image;
20 use Friendica\Util\Crypto;
21 use Friendica\Util\DateTimeFormat;
22 use Friendica\Util\Network;
23 use dba;
24 use Exception;
25 use LightOpenID;
26 use function password_exposed;
27
28 require_once 'boot.php';
29 require_once 'include/dba.php';
30 require_once 'include/enotify.php';
31 require_once 'include/text.php';
32 /**
33  * @brief This class handles User related functions
34  */
35 class User
36 {
37         /**
38          * @brief Get owner data by user id
39          *
40          * @param int $uid
41          * @return boolean|array
42          */
43         public static function getOwnerDataById($uid) {
44                 $r = dba::fetch_first("SELECT
45                         `contact`.*,
46                         `user`.`prvkey` AS `uprvkey`,
47                         `user`.`timezone`,
48                         `user`.`nickname`,
49                         `user`.`sprvkey`,
50                         `user`.`spubkey`,
51                         `user`.`page-flags`,
52                         `user`.`account-type`,
53                         `user`.`prvnets`
54                         FROM `contact`
55                         INNER JOIN `user`
56                                 ON `user`.`uid` = `contact`.`uid`
57                         WHERE `contact`.`uid` = ?
58                         AND `contact`.`self`
59                         LIMIT 1",
60                         $uid
61                 );
62                 if (!DBM::is_result($r)) {
63                         return false;
64                 }
65                 return $r;
66         }
67
68         /**
69          * @brief Get owner data by nick name
70          *
71          * @param int $nick
72          * @return boolean|array
73          */
74         public static function getOwnerDataByNick($nick)
75         {
76                 $user = dba::selectFirst('user', ['uid'], ['nickname' => $nick]);
77                 if (!DBM::is_result($user)) {
78                         return false;
79                 }
80                 return self::getOwnerDataById($user['uid']);
81         }
82
83         /**
84          * @brief Returns the default group for a given user and network
85          *
86          * @param int $uid User id
87          * @param string $network network name
88          *
89          * @return int group id
90          */
91         public static function getDefaultGroup($uid, $network = '')
92         {
93                 $default_group = 0;
94
95                 if ($network == NETWORK_OSTATUS) {
96                         $default_group = PConfig::get($uid, "ostatus", "default_group");
97                 }
98
99                 if ($default_group != 0) {
100                         return $default_group;
101                 }
102
103                 $user = dba::selectFirst('user', ['def_gid'], ['uid' => $uid]);
104
105                 if (DBM::is_result($user)) {
106                         $default_group = $user["def_gid"];
107                 }
108
109                 return $default_group;
110         }
111
112
113         /**
114          * Authenticate a user with a clear text password
115          *
116          * @brief Authenticate a user with a clear text password
117          * @param mixed $user_info
118          * @param string $password
119          * @return int|boolean
120          * @deprecated since version 3.6
121          * @see User::getIdFromPasswordAuthentication()
122          */
123         public static function authenticate($user_info, $password)
124         {
125                 try {
126                         return self::getIdFromPasswordAuthentication($user_info, $password);
127                 } catch (Exception $ex) {
128                         return false;
129                 }
130         }
131
132         /**
133          * Returns the user id associated with a successful password authentication
134          *
135          * @brief Authenticate a user with a clear text password
136          * @param mixed $user_info
137          * @param string $password
138          * @return int User Id if authentication is successful
139          * @throws Exception
140          */
141         public static function getIdFromPasswordAuthentication($user_info, $password)
142         {
143                 $user = self::getAuthenticationInfo($user_info);
144
145                 if (strpos($user['password'], '$') === false) {
146                         //Legacy hash that has not been replaced by a new hash yet
147                         if (self::hashPasswordLegacy($password) === $user['password']) {
148                                 self::updatePassword($user['uid'], $password);
149
150                                 return $user['uid'];
151                         }
152                 } elseif (!empty($user['legacy_password'])) {
153                         //Legacy hash that has been double-hashed and not replaced by a new hash yet
154                         //Warning: `legacy_password` is not necessary in sync with the content of `password`
155                         if (password_verify(self::hashPasswordLegacy($password), $user['password'])) {
156                                 self::updatePassword($user['uid'], $password);
157
158                                 return $user['uid'];
159                         }
160                 } elseif (password_verify($password, $user['password'])) {
161                         //New password hash
162                         if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
163                                 self::updatePassword($user['uid'], $password);
164                         }
165
166                         return $user['uid'];
167                 }
168
169                 throw new Exception(L10n::t('Login failed'));
170         }
171
172         /**
173          * Returns authentication info from various parameters types
174          *
175          * User info can be any of the following:
176          * - User DB object
177          * - User Id
178          * - User email or username or nickname
179          * - User array with at least the uid and the hashed password
180          *
181          * @param mixed $user_info
182          * @return array
183          * @throws Exception
184          */
185         private static function getAuthenticationInfo($user_info)
186         {
187                 $user = null;
188
189                 if (is_object($user_info) || is_array($user_info)) {
190                         if (is_object($user_info)) {
191                                 $user = (array) $user_info;
192                         } else {
193                                 $user = $user_info;
194                         }
195
196                         if (!isset($user['uid'])
197                                 || !isset($user['password'])
198                                 || !isset($user['legacy_password'])
199                         ) {
200                                 throw new Exception(L10n::t('Not enough information to authenticate'));
201                         }
202                 } elseif (is_int($user_info) || is_string($user_info)) {
203                         if (is_int($user_info)) {
204                                 $user = dba::selectFirst('user', ['uid', 'password', 'legacy_password'],
205                                         [
206                                                 'uid' => $user_info,
207                                                 'blocked' => 0,
208                                                 'account_expired' => 0,
209                                                 'account_removed' => 0,
210                                                 'verified' => 1
211                                         ]
212                                 );
213                         } else {
214                                 $fields = ['uid', 'password', 'legacy_password'];
215                                 $condition = ["(`email` = ? OR `username` = ? OR `nickname` = ?)
216                                         AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
217                                         $user_info, $user_info, $user_info];
218                                 $user = dba::selectFirst('user', $fields, $condition);
219                         }
220
221                         if (!DBM::is_result($user)) {
222                                 throw new Exception(L10n::t('User not found'));
223                         }
224                 }
225
226                 return $user;
227         }
228
229         /**
230          * Generates a human-readable random password
231          *
232          * @return string
233          */
234         public static function generateNewPassword()
235         {
236                 return autoname(6) . mt_rand(100, 9999);
237         }
238
239         /**
240          * Checks if the provided plaintext password has been exposed or not
241          *
242          * @param string $password
243          * @return bool
244          */
245         public static function isPasswordExposed($password)
246         {
247                 return password_exposed($password) === PasswordStatus::EXPOSED;
248         }
249
250         /**
251          * Legacy hashing function, kept for password migration purposes
252          *
253          * @param string $password
254          * @return string
255          */
256         private static function hashPasswordLegacy($password)
257         {
258                 return hash('whirlpool', $password);
259         }
260
261         /**
262          * Global user password hashing function
263          *
264          * @param string $password
265          * @return string
266          */
267         public static function hashPassword($password)
268         {
269                 if (!trim($password)) {
270                         throw new Exception(L10n::t('Password can\'t be empty'));
271                 }
272
273                 return password_hash($password, PASSWORD_DEFAULT);
274         }
275
276         /**
277          * Updates a user row with a new plaintext password
278          *
279          * @param int    $uid
280          * @param string $password
281          * @return bool
282          */
283         public static function updatePassword($uid, $password)
284         {
285                 return self::updatePasswordHashed($uid, self::hashPassword($password));
286         }
287
288         /**
289          * Updates a user row with a new hashed password.
290          * Empties the password reset token field just in case.
291          *
292          * @param int    $uid
293          * @param string $pasword_hashed
294          * @return bool
295          */
296         private static function updatePasswordHashed($uid, $pasword_hashed)
297         {
298                 $fields = [
299                         'password' => $pasword_hashed,
300                         'pwdreset' => null,
301                         'pwdreset_time' => null,
302                         'legacy_password' => false
303                 ];
304                 return dba::update('user', $fields, ['uid' => $uid]);
305         }
306
307         /**
308          * @brief Checks if a nickname is in the list of the forbidden nicknames
309          *
310          * Check if a nickname is forbidden from registration on the node by the
311          * admin. Forbidden nicknames (e.g. role namess) can be configured in the
312          * admin panel.
313          *
314          * @param string $nickname The nickname that should be checked
315          * @return boolean True is the nickname is blocked on the node
316          */
317         public static function isNicknameBlocked($nickname)
318         {
319                 $forbidden_nicknames = Config::get('system', 'forbidden_nicknames', '');
320                 // if the config variable is empty return false
321                 if (!x($forbidden_nicknames)) {
322                         return false;
323                 }
324                 // check if the nickname is in the list of blocked nicknames
325                 $forbidden = explode(',', $forbidden_nicknames);
326                 $forbidden = array_map('trim', $forbidden);
327                 if (in_array(strtolower($nickname), $forbidden)) {
328                         return true;
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  = x($data, 'invite_id')  ? notags(trim($data['invite_id']))  : '';
359                 $username   = x($data, 'username')   ? notags(trim($data['username']))   : '';
360                 $nickname   = x($data, 'nickname')   ? notags(trim($data['nickname']))   : '';
361                 $email      = x($data, 'email')      ? notags(trim($data['email']))      : '';
362                 $openid_url = x($data, 'openid_url') ? notags(trim($data['openid_url'])) : '';
363                 $photo      = x($data, 'photo')      ? notags(trim($data['photo']))      : '';
364                 $password   = x($data, 'password')   ? trim($data['password'])           : '';
365                 $password1  = x($data, 'password1')  ? trim($data['password1'])          : '';
366                 $confirm    = x($data, 'confirm')    ? trim($data['confirm'])            : '';
367                 $blocked    = x($data, 'blocked')    ? intval($data['blocked'])          : 0;
368                 $verified   = x($data, 'verified')   ? intval($data['verified'])         : 0;
369                 $language   = x($data, 'language')   ? notags(trim($data['language'])) : 'en';
370
371                 $publish = x($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 (!x($username) || !x($email) || !x($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 (x($a->config, 'admin_email') && strlen($openid_url)) {
458                         $adminlist = explode(',', str_replace(' ', '', strtolower($a->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)
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                         'type' => SYSTEM_EMAIL,
702                         'to_email' => $email,
703                         'subject'=> L10n::t('Registration details for %s', $sitename),
704                         'preamble'=> $preamble,
705                         'body' => $body]);
706         }
707
708         /**
709          * @param object $uid user to remove
710          * @return void
711          */
712         public static function remove($uid)
713         {
714                 if (!$uid) {
715                         return;
716                 }
717
718                 logger('Removing user: ' . $uid);
719
720                 $user = dba::selectFirst('user', [], ['uid' => $uid]);
721
722                 Addon::callHooks('remove_user', $user);
723
724                 // save username (actually the nickname as it is guaranteed
725                 // unique), so it cannot be re-registered in the future.
726                 dba::insert('userd', ['username' => $user['nickname']]);
727
728                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
729                 dba::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utcNow()], ['uid' => $uid]);
730                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
731
732                 // Send an update to the directory
733                 Worker::add(PRIORITY_LOW, "Directory", $user['url']);
734
735                 if ($uid == local_user()) {
736                         unset($_SESSION['authenticated']);
737                         unset($_SESSION['uid']);
738                         goaway(System::baseUrl());
739                 }
740         }
741 }