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