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