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