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