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