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