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