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