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