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