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