]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
f1fa32f48df450728e5e156955116693614ecd27
[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                 if (in_array(strtolower($nickname), $forbidden)) {
327                         return true;
328                 }
329                 // else return false
330                 return false;
331         }
332
333         /**
334          * @brief Catch-all user creation function
335          *
336          * Creates a user from the provided data array, either form fields or OpenID.
337          * Required: { username, nickname, email } or { openid_url }
338          *
339          * Performs the following:
340          * - Sends to the OpenId auth URL (if relevant)
341          * - Creates new key pairs for crypto
342          * - Create self-contact
343          * - Create profile image
344          *
345          * @param array $data
346          * @return string
347          * @throw Exception
348          */
349         public static function create(array $data)
350         {
351                 $a = get_app();
352                 $return = ['user' => null, 'password' => ''];
353
354                 $using_invites = Config::get('system', 'invitation_only');
355                 $num_invites   = Config::get('system', 'number_invites');
356
357                 $invite_id  = x($data, 'invite_id')  ? notags(trim($data['invite_id']))  : '';
358                 $username   = x($data, 'username')   ? notags(trim($data['username']))   : '';
359                 $nickname   = x($data, 'nickname')   ? notags(trim($data['nickname']))   : '';
360                 $email      = x($data, 'email')      ? notags(trim($data['email']))      : '';
361                 $openid_url = x($data, 'openid_url') ? notags(trim($data['openid_url'])) : '';
362                 $photo      = x($data, 'photo')      ? notags(trim($data['photo']))      : '';
363                 $password   = x($data, 'password')   ? trim($data['password'])           : '';
364                 $password1  = x($data, 'password1')  ? trim($data['password1'])          : '';
365                 $confirm    = x($data, 'confirm')    ? trim($data['confirm'])            : '';
366                 $blocked    = x($data, 'blocked')    ? intval($data['blocked'])          : 0;
367                 $verified   = x($data, 'verified')   ? intval($data['verified'])         : 0;
368                 $language   = x($data, 'language')   ? notags(trim($data['language'])) : 'en';
369
370                 $publish = x($data, 'profile_publish_reg') && intval($data['profile_publish_reg']) ? 1 : 0;
371                 $netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0;
372
373                 if ($password1 != $confirm) {
374                         throw new Exception(L10n::t('Passwords do not match. Password unchanged.'));
375                 } elseif ($password1 != '') {
376                         $password = $password1;
377                 }
378
379                 if ($using_invites) {
380                         if (!$invite_id) {
381                                 throw new Exception(L10n::t('An invitation is required.'));
382                         }
383
384                         if (!dba::exists('register', ['hash' => $invite_id])) {
385                                 throw new Exception(L10n::t('Invitation could not be verified.'));
386                         }
387                 }
388
389                 if (!x($username) || !x($email) || !x($nickname)) {
390                         if ($openid_url) {
391                                 if (!Network::isUrlValid($openid_url)) {
392                                         throw new Exception(L10n::t('Invalid OpenID url'));
393                                 }
394                                 $_SESSION['register'] = 1;
395                                 $_SESSION['openid'] = $openid_url;
396
397                                 $openid = new LightOpenID($a->get_hostname());
398                                 $openid->identity = $openid_url;
399                                 $openid->returnUrl = System::baseUrl() . '/openid';
400                                 $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
401                                 $openid->optional = ['namePerson/first', 'media/image/aspect11', 'media/image/default'];
402                                 try {
403                                         $authurl = $openid->authUrl();
404                                 } catch (Exception $e) {
405                                         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);
406                                 }
407                                 goaway($authurl);
408                                 // NOTREACHED
409                         }
410
411                         throw new Exception(L10n::t('Please enter the required information.'));
412                 }
413
414                 if (!Network::isUrlValid($openid_url)) {
415                         $openid_url = '';
416                 }
417
418                 $err = '';
419
420                 // collapse multiple spaces in name
421                 $username = preg_replace('/ +/', ' ', $username);
422
423                 if (mb_strlen($username) > 48) {
424                         throw new Exception(L10n::t('Please use a shorter name.'));
425                 }
426                 if (mb_strlen($username) < 3) {
427                         throw new Exception(L10n::t('Name too short.'));
428                 }
429
430                 // So now we are just looking for a space in the full name.
431                 $loose_reg = Config::get('system', 'no_regfullname');
432                 if (!$loose_reg) {
433                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
434                         if (!strpos($username, ' ')) {
435                                 throw new Exception(L10n::t("That doesn't appear to be your full \x28First Last\x29 name."));
436                         }
437                 }
438
439                 if (!Network::isEmailDomainAllowed($email)) {
440                         throw new Exception(L10n::t('Your email domain is not among those allowed on this site.'));
441                 }
442
443                 if (!valid_email($email) || !Network::isEmailDomainValid($email)) {
444                         throw new Exception(L10n::t('Not a valid email address.'));
445                 }
446                 if (self::isNicknameBlocked($nickname)) {
447                         throw new Exception(L10n::t('The nickname was blocked from registration by the nodes admin.'));
448                 }
449
450                 if (Config::get('system', 'block_extended_register', false) && dba::exists('user', ['email' => $email])) {
451                         throw new Exception(L10n::t('Cannot use that email.'));
452                 }
453
454                 // Disallow somebody creating an account using openid that uses the admin email address,
455                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
456                 if (x($a->config, 'admin_email') && strlen($openid_url)) {
457                         $adminlist = explode(',', str_replace(' ', '', strtolower($a->config['admin_email'])));
458                         if (in_array(strtolower($email), $adminlist)) {
459                                 throw new Exception(L10n::t('Cannot use that email.'));
460                         }
461                 }
462
463                 $nickname = $data['nickname'] = strtolower($nickname);
464
465                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
466                         throw new Exception(L10n::t('Your nickname can only contain a-z, 0-9 and _.'));
467                 }
468
469                 // Check existing and deleted accounts for this nickname.
470                 if (dba::exists('user', ['nickname' => $nickname])
471                         || dba::exists('userd', ['username' => $nickname])
472                 ) {
473                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
474                 }
475
476                 $new_password = strlen($password) ? $password : User::generateNewPassword();
477                 $new_password_encoded = self::hashPassword($new_password);
478
479                 $return['password'] = $new_password;
480
481                 $keys = Crypto::newKeypair(4096);
482                 if ($keys === false) {
483                         throw new Exception(L10n::t('SERIOUS ERROR: Generation of security keys failed.'));
484                 }
485
486                 $prvkey = $keys['prvkey'];
487                 $pubkey = $keys['pubkey'];
488
489                 // Create another keypair for signing/verifying salmon protocol messages.
490                 $sres = Crypto::newKeypair(512);
491                 $sprvkey = $sres['prvkey'];
492                 $spubkey = $sres['pubkey'];
493
494                 $insert_result = dba::insert('user', [
495                         'guid'     => generate_user_guid(),
496                         'username' => $username,
497                         'password' => $new_password_encoded,
498                         'email'    => $email,
499                         'openid'   => $openid_url,
500                         'nickname' => $nickname,
501                         'pubkey'   => $pubkey,
502                         'prvkey'   => $prvkey,
503                         'spubkey'  => $spubkey,
504                         'sprvkey'  => $sprvkey,
505                         'verified' => $verified,
506                         'blocked'  => $blocked,
507                         'language' => $language,
508                         'timezone' => 'UTC',
509                         'register_date' => DateTimeFormat::utcNow(),
510                         'default-location' => ''
511                 ]);
512
513                 if ($insert_result) {
514                         $uid = dba::lastInsertId();
515                         $user = dba::selectFirst('user', [], ['uid' => $uid]);
516                 } else {
517                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
518                 }
519
520                 if (!$uid) {
521                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
522                 }
523
524                 // if somebody clicked submit twice very quickly, they could end up with two accounts
525                 // due to race condition. Remove this one.
526                 $user_count = dba::count('user', ['nickname' => $nickname]);
527                 if ($user_count > 1) {
528                         dba::delete('user', ['uid' => $uid]);
529
530                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
531                 }
532
533                 $insert_result = dba::insert('profile', [
534                         'uid' => $uid,
535                         'name' => $username,
536                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
537                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
538                         'publish' => $publish,
539                         'is-default' => 1,
540                         'net-publish' => $netpublish,
541                         'profile-name' => L10n::t('default')
542                 ]);
543                 if (!$insert_result) {
544                         dba::delete('user', ['uid' => $uid]);
545
546                         throw new Exception(L10n::t('An error occurred creating your default profile. Please try again.'));
547                 }
548
549                 // Create the self contact
550                 if (!Contact::createSelfFromUserId($uid)) {
551                         dba::delete('user', ['uid' => $uid]);
552
553                         throw new Exception(L10n::t('An error occurred creating your self contact. Please try again.'));
554                 }
555
556                 // Create a group with no members. This allows somebody to use it
557                 // right away as a default group for new contacts.
558                 $def_gid = Group::create($uid, L10n::t('Friends'));
559                 if (!$def_gid) {
560                         dba::delete('user', ['uid' => $uid]);
561
562                         throw new Exception(L10n::t('An error occurred creating your default contact group. Please try again.'));
563                 }
564
565                 $fields = ['def_gid' => $def_gid];
566                 if (Config::get('system', 'newuser_private') && $def_gid) {
567                         $fields['allow_gid'] = '<' . $def_gid . '>';
568                 }
569
570                 dba::update('user', $fields, ['uid' => $uid]);
571
572                 // if we have no OpenID photo try to look up an avatar
573                 if (!strlen($photo)) {
574                         $photo = Network::lookupAvatarByEmail($email);
575                 }
576
577                 // unless there is no avatar-addon loaded
578                 if (strlen($photo)) {
579                         $photo_failure = false;
580
581                         $filename = basename($photo);
582                         $img_str = Network::fetchUrl($photo, true);
583                         // guess mimetype from headers or filename
584                         $type = Image::guessType($photo, true);
585
586                         $Image = new Image($img_str, $type);
587                         if ($Image->isValid()) {
588                                 $Image->scaleToSquare(175);
589
590                                 $hash = Photo::newResource();
591
592                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 4);
593
594                                 if ($r === false) {
595                                         $photo_failure = true;
596                                 }
597
598                                 $Image->scaleDown(80);
599
600                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 5);
601
602                                 if ($r === false) {
603                                         $photo_failure = true;
604                                 }
605
606                                 $Image->scaleDown(48);
607
608                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 6);
609
610                                 if ($r === false) {
611                                         $photo_failure = true;
612                                 }
613
614                                 if (!$photo_failure) {
615                                         dba::update('photo', ['profile' => 1], ['resource-id' => $hash]);
616                                 }
617                         }
618                 }
619
620                 Addon::callHooks('register_account', $uid);
621
622                 $return['user'] = $user;
623                 return $return;
624         }
625
626         /**
627          * @brief Sends pending registration confiƕmation email
628          *
629          * @param string $email
630          * @param string $sitename
631          * @param string $username
632          * @return NULL|boolean from notification() and email() inherited
633          */
634         public static function sendRegisterPendingEmail($email, $sitename, $username)
635         {
636                 $body = deindent(L10n::t('
637                         Dear %1$s,
638                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
639                 '));
640
641                 $body = sprintf($body, $username, $sitename);
642
643                 return notification([
644                         'type' => SYSTEM_EMAIL,
645                         'to_email' => $email,
646                         'subject'=> L10n::t('Registration at %s', $sitename),
647                         'body' => $body]);
648         }
649
650         /**
651          * @brief Sends registration confirmation
652          *
653          * It's here as a function because the mail is sent from different parts
654          *
655          * @param string $email
656          * @param string $sitename
657          * @param string $siteurl
658          * @param string $username
659          * @param string $password
660          * @return NULL|boolean from notification() and email() inherited
661          */
662         public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password)
663         {
664                 $preamble = deindent(L10n::t('
665                         Dear %1$s,
666                                 Thank you for registering at %2$s. Your account has been created.
667                 '));
668                 $body = deindent(L10n::t('
669                         The login details are as follows:
670
671                         Site Location:  %3$s
672                         Login Name:             %1$s
673                         Password:               %5$s
674
675                         You may change your password from your account "Settings" page after logging
676                         in.
677
678                         Please take a few moments to review the other account settings on that page.
679
680                         You may also wish to add some basic information to your default profile
681                         ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
682
683                         We recommend setting your full name, adding a profile photo,
684                         adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and
685                         perhaps what country you live in; if you do not wish to be more specific
686                         than that.
687
688                         We fully respect your right to privacy, and none of these items are necessary.
689                         If you are new and do not know anybody here, they may help
690                         you to make some new and interesting friends.
691
692                         If you ever want to delete your account, you can do so at %3$s/removeme
693
694                         Thank you and welcome to %2$s.'));
695
696                 $preamble = sprintf($preamble, $username, $sitename);
697                 $body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
698
699                 return notification([
700                         'type' => SYSTEM_EMAIL,
701                         'to_email' => $email,
702                         'subject'=> L10n::t('Registration details for %s', $sitename),
703                         'preamble'=> $preamble,
704                         'body' => $body]);
705         }
706
707         /**
708          * @param object $uid user to remove
709          * @return void
710          */
711         public static function remove($uid)
712         {
713                 if (!$uid) {
714                         return;
715                 }
716
717                 logger('Removing user: ' . $uid);
718
719                 $user = dba::selectFirst('user', [], ['uid' => $uid]);
720
721                 Addon::callHooks('remove_user', $user);
722
723                 // save username (actually the nickname as it is guaranteed
724                 // unique), so it cannot be re-registered in the future.
725                 dba::insert('userd', ['username' => $user['nickname']]);
726
727                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
728                 dba::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utcNow()], ['uid' => $uid]);
729                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
730
731                 // Send an update to the directory
732                 Worker::add(PRIORITY_LOW, "Directory", $user['url']);
733
734                 if ($uid == local_user()) {
735                         unset($_SESSION['authenticated']);
736                         unset($_SESSION['uid']);
737                         goaway(System::baseUrl());
738                 }
739         }
740 }