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