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