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