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