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