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