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