]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
Update "storage" console command
[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\Config;
11 use Friendica\Core\Hook;
12 use Friendica\Core\L10n;
13 use Friendica\Core\Logger;
14 use Friendica\Core\PConfig;
15 use Friendica\Core\Protocol;
16 use Friendica\Core\System;
17 use Friendica\Core\Worker;
18 use Friendica\Database\DBA;
19 use Friendica\Model\Photo;
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::updatePasswordHashed($user['uid'], self::hashPassword($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::updatePasswordHashed($user['uid'], self::hashPassword($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::updatePasswordHashed($user['uid'], self::hashPassword($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 ucfirst(Strings::getRandomName(8)) . mt_rand(1000, 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          * @throws Exception
321          */
322         public static function hashPassword($password)
323         {
324                 if (!trim($password)) {
325                         throw new Exception(L10n::t('Password can\'t be empty'));
326                 }
327
328                 return password_hash($password, PASSWORD_DEFAULT);
329         }
330
331         /**
332          * Updates a user row with a new plaintext password
333          *
334          * @param int    $uid
335          * @param string $password
336          * @return bool
337          * @throws Exception
338          */
339         public static function updatePassword($uid, $password)
340         {
341                 $password = trim($password);
342
343                 if (empty($password)) {
344                         throw new Exception(L10n::t('Empty passwords are not allowed.'));
345                 }
346
347                 if (!Config::get('system', 'disable_password_exposed', false) && self::isPasswordExposed($password)) {
348                         throw new Exception(L10n::t('The new password has been exposed in a public data dump, please choose another.'));
349                 }
350
351                 $allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
352
353                 if (!preg_match('/^[a-z0-9' . preg_quote($allowed_characters, '/') . ']+$/i', $password)) {
354                         throw new Exception(L10n::t('The password can\'t contain accentuated letters, white spaces or colons (:)'));
355                 }
356
357                 return self::updatePasswordHashed($uid, self::hashPassword($password));
358         }
359
360         /**
361          * Updates a user row with a new hashed password.
362          * Empties the password reset token field just in case.
363          *
364          * @param int    $uid
365          * @param string $pasword_hashed
366          * @return bool
367          */
368         private static function updatePasswordHashed($uid, $pasword_hashed)
369         {
370                 $fields = [
371                         'password' => $pasword_hashed,
372                         'pwdreset' => null,
373                         'pwdreset_time' => null,
374                         'legacy_password' => false
375                 ];
376                 return DBA::update('user', $fields, ['uid' => $uid]);
377         }
378
379         /**
380          * @brief Checks if a nickname is in the list of the forbidden nicknames
381          *
382          * Check if a nickname is forbidden from registration on the node by the
383          * admin. Forbidden nicknames (e.g. role namess) can be configured in the
384          * admin panel.
385          *
386          * @param string $nickname The nickname that should be checked
387          * @return boolean True is the nickname is blocked on the node
388          */
389         public static function isNicknameBlocked($nickname)
390         {
391                 $forbidden_nicknames = Config::get('system', 'forbidden_nicknames', '');
392
393                 // if the config variable is empty return false
394                 if (empty($forbidden_nicknames)) {
395                         return false;
396                 }
397
398                 // check if the nickname is in the list of blocked nicknames
399                 $forbidden = explode(',', $forbidden_nicknames);
400                 $forbidden = array_map('trim', $forbidden);
401                 if (in_array(strtolower($nickname), $forbidden)) {
402                         return true;
403                 }
404
405                 // else return false
406                 return false;
407         }
408
409         /**
410          * @brief Catch-all user creation function
411          *
412          * Creates a user from the provided data array, either form fields or OpenID.
413          * Required: { username, nickname, email } or { openid_url }
414          *
415          * Performs the following:
416          * - Sends to the OpenId auth URL (if relevant)
417          * - Creates new key pairs for crypto
418          * - Create self-contact
419          * - Create profile image
420          *
421          * @param  array $data
422          * @return array
423          * @throws \ErrorException
424          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
425          * @throws Exception
426          */
427         public static function create(array $data)
428         {
429                 $a = \get_app();
430                 $return = ['user' => null, 'password' => ''];
431
432                 $using_invites = Config::get('system', 'invitation_only');
433                 $num_invites   = Config::get('system', 'number_invites');
434
435                 $invite_id  = !empty($data['invite_id'])  ? Strings::escapeTags(trim($data['invite_id']))  : '';
436                 $username   = !empty($data['username'])   ? Strings::escapeTags(trim($data['username']))   : '';
437                 $nickname   = !empty($data['nickname'])   ? Strings::escapeTags(trim($data['nickname']))   : '';
438                 $email      = !empty($data['email'])      ? Strings::escapeTags(trim($data['email']))      : '';
439                 $openid_url = !empty($data['openid_url']) ? Strings::escapeTags(trim($data['openid_url'])) : '';
440                 $photo      = !empty($data['photo'])      ? Strings::escapeTags(trim($data['photo']))      : '';
441                 $password   = !empty($data['password'])   ? trim($data['password'])           : '';
442                 $password1  = !empty($data['password1'])  ? trim($data['password1'])          : '';
443                 $confirm    = !empty($data['confirm'])    ? trim($data['confirm'])            : '';
444                 $blocked    = !empty($data['blocked']);
445                 $verified   = !empty($data['verified']);
446                 $language   = !empty($data['language'])   ? Strings::escapeTags(trim($data['language']))   : 'en';
447
448                 $publish = !empty($data['profile_publish_reg']);
449                 $netpublish = $publish && Config::get('system', 'directory');
450
451                 if ($password1 != $confirm) {
452                         throw new Exception(L10n::t('Passwords do not match. Password unchanged.'));
453                 } elseif ($password1 != '') {
454                         $password = $password1;
455                 }
456
457                 if ($using_invites) {
458                         if (!$invite_id) {
459                                 throw new Exception(L10n::t('An invitation is required.'));
460                         }
461
462                         if (!Register::existsByHash($invite_id)) {
463                                 throw new Exception(L10n::t('Invitation could not be verified.'));
464                         }
465                 }
466
467                 if (empty($username) || empty($email) || empty($nickname)) {
468                         if ($openid_url) {
469                                 if (!Network::isUrlValid($openid_url)) {
470                                         throw new Exception(L10n::t('Invalid OpenID url'));
471                                 }
472                                 $_SESSION['register'] = 1;
473                                 $_SESSION['openid'] = $openid_url;
474
475                                 $openid = new LightOpenID($a->getHostName());
476                                 $openid->identity = $openid_url;
477                                 $openid->returnUrl = System::baseUrl() . '/openid';
478                                 $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
479                                 $openid->optional = ['namePerson/first', 'media/image/aspect11', 'media/image/default'];
480                                 try {
481                                         $authurl = $openid->authUrl();
482                                 } catch (Exception $e) {
483                                         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);
484                                 }
485                                 System::externalRedirect($authurl);
486                                 // NOTREACHED
487                         }
488
489                         throw new Exception(L10n::t('Please enter the required information.'));
490                 }
491
492                 if (!Network::isUrlValid($openid_url)) {
493                         $openid_url = '';
494                 }
495
496                 $err = '';
497
498                 // collapse multiple spaces in name
499                 $username = preg_replace('/ +/', ' ', $username);
500
501                 $username_min_length = max(1, min(64, intval(Config::get('system', 'username_min_length', 3))));
502                 $username_max_length = max(1, min(64, intval(Config::get('system', 'username_max_length', 48))));
503
504                 if ($username_min_length > $username_max_length) {
505                         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);
506                         $tmp = $username_min_length;
507                         $username_min_length = $username_max_length;
508                         $username_max_length = $tmp;
509                 }
510
511                 if (mb_strlen($username) < $username_min_length) {
512                         throw new Exception(L10n::tt('Username should be at least %s character.', 'Username should be at least %s characters.', $username_min_length));
513                 }
514
515                 if (mb_strlen($username) > $username_max_length) {
516                         throw new Exception(L10n::tt('Username should be at most %s character.', 'Username should be at most %s characters.', $username_max_length));
517                 }
518
519                 // So now we are just looking for a space in the full name.
520                 $loose_reg = Config::get('system', 'no_regfullname');
521                 if (!$loose_reg) {
522                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
523                         if (strpos($username, ' ') === false) {
524                                 throw new Exception(L10n::t("That doesn't appear to be your full (First Last) name."));
525                         }
526                 }
527
528                 if (!Network::isEmailDomainAllowed($email)) {
529                         throw new Exception(L10n::t('Your email domain is not among those allowed on this site.'));
530                 }
531
532                 if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !Network::isEmailDomainValid($email)) {
533                         throw new Exception(L10n::t('Not a valid email address.'));
534                 }
535                 if (self::isNicknameBlocked($nickname)) {
536                         throw new Exception(L10n::t('The nickname was blocked from registration by the nodes admin.'));
537                 }
538
539                 if (Config::get('system', 'block_extended_register', false) && DBA::exists('user', ['email' => $email])) {
540                         throw new Exception(L10n::t('Cannot use that email.'));
541                 }
542
543                 // Disallow somebody creating an account using openid that uses the admin email address,
544                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
545                 if (Config::get('config', 'admin_email') && strlen($openid_url)) {
546                         $adminlist = explode(',', str_replace(' ', '', strtolower(Config::get('config', 'admin_email'))));
547                         if (in_array(strtolower($email), $adminlist)) {
548                                 throw new Exception(L10n::t('Cannot use that email.'));
549                         }
550                 }
551
552                 $nickname = $data['nickname'] = strtolower($nickname);
553
554                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
555                         throw new Exception(L10n::t('Your nickname can only contain a-z, 0-9 and _.'));
556                 }
557
558                 // Check existing and deleted accounts for this nickname.
559                 if (DBA::exists('user', ['nickname' => $nickname])
560                         || DBA::exists('userd', ['username' => $nickname])
561                 ) {
562                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
563                 }
564
565                 $new_password = strlen($password) ? $password : User::generateNewPassword();
566                 $new_password_encoded = self::hashPassword($new_password);
567
568                 $return['password'] = $new_password;
569
570                 $keys = Crypto::newKeypair(4096);
571                 if ($keys === false) {
572                         throw new Exception(L10n::t('SERIOUS ERROR: Generation of security keys failed.'));
573                 }
574
575                 $prvkey = $keys['prvkey'];
576                 $pubkey = $keys['pubkey'];
577
578                 // Create another keypair for signing/verifying salmon protocol messages.
579                 $sres = Crypto::newKeypair(512);
580                 $sprvkey = $sres['prvkey'];
581                 $spubkey = $sres['pubkey'];
582
583                 $insert_result = DBA::insert('user', [
584                         'guid'     => System::createUUID(),
585                         'username' => $username,
586                         'password' => $new_password_encoded,
587                         'email'    => $email,
588                         'openid'   => $openid_url,
589                         'nickname' => $nickname,
590                         'pubkey'   => $pubkey,
591                         'prvkey'   => $prvkey,
592                         'spubkey'  => $spubkey,
593                         'sprvkey'  => $sprvkey,
594                         'verified' => $verified,
595                         'blocked'  => $blocked,
596                         'language' => $language,
597                         'timezone' => 'UTC',
598                         'register_date' => DateTimeFormat::utcNow(),
599                         'default-location' => ''
600                 ]);
601
602                 if ($insert_result) {
603                         $uid = DBA::lastInsertId();
604                         $user = DBA::selectFirst('user', [], ['uid' => $uid]);
605                 } else {
606                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
607                 }
608
609                 if (!$uid) {
610                         throw new Exception(L10n::t('An error occurred during registration. Please try again.'));
611                 }
612
613                 // if somebody clicked submit twice very quickly, they could end up with two accounts
614                 // due to race condition. Remove this one.
615                 $user_count = DBA::count('user', ['nickname' => $nickname]);
616                 if ($user_count > 1) {
617                         DBA::delete('user', ['uid' => $uid]);
618
619                         throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
620                 }
621
622                 $insert_result = DBA::insert('profile', [
623                         'uid' => $uid,
624                         'name' => $username,
625                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
626                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
627                         'publish' => $publish,
628                         'is-default' => 1,
629                         'net-publish' => $netpublish,
630                         'profile-name' => L10n::t('default')
631                 ]);
632                 if (!$insert_result) {
633                         DBA::delete('user', ['uid' => $uid]);
634
635                         throw new Exception(L10n::t('An error occurred creating your default profile. Please try again.'));
636                 }
637
638                 // Create the self contact
639                 if (!Contact::createSelfFromUserId($uid)) {
640                         DBA::delete('user', ['uid' => $uid]);
641
642                         throw new Exception(L10n::t('An error occurred creating your self contact. Please try again.'));
643                 }
644
645                 // Create a group with no members. This allows somebody to use it
646                 // right away as a default group for new contacts.
647                 $def_gid = Group::create($uid, L10n::t('Friends'));
648                 if (!$def_gid) {
649                         DBA::delete('user', ['uid' => $uid]);
650
651                         throw new Exception(L10n::t('An error occurred creating your default contact group. Please try again.'));
652                 }
653
654                 $fields = ['def_gid' => $def_gid];
655                 if (Config::get('system', 'newuser_private') && $def_gid) {
656                         $fields['allow_gid'] = '<' . $def_gid . '>';
657                 }
658
659                 DBA::update('user', $fields, ['uid' => $uid]);
660
661                 // if we have no OpenID photo try to look up an avatar
662                 if (!strlen($photo)) {
663                         $photo = Network::lookupAvatarByEmail($email);
664                 }
665
666                 // unless there is no avatar-addon loaded
667                 if (strlen($photo)) {
668                         $photo_failure = false;
669
670                         $filename = basename($photo);
671                         $img_str = Network::fetchUrl($photo, true);
672                         // guess mimetype from headers or filename
673                         $type = Image::guessType($photo, true);
674
675                         $Image = new Image($img_str, $type);
676                         if ($Image->isValid()) {
677                                 $Image->scaleToSquare(300);
678
679                                 $hash = Photo::newResource();
680
681                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 4);
682
683                                 if ($r === false) {
684                                         $photo_failure = true;
685                                 }
686
687                                 $Image->scaleDown(80);
688
689                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 5);
690
691                                 if ($r === false) {
692                                         $photo_failure = true;
693                                 }
694
695                                 $Image->scaleDown(48);
696
697                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, L10n::t('Profile Photos'), 6);
698
699                                 if ($r === false) {
700                                         $photo_failure = true;
701                                 }
702
703                                 if (!$photo_failure) {
704                                         Photo::update(['profile' => 1], ['resource-id' => $hash]);
705                                 }
706                         }
707                 }
708
709                 Hook::callAll('register_account', $uid);
710
711                 $return['user'] = $user;
712                 return $return;
713         }
714
715         /**
716          * @brief Sends pending registration confirmation email
717          *
718          * @param array  $user     User record array
719          * @param string $sitename
720          * @param string $siteurl
721          * @param string $password Plaintext password
722          * @return NULL|boolean from notification() and email() inherited
723          */
724         public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
725         {
726                 $body = Strings::deindent(L10n::t('
727                         Dear %1$s,
728                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
729
730                         Your login details are as follows:
731
732                         Site Location:  %3$s
733                         Login Name:             %4$s
734                         Password:               %5$s
735                 ',
736                         $user['username'], $sitename, $siteurl, $user['nickname'], $password
737                 ));
738
739                 return notification([
740                         'type'     => SYSTEM_EMAIL,
741                         'uid'      => $user['uid'],
742                         'to_email' => $user['email'],
743                         'subject'  => L10n::t('Registration at %s', $sitename),
744                         'body'     => $body
745                 ]);
746         }
747
748         /**
749          * @brief Sends registration confirmation
750          *
751          * It's here as a function because the mail is sent from different parts
752          *
753          * @param array  $user     User record array
754          * @param string $sitename
755          * @param string $siteurl
756          * @param string $password Plaintext password
757          * @return NULL|boolean from notification() and email() inherited
758          */
759         public static function sendRegisterOpenEmail($user, $sitename, $siteurl, $password)
760         {
761                 $preamble = Strings::deindent(L10n::t('
762                         Dear %1$s,
763                                 Thank you for registering at %2$s. Your account has been created.
764                 ',
765                         $user['username'], $sitename
766                 ));
767                 $body = Strings::deindent(L10n::t('
768                         The login details are as follows:
769
770                         Site Location:  %3$s
771                         Login Name:             %1$s
772                         Password:               %5$s
773
774                         You may change your password from your account "Settings" page after logging
775                         in.
776
777                         Please take a few moments to review the other account settings on that page.
778
779                         You may also wish to add some basic information to your default profile
780                         ' . "\x28" . 'on the "Profiles" page' . "\x29" . ' so that other people can easily find you.
781
782                         We recommend setting your full name, adding a profile photo,
783                         adding some profile "keywords" ' . "\x28" . 'very useful in making new friends' . "\x29" . ' - and
784                         perhaps what country you live in; if you do not wish to be more specific
785                         than that.
786
787                         We fully respect your right to privacy, and none of these items are necessary.
788                         If you are new and do not know anybody here, they may help
789                         you to make some new and interesting friends.
790
791                         If you ever want to delete your account, you can do so at %3$s/removeme
792
793                         Thank you and welcome to %2$s.',
794                         $user['nickname'], $sitename, $siteurl, $user['username'], $password
795                 ));
796
797                 return notification([
798                         'uid'      => $user['uid'],
799                         'language' => $user['language'],
800                         'type'     => SYSTEM_EMAIL,
801                         'to_email' => $user['email'],
802                         'subject'  => L10n::t('Registration details for %s', $sitename),
803                         'preamble' => $preamble,
804                         'body'     => $body
805                 ]);
806         }
807
808         /**
809          * @param object $uid user to remove
810          * @return void
811          */
812         public static function remove($uid)
813         {
814                 if (!$uid) {
815                         return false;
816                 }
817
818                 $a = \get_app();
819
820                 Logger::log('Removing user: ' . $uid);
821
822                 $user = DBA::selectFirst('user', [], ['uid' => $uid]);
823
824                 Hook::callAll('remove_user', $user);
825
826                 // save username (actually the nickname as it is guaranteed
827                 // unique), so it cannot be re-registered in the future.
828                 DBA::insert('userd', ['username' => $user['nickname']]);
829
830                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
831                 DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]);
832                 Worker::add(PRIORITY_HIGH, 'Notifier', 'removeme', $uid);
833
834                 // Send an update to the directory
835                 $self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
836                 Worker::add(PRIORITY_LOW, 'Directory', $self['url']);
837
838                 // Remove the user relevant data
839                 Worker::add(PRIORITY_LOW, 'RemoveUser', $uid);
840
841                 return true;
842         }
843
844         /**
845          * Return all identities to a user
846          *
847          * @param int $uid The user id
848          * @return array All identities for this user
849          *
850          * Example for a return:
851          *      [
852          *              [
853          *                      'uid' => 1,
854          *                      'username' => 'maxmuster',
855          *                      'nickname' => 'Max Mustermann'
856          *              ],
857          *              [
858          *                      'uid' => 2,
859          *                      'username' => 'johndoe',
860          *                      'nickname' => 'John Doe'
861          *              ]
862          *      ]
863          */
864         public static function identities($uid)
865         {
866                 $identities = [];
867
868                 $user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]);
869                 if (!DBA::isResult($user)) {
870                         return $identities;
871                 }
872
873                 if ($user['parent-uid'] == 0) {
874                         // First add our own entry
875                         $identities = [['uid' => $user['uid'],
876                                 'username' => $user['username'],
877                                 'nickname' => $user['nickname']]];
878
879                         // Then add all the children
880                         $r = DBA::select('user', ['uid', 'username', 'nickname'],
881                                 ['parent-uid' => $user['uid'], 'account_removed' => false]);
882                         if (DBA::isResult($r)) {
883                                 $identities = array_merge($identities, DBA::toArray($r));
884                         }
885                 } else {
886                         // First entry is our parent
887                         $r = DBA::select('user', ['uid', 'username', 'nickname'],
888                                 ['uid' => $user['parent-uid'], 'account_removed' => false]);
889                         if (DBA::isResult($r)) {
890                                 $identities = DBA::toArray($r);
891                         }
892
893                         // Then add all siblings
894                         $r = DBA::select('user', ['uid', 'username', 'nickname'],
895                                 ['parent-uid' => $user['parent-uid'], 'account_removed' => false]);
896                         if (DBA::isResult($r)) {
897                                 $identities = array_merge($identities, DBA::toArray($r));
898                         }
899                 }
900
901                 $r = DBA::p("SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
902                         FROM `manage`
903                         INNER JOIN `user` ON `manage`.`mid` = `user`.`uid`
904                         WHERE `user`.`account_removed` = 0 AND `manage`.`uid` = ?",
905                         $user['uid']
906                 );
907                 if (DBA::isResult($r)) {
908                         $identities = array_merge($identities, DBA::toArray($r));
909                 }
910
911                 return $identities;
912         }
913 }