]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
63aaa1e3de2c59312e1033bbd2ad6d5807e44210
[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                 if (mb_strlen($username) > 48) {
470                         throw new Exception(L10n::t('Please use a shorter name.'));
471                 }
472                 if (mb_strlen($username) < 3) {
473                         throw new Exception(L10n::t('Name too short.'));
474                 }
475
476                 // So now we are just looking for a space in the full name.
477                 $loose_reg = Config::get('system', 'no_regfullname');
478                 if (!$loose_reg) {
479                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
480                         if (!strpos($username, ' ')) {
481                                 throw new Exception(L10n::t("That doesn't appear to be your full \x28First Last\x29 name."));
482                         }
483                 }
484
485                 if (!Network::isEmailDomainAllowed($email)) {
486                         throw new Exception(L10n::t('Your email domain is not among those allowed on this site.'));
487                 }
488
489                 if (!valid_email($email) || !Network::isEmailDomainValid($email)) {
490                         throw new Exception(L10n::t('Not a valid email address.'));
491                 }
492                 if (self::isNicknameBlocked($nickname)) {
493                         throw new Exception(L10n::t('The nickname was blocked from registration by the nodes admin.'));
494                 }
495
496                 if (Config::get('system', 'block_extended_register', false) && DBA::exists('user', ['email' => $email])) {
497                         throw new Exception(L10n::t('Cannot use that email.'));
498                 }
499
500                 // Disallow somebody creating an account using openid that uses the admin email address,
501                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
502                 if (Config::get('config', 'admin_email') && strlen($openid_url)) {
503                         $adminlist = explode(',', str_replace(' ', '', strtolower(Config::get('config', 'admin_email'))));
504                         if (in_array(strtolower($email), $adminlist)) {
505                                 throw new Exception(L10n::t('Cannot use that email.'));
506                         }
507                 }
508
509                 $nickname = $data['nickname'] = strtolower($nickname);
510
511                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
512                         throw new Exception(L10n::t('Your nickname can only contain a-z, 0-9 and _.'));
513                 }
514
515                 // Check existing and deleted accounts for this nickname.
516                 if (DBA::exists('user', ['nickname' => $nickname])
517                         || DBA::exists('userd', ['username' => $nickname])
518                 ) {
519                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
520                 }
521
522                 $new_password = strlen($password) ? $password : User::generateNewPassword();
523                 $new_password_encoded = self::hashPassword($new_password);
524
525                 $return['password'] = $new_password;
526
527                 $keys = Crypto::newKeypair(4096);
528                 if ($keys === false) {
529                         throw new Exception(L10n::t('SERIOUS ERROR: Generation of security keys failed.'));
530                 }
531
532                 $prvkey = $keys['prvkey'];
533                 $pubkey = $keys['pubkey'];
534
535                 // Create another keypair for signing/verifying salmon protocol messages.
536                 $sres = Crypto::newKeypair(512);
537                 $sprvkey = $sres['prvkey'];
538                 $spubkey = $sres['pubkey'];
539
540                 $insert_result = DBA::insert('user', [
541                         'guid'     => System::createUUID(),
542                         'username' => $username,
543                         'password' => $new_password_encoded,
544                         'email'    => $email,
545                         'openid'   => $openid_url,
546                         'nickname' => $nickname,
547                         'pubkey'   => $pubkey,
548                         'prvkey'   => $prvkey,
549                         'spubkey'  => $spubkey,
550                         'sprvkey'  => $sprvkey,
551                         'verified' => $verified,
552                         'blocked'  => $blocked,
553                         'language' => $language,
554                         'timezone' => 'UTC',
555                         'register_date' => DateTimeFormat::utcNow(),
556                         'default-location' => ''
557                 ]);
558
559                 if ($insert_result) {
560                         $uid = DBA::lastInsertId();
561                         $user = DBA::selectFirst('user', [], ['uid' => $uid]);
562                 } else {
563                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
564                 }
565
566                 if (!$uid) {
567                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
568                 }
569
570                 // if somebody clicked submit twice very quickly, they could end up with two accounts
571                 // due to race condition. Remove this one.
572                 $user_count = DBA::count('user', ['nickname' => $nickname]);
573                 if ($user_count > 1) {
574                         DBA::delete('user', ['uid' => $uid]);
575
576                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
577                 }
578
579                 $insert_result = DBA::insert('profile', [
580                         'uid' => $uid,
581                         'name' => $username,
582                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
583                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
584                         'publish' => $publish,
585                         'is-default' => 1,
586                         'net-publish' => $netpublish,
587                         'profile-name' => L10n::t('default')
588                 ]);
589                 if (!$insert_result) {
590                         DBA::delete('user', ['uid' => $uid]);
591
592                         throw new Exception(L10n::t('An error occurred creating your default profile. Please try again.'));
593                 }
594
595                 // Create the self contact
596                 if (!Contact::createSelfFromUserId($uid)) {
597                         DBA::delete('user', ['uid' => $uid]);
598
599                         throw new Exception(L10n::t('An error occurred creating your self contact. Please try again.'));
600                 }
601
602                 // Create a group with no members. This allows somebody to use it
603                 // right away as a default group for new contacts.
604                 $def_gid = Group::create($uid, L10n::t('Friends'));
605                 if (!$def_gid) {
606                         DBA::delete('user', ['uid' => $uid]);
607
608                         throw new Exception(L10n::t('An error occurred creating your default contact group. Please try again.'));
609                 }
610
611                 $fields = ['def_gid' => $def_gid];
612                 if (Config::get('system', 'newuser_private') && $def_gid) {
613                         $fields['allow_gid'] = '<' . $def_gid . '>';
614                 }
615
616                 DBA::update('user', $fields, ['uid' => $uid]);
617
618                 // if we have no OpenID photo try to look up an avatar
619                 if (!strlen($photo)) {
620                         $photo = Network::lookupAvatarByEmail($email);
621                 }
622
623                 // unless there is no avatar-addon loaded
624                 if (strlen($photo)) {
625                         $photo_failure = false;
626
627                         $filename = basename($photo);
628                         $img_str = Network::fetchUrl($photo, true);
629                         // guess mimetype from headers or filename
630                         $type = Image::guessType($photo, true);
631
632                         $Image = new Image($img_str, $type);
633                         if ($Image->isValid()) {
634                                 $Image->scaleToSquare(175);
635
636                                 $hash = Photo::newResource();
637
638                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 4);
639
640                                 if ($r === false) {
641                                         $photo_failure = true;
642                                 }
643
644                                 $Image->scaleDown(80);
645
646                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 5);
647
648                                 if ($r === false) {
649                                         $photo_failure = true;
650                                 }
651
652                                 $Image->scaleDown(48);
653
654                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 6);
655
656                                 if ($r === false) {
657                                         $photo_failure = true;
658                                 }
659
660                                 if (!$photo_failure) {
661                                         DBA::update('photo', ['profile' => 1], ['resource-id' => $hash]);
662                                 }
663                         }
664                 }
665
666                 Addon::callHooks('register_account', $uid);
667
668                 $return['user'] = $user;
669                 return $return;
670         }
671
672         /**
673          * @brief Sends pending registration confirmation email
674          *
675          * @param array  $user     User record array
676          * @param string $sitename
677          * @param string $siteurl
678          * @param string $password Plaintext password
679          * @return NULL|boolean from notification() and email() inherited
680          */
681         public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
682         {
683                 $body = deindent(L10n::t('
684                         Dear %1$s,
685                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
686
687                         Your login details are as follows:
688
689                         Site Location:  %3$s
690                         Login Name:             %4$s
691                         Password:               %5$s
692                 ',
693                         $user['username'], $sitename, $siteurl, $user['nickname'], $password
694                 ));
695
696                 return notification([
697                         'type'     => SYSTEM_EMAIL,
698                         'uid'      => $user['uid'],
699                         'to_email' => $user['email'],
700                         'subject'  => L10n::t('Registration at %s', $sitename),
701                         'body'     => $body
702                 ]);
703         }
704
705         /**
706          * @brief Sends registration confirmation
707          *
708          * It's here as a function because the mail is sent from different parts
709          *
710          * @param array  $user     User record array
711          * @param string $sitename
712          * @param string $siteurl
713          * @param string $password Plaintext password
714          * @return NULL|boolean from notification() and email() inherited
715          */
716         public static function sendRegisterOpenEmail($user, $sitename, $siteurl, $password)
717         {
718                 $preamble = deindent(L10n::t('
719                         Dear %1$s,
720                                 Thank you for registering at %2$s. Your account has been created.
721                 ',
722                         $preamble, $user['username'], $sitename
723                 ));
724                 $body = deindent(L10n::t('
725                         The login details are as follows:
726
727                         Site Location:  %3$s
728                         Login Name:             %1$s
729                         Password:               %5$s
730
731                         You may change your password from your account "Settings" page after logging
732                         in.
733
734                         Please take a few moments to review the other account settings on that page.
735
736                         You may also wish to add some basic information to your default profile
737                         ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
738
739                         We recommend setting your full name, adding a profile photo,
740                         adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and
741                         perhaps what country you live in; if you do not wish to be more specific
742                         than that.
743
744                         We fully respect your right to privacy, and none of these items are necessary.
745                         If you are new and do not know anybody here, they may help
746                         you to make some new and interesting friends.
747
748                         If you ever want to delete your account, you can do so at %3$s/removeme
749
750                         Thank you and welcome to %2$s.',
751                         $user['email'], $sitename, $siteurl, $user['username'], $password
752                 ));
753
754                 return notification([
755                         'uid'      => $user['uid'],
756                         'language' => $user['language'],
757                         'type'     => SYSTEM_EMAIL,
758                         'to_email' => $user['email'],
759                         'subject'  => L10n::t('Registration details for %s', $sitename),
760                         'preamble' => $preamble,
761                         'body'     => $body
762                 ]);
763         }
764
765         /**
766          * @param object $uid user to remove
767          * @return void
768          */
769         public static function remove($uid)
770         {
771                 if (!$uid) {
772                         return;
773                 }
774
775                 logger('Removing user: ' . $uid);
776
777                 $user = DBA::selectFirst('user', [], ['uid' => $uid]);
778
779                 Addon::callHooks('remove_user', $user);
780
781                 // save username (actually the nickname as it is guaranteed
782                 // unique), so it cannot be re-registered in the future.
783                 DBA::insert('userd', ['username' => $user['nickname']]);
784
785                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
786                 DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc(DateTimeFormat::utcNow() . " + 7 day")], ['uid' => $uid]);
787                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
788
789                 // Send an update to the directory
790                 $self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
791                 Worker::add(PRIORITY_LOW, "Directory", $self['url']);
792
793                 // Remove the user relevant data
794                 Worker::add(PRIORITY_LOW, "RemoveUser", $uid);
795
796                 if ($uid == local_user()) {
797                         unset($_SESSION['authenticated']);
798                         unset($_SESSION['uid']);
799                         goaway();;
800                 }
801         }
802 }