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