]> git.mxchange.org Git - friendica.git/blob - src/Model/User.php
57f1d0ab241d49862973ef44f973680bf3ab460d
[friendica.git] / src / Model / User.php
1 <?php
2
3 /**
4  * @file src/Model/User.php
5  * @brief This file includes the User class with user related database functions
6  */
7
8 namespace Friendica\Model;
9
10 use Friendica\Core\Addon;
11 use Friendica\Core\Config;
12 use Friendica\Core\PConfig;
13 use Friendica\Core\System;
14 use Friendica\Core\Worker;
15 use Friendica\Database\DBM;
16 use Friendica\Model\Contact;
17 use Friendica\Model\Group;
18 use Friendica\Model\Photo;
19 use Friendica\Object\Image;
20 use Friendica\Util\Crypto;
21 use dba;
22 use Exception;
23
24 require_once 'boot.php';
25 require_once 'include/dba.php';
26 require_once 'include/enotify.php';
27 require_once 'include/network.php';
28 require_once 'library/openid.php';
29 require_once 'include/pgettext.php';
30 require_once 'include/plugin.php';
31 require_once 'include/text.php';
32 /**
33  * @brief This class handles User related functions
34  */
35 class User
36 {
37         /**
38          * @brief Get owner data by user id
39          *
40          * @param int $uid
41          * @return boolean|array
42          */
43         public static function getOwnerDataById($uid) {
44                 $r = dba::fetch_first("SELECT
45                         `contact`.*,
46                         `user`.`prvkey` AS `uprvkey`,
47                         `user`.`timezone`,
48                         `user`.`nickname`,
49                         `user`.`sprvkey`,
50                         `user`.`spubkey`,
51                         `user`.`page-flags`,
52                         `user`.`account-type`,
53                         `user`.`prvnets`
54                         FROM `contact`
55                         INNER JOIN `user`
56                                 ON `user`.`uid` = `contact`.`uid`
57                         WHERE `contact`.`uid` = ?
58                         AND `contact`.`self`
59                         LIMIT 1",
60                         $uid
61                 );
62                 if (!DBM::is_result($r)) {
63                         return false;
64                 }
65                 return $r;
66         }
67
68         /**
69          * @brief Returns the default group for a given user and network
70          *
71          * @param int $uid User id
72          * @param string $network network name
73          *
74          * @return int group id
75          */
76         public static function getDefaultGroup($uid, $network = '')
77         {
78                 $default_group = 0;
79
80                 if ($network == NETWORK_OSTATUS) {
81                         $default_group = PConfig::get($uid, "ostatus", "default_group");
82                 }
83
84                 if ($default_group != 0) {
85                         return $default_group;
86                 }
87
88                 $user = dba::selectFirst('user', ['def_gid'], ['uid' => $uid]);
89
90                 if (DBM::is_result($user)) {
91                         $default_group = $user["def_gid"];
92                 }
93
94                 return $default_group;
95         }
96
97
98         /**
99          * @brief Authenticate a user with a clear text password
100          *
101          * User info can be any of the following:
102          * - User DB object
103          * - User Id
104          * - User email or username or nickname
105          * - User array with at least the uid and the hashed password
106          *
107          * @param mixed $user_info
108          * @param string $password
109          * @return boolean
110          */
111         public static function authenticate($user_info, $password)
112         {
113                 if (is_object($user_info)) {
114                         $user = (array) $user_info;
115                 } elseif (is_int($user_info)) {
116                         $user = dba::selectFirst('user', ['uid', 'password'],
117                                 [
118                                         'uid' => $user_info,
119                                         'blocked' => 0,
120                                         'account_expired' => 0,
121                                         'account_removed' => 0,
122                                         'verified' => 1
123                                 ]
124                         );
125                 } elseif (is_string($user_info)) {
126                         $user = dba::fetch_first('SELECT `uid`, `password`
127                                 FROM `user`
128                                 WHERE (`email` = ? OR `username` = ? OR `nickname` = ?)
129                                 AND `blocked` = 0
130                                 AND `account_expired` = 0
131                                 AND `account_removed` = 0
132                                 AND `verified` = 1
133                                 LIMIT 1',
134                                 $user_info,
135                                 $user_info,
136                                 $user_info
137                         );
138                 } else {
139                         $user = $user_info;
140                 }
141
142                 if (!DBM::is_result($user) || !isset($user['uid']) || !isset($user['password'])) {
143                         return false;
144                 }
145
146                 $password_hashed = hash('whirlpool', $password);
147
148                 if ($password_hashed !== $user['password']) {
149                         return false;
150                 }
151
152                 return $user['uid'];
153         }
154
155         /**
156          * @brief Catch-all user creation function
157          *
158          * Creates a user from the provided data array, either form fields or OpenID.
159          * Required: { username, nickname, email } or { openid_url }
160          *
161          * Performs the following:
162          * - Sends to the OpenId auth URL (if relevant)
163          * - Creates new key pairs for crypto
164          * - Create self-contact
165          * - Create profile image
166          *
167          * @param array $data
168          * @return string
169          * @throw Exception
170          */
171         public static function create(array $data)
172         {
173                 $a = get_app();
174                 $return = ['user' => null, 'password' => ''];
175
176                 $using_invites = Config::get('system', 'invitation_only');
177                 $num_invites   = Config::get('system', 'number_invites');
178
179                 $invite_id  = x($data, 'invite_id')  ? notags(trim($data['invite_id']))  : '';
180                 $username   = x($data, 'username')   ? notags(trim($data['username']))   : '';
181                 $nickname   = x($data, 'nickname')   ? notags(trim($data['nickname']))   : '';
182                 $email      = x($data, 'email')      ? notags(trim($data['email']))      : '';
183                 $openid_url = x($data, 'openid_url') ? notags(trim($data['openid_url'])) : '';
184                 $photo      = x($data, 'photo')      ? notags(trim($data['photo']))      : '';
185                 $password   = x($data, 'password')   ? trim($data['password'])           : '';
186                 $password1  = x($data, 'password1')  ? trim($data['password1'])          : '';
187                 $confirm    = x($data, 'confirm')    ? trim($data['confirm'])            : '';
188                 $blocked    = x($data, 'blocked')    ? intval($data['blocked'])          : 0;
189                 $verified   = x($data, 'verified')   ? intval($data['verified'])         : 0;
190
191                 $publish = x($data, 'profile_publish_reg') && intval($data['profile_publish_reg']) ? 1 : 0;
192                 $netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0;
193
194                 if ($password1 != $confirm) {
195                         throw new Exception(t('Passwords do not match. Password unchanged.'));
196                 } elseif ($password1 != '') {
197                         $password = $password1;
198                 }
199
200                 if ($using_invites) {
201                         if (!$invite_id) {
202                                 throw new Exception(t('An invitation is required.'));
203                         }
204
205                         if (!dba::exists('register', ['hash' => $invite_id])) {
206                                 throw new Exception(t('Invitation could not be verified.'));
207                         }
208                 }
209
210                 if (!x($username) || !x($email) || !x($nickname)) {
211                         if ($openid_url) {
212                                 if (!validate_url($openid_url)) {
213                                         throw new Exception(t('Invalid OpenID url'));
214                                 }
215                                 $_SESSION['register'] = 1;
216                                 $_SESSION['openid'] = $openid_url;
217
218                                 $openid = new \LightOpenID;
219                                 $openid->identity = $openid_url;
220                                 $openid->returnUrl = System::baseUrl() . '/openid';
221                                 $openid->required = ['namePerson/friendly', 'contact/email', 'namePerson'];
222                                 $openid->optional = ['namePerson/first', 'media/image/aspect11', 'media/image/default'];
223                                 try {
224                                         $authurl = $openid->authUrl();
225                                 } catch (Exception $e) {
226                                         throw new Exception(t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . t('The error message was:') . $e->getMessage(), 0, $e);
227                                 }
228                                 goaway($authurl);
229                                 // NOTREACHED
230                         }
231
232                         throw new Exception(t('Please enter the required information.'));
233                 }
234
235                 if (!validate_url($openid_url)) {
236                         $openid_url = '';
237                 }
238
239                 $err = '';
240
241                 // collapse multiple spaces in name
242                 $username = preg_replace('/ +/', ' ', $username);
243
244                 if (mb_strlen($username) > 48) {
245                         throw new Exception(t('Please use a shorter name.'));
246                 }
247                 if (mb_strlen($username) < 3) {
248                         throw new Exception(t('Name too short.'));
249                 }
250
251                 // So now we are just looking for a space in the full name.
252                 $loose_reg = Config::get('system', 'no_regfullname');
253                 if (!$loose_reg) {
254                         $username = mb_convert_case($username, MB_CASE_TITLE, 'UTF-8');
255                         if (!strpos($username, ' ')) {
256                                 throw new Exception(t("That doesn't appear to be your full \x28First Last\x29 name."));
257                         }
258                 }
259
260                 if (!allowed_email($email)) {
261                         throw new Exception(t('Your email domain is not among those allowed on this site.'));
262                 }
263
264                 if (!valid_email($email) || !validate_email($email)) {
265                         throw new Exception(t('Not a valid email address.'));
266                 }
267
268                 if (dba::exists('user', ['email' => $email])) {
269                         throw new Exception(t('Cannot use that email.'));
270                 }
271
272                 // Disallow somebody creating an account using openid that uses the admin email address,
273                 // since openid bypasses email verification. We'll allow it if there is not yet an admin account.
274                 if (x($a->config, 'admin_email') && strlen($openid_url)) {
275                         $adminlist = explode(',', str_replace(' ', '', strtolower($a->config['admin_email'])));
276                         if (in_array(strtolower($email), $adminlist)) {
277                                 throw new Exception(t('Cannot use that email.'));
278                         }
279                 }
280
281                 $nickname = $data['nickname'] = strtolower($nickname);
282
283                 if (!preg_match('/^[a-z0-9][a-z0-9\_]*$/', $nickname)) {
284                         throw new Exception(t('Your "nickname" can only contain "a-z", "0-9" and "_".'));
285                 }
286
287                 // Check existing and deleted accounts for this nickname.
288                 if (dba::exists('user', ['nickname' => $nickname])
289                         || dba::exists('userd', ['username' => $nickname])
290                 ) {
291                         throw new Exception(t('Nickname is already registered. Please choose another.'));
292                 }
293
294                 $new_password = strlen($password) ? $password : autoname(6) . mt_rand(100, 9999);
295                 $new_password_encoded = hash('whirlpool', $new_password);
296
297                 $return['password'] = $new_password;
298
299                 $keys = Crypto::newKeypair(4096);
300                 if ($keys === false) {
301                         throw new Exception(t('SERIOUS ERROR: Generation of security keys failed.'));
302                 }
303
304                 $prvkey = $keys['prvkey'];
305                 $pubkey = $keys['pubkey'];
306
307                 // Create another keypair for signing/verifying salmon protocol messages.
308                 $sres = Crypto::newKeypair(512);
309                 $sprvkey = $sres['prvkey'];
310                 $spubkey = $sres['pubkey'];
311
312                 $insert_result = dba::insert('user', [
313                         'guid'     => generate_user_guid(),
314                         'username' => $username,
315                         'password' => $new_password_encoded,
316                         'email'    => $email,
317                         'openid'   => $openid_url,
318                         'nickname' => $nickname,
319                         'pubkey'   => $pubkey,
320                         'prvkey'   => $prvkey,
321                         'spubkey'  => $spubkey,
322                         'sprvkey'  => $sprvkey,
323                         'verified' => $verified,
324                         'blocked'  => $blocked,
325                         'timezone' => 'UTC',
326                         'register_date' => datetime_convert(),
327                         'default-location' => ''
328                 ]);
329
330                 if ($insert_result) {
331                         $uid = dba::lastInsertId();
332                         $user = dba::selectFirst('user', [], ['uid' => $uid]);
333                 } else {
334                         throw new Exception(t('An error occurred during registration. Please try again.'));
335                 }
336
337                 if (!$uid) {
338                         throw new Exception(t('An error occurred during registration. Please try again.'));
339                 }
340
341                 // if somebody clicked submit twice very quickly, they could end up with two accounts
342                 // due to race condition. Remove this one.
343                 $user_count = dba::count('user', ['nickname' => $nickname]);
344                 if ($user_count > 1) {
345                         dba::delete('user', ['uid' => $uid]);
346
347                         throw new Exception(t('Nickname is already registered. Please choose another.'));
348                 }
349
350                 $insert_result = dba::insert('profile', [
351                         'uid' => $uid,
352                         'name' => $username,
353                         'photo' => System::baseUrl() . "/photo/profile/{$uid}.jpg",
354                         'thumb' => System::baseUrl() . "/photo/avatar/{$uid}.jpg",
355                         'publish' => $publish,
356                         'is-default' => 1,
357                         'net-publish' => $netpublish,
358                         'profile-name' => t('default')
359                 ]);
360                 if (!$insert_result) {
361                         dba::delete('user', ['uid' => $uid]);
362
363                         throw new Exception(t('An error occurred creating your default profile. Please try again.'));
364                 }
365
366                 // Create the self contact
367                 if (!Contact::createSelfFromUserId($uid)) {
368                         dba::delete('user', ['uid' => $uid]);
369
370                         throw new Exception(t('An error occurred creating your self contact. Please try again.'));
371                 }
372
373                 // Create a group with no members. This allows somebody to use it
374                 // right away as a default group for new contacts.
375                 $def_gid = Group::create($uid, t('Friends'));
376                 if (!$def_gid) {
377                         dba::delete('user', ['uid' => $uid]);
378
379                         throw new Exception(t('An error occurred creating your default contact group. Please try again.'));
380                 }
381
382                 $fields = ['def_gid' => $def_gid];
383                 if (Config::get('system', 'newuser_private') && $def_gid) {
384                         $fields['allow_gid'] = '<' . $def_gid . '>';
385                 }
386
387                 dba::update('user', $fields, ['uid' => $uid]);
388
389                 // if we have no OpenID photo try to look up an avatar
390                 if (!strlen($photo)) {
391                         $photo = avatar_img($email);
392                 }
393
394                 // unless there is no avatar-plugin loaded
395                 if (strlen($photo)) {
396                         $photo_failure = false;
397
398                         $filename = basename($photo);
399                         $img_str = fetch_url($photo, true);
400                         // guess mimetype from headers or filename
401                         $type = Image::guessType($photo, true);
402
403                         $Image = new Image($img_str, $type);
404                         if ($Image->isValid()) {
405                                 $Image->scaleToSquare(175);
406
407                                 $hash = photo_new_resource();
408
409                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 4);
410
411                                 if ($r === false) {
412                                         $photo_failure = true;
413                                 }
414
415                                 $Image->scaleDown(80);
416
417                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 5);
418
419                                 if ($r === false) {
420                                         $photo_failure = true;
421                                 }
422
423                                 $Image->scaleDown(48);
424
425                                 $r = Photo::store($Image, $uid, 0, $hash, $filename, t('Profile Photos'), 6);
426
427                                 if ($r === false) {
428                                         $photo_failure = true;
429                                 }
430
431                                 if (!$photo_failure) {
432                                         dba::update('photo', ['profile' => 1], ['resource-id' => $hash]);
433                                 }
434                         }
435                 }
436
437                 Addon::callHooks('register_account', $uid);
438
439                 $return['user'] = $user;
440                 return $return;
441         }
442
443         /**
444          * @brief Sends pending registration confiƕmation email
445          *
446          * @param string $email
447          * @param string $sitename
448          * @param string $username
449          * @return NULL|boolean from notification() and email() inherited
450          */
451         public static function sendRegisterPendingEmail($email, $sitename, $username)
452         {
453                 $body = deindent(t('
454                         Dear %1$s,
455                                 Thank you for registering at %2$s. Your account is pending for approval by the administrator.
456                 '));
457
458                 $body = sprintf($body, $username, $sitename);
459
460                 return notification([
461                         'type' => SYSTEM_EMAIL,
462                         'to_email' => $email,
463                         'subject'=> sprintf( t('Registration at %s'), $sitename),
464                         'body' => $body]);
465         }
466
467         /**
468          * @brief Sends registration confirmation
469          *
470          * It's here as a function because the mail is sent from different parts
471          *
472          * @param string $email
473          * @param string $sitename
474          * @param string $siteurl
475          * @param string $username
476          * @param string $password
477          * @return NULL|boolean from notification() and email() inherited
478          */
479         public static function sendRegisterOpenEmail($email, $sitename, $siteurl, $username, $password)
480         {
481                 $preamble = deindent(t('
482                         Dear %1$s,
483                                 Thank you for registering at %2$s. Your account has been created.
484                 '));
485                 $body = deindent(t('
486                         The login details are as follows:
487                                 Site Location:  %3$s
488                                 Login Name:     %1$s
489                                 Password:       %5$s
490
491                         You may change your password from your account "Settings" page after logging
492                         in.
493
494                         Please take a few moments to review the other account settings on that page.
495
496                         You may also wish to add some basic information to your default profile
497                         (on the "Profiles" page) so that other people can easily find you.
498
499                         We recommend setting your full name, adding a profile photo,
500                         adding some profile "keywords" (very useful in making new friends) - and
501                         perhaps what country you live in; if you do not wish to be more specific
502                         than that.
503
504                         We fully respect your right to privacy, and none of these items are necessary.
505                         If you are new and do not know anybody here, they may help
506                         you to make some new and interesting friends.
507
508
509                         Thank you and welcome to %2$s.'));
510
511                 $preamble = sprintf($preamble, $username, $sitename);
512                 $body = sprintf($body, $email, $sitename, $siteurl, $username, $password);
513
514                 return notification([
515                         'type' => SYSTEM_EMAIL,
516                         'to_email' => $email,
517                         'subject'=> sprintf( t('Registration details for %s'), $sitename),
518                         'preamble'=> $preamble,
519                         'body' => $body]);
520         }
521
522         /**
523          * @param object $uid user to remove
524          * @return void
525          */
526         public static function remove($uid)
527         {
528                 if (!$uid) {
529                         return;
530                 }
531
532                 logger('Removing user: ' . $uid);
533
534                 $user = dba::selectFirst('user', [], ['uid' => $uid]);
535
536                 Addon::callHooks('remove_user', $user);
537
538                 // save username (actually the nickname as it is guaranteed
539                 // unique), so it cannot be re-registered in the future.
540                 dba::insert('userd', ['username' => $user['nickname']]);
541
542                 // The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
543                 dba::update('user', ['account_removed' => true, 'account_expires_on' => datetime_convert()], ['uid' => $uid]);
544                 Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
545
546                 // Send an update to the directory
547                 Worker::add(PRIORITY_LOW, "Directory", $user['url']);
548
549                 if ($uid == local_user()) {
550                         unset($_SESSION['authenticated']);
551                         unset($_SESSION['uid']);
552                         goaway(System::baseUrl());
553                 }
554         }
555 }