Passed
Pull Request — master (#1917)
by Struan
34:42
created

USER::init()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 59
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 5.002

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 47
nc 9
nop 1
dl 0
loc 59
ccs 22
cts 23
cp 0.9565
crap 5.002
rs 8.8452
c 2
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
5
NO HTML IN THIS FILE!!
6
7
This file contains USER and THEUSER classes.
8
9
It automatically instantiates a $THEUSER global object. This refers to the person actually viewing the site. If they have a valid cookie set, $THEUSER's data will be fetched from the DB and they will be logged in. Otherwise they will have a minimum access level and will not be logged in.
10
11
12
The USER class allows us to fetch and alter data about any user (rather than $THEUSER).
13
14
To create a new user do:
15
    $USER = new USER;
16
    $USER->init($user_id);
17
18
You can then access all the user's variables with appropriately named functions, such as:
19
    $USER->user_id();
20
    $USER->email();
21
etc. Don't access the variables directly because I think that's bad.
22
23
USER is extended into the THEUSER class which is used only for the person currently using the site. ie, it adds functions for logging in and out, checking log in status, etc.
24
25
GUESTUSER:
26
In the database there should be a user with an id of 0 and a status of 'Viewer' (and probably a name of 'Guest').
27
28
The cookie set to indicate a logged in user is called "epuser_id". More on that in THEUSER().
29
30
Functions here:
31
32
USER
33
    init()              Send it a user id to fetch data from DB.
34
    add()               Add a new user to the DB.
35
    send_confirmation_email()   Done after add()ing the user.
36
    update_other_user() Update the data of another user.
37
    change_password()   Generate a new password and put in DB.
38
    id_exists()         Checks if a user_id is valid.
39
    email_exists()      Checks if a user exists with a certain email address.
40
    is_able_to()        Is the user allowed to perform this action?
41
    possible_statuses() Return an array of the possible security statuses for users.
42
    Accessor functions for each object variable (eg, user_id()  ).
43
    _update()           Private function that updates a user's data in DB.
44
45
THEUSER
46
    THEUSER()           Constructor that logs in if the cookie is correct.
47
    isloggedin()        Check if the user is logged in or not.
48
    isvalid()           Check to see if the user's login form details are OK.
49
    login()             Log the user in.
50
    logout()            Log the user out.
51
    confirm()           With the correct token, confirms the user then logs them in.
52
    update_self()       Update the user's own data in the DB.
53
    check_user_access() Check a the user is allowed to view this page.
54
55
*/
56
57
class USER {
58
    public $user_id = "0";         // So we have an ID for non-logged in users reporting comments etc.
59
    public $firstname = "Guest";   // So we have something to print for non-logged in users.
60
    public $lastname = "";
61
    public $password = "";         // This will be a hashed version of a plaintext pw.
62
    public $email = "";
63
    public $postcode = "";
64
    public $url = "";
65
    public $lastvisit = "";        // Last time the logged-in user loaded a page (GMT).
66
    public $registrationtime = ""; // When they registered (GMT).
67
    public $registrationip = "";   // Where they registered from.
68
    public $optin = "";            // Int containing multiple binary opt-ins. (See top of User.php)
69
    public $deleted = "";          // User can't log in or have their info displayed.
70
    public $confirmed = '';        // boolean - Has the user confirmed via email?
71
    public $facebook_id = '';      // Facebook ID for users who login with FB
72
    public $facebook_token = '';   // Facebook token for users who login with FB
73
    public $can_annotate = false;  // Can the user add annotations
74
    public $organisation = '';     // The organisation the user belongs to
75
    // Don't use the status to check access privileges - use the is_able_to() function.
76
    public $status = "Viewer";
77
78
    // If you add more user variables above you should also:
79
    //      Add the approrprate code to $this->add()
80
    //      Add the appropriate code to $this->_update()
81
    //      Add accessor functions way down below...
82
    //      Alter THEUSER->update_self() to update with the new vars, if appropriate.
83
    //      Change things in the add/edit/view user page.
84 1
85 1
    public function __construct() {
86 1
        $this->db = new ParlDB();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
87
    }
88 4
89
    public function init($user_id) {
90
        // Pass it a user id and it will fetch the user's data from the db
91
        // and put it all in the appropriate variables.
92
        // Returns true if we've found user_id in the DB, false otherwise.
93
94 4
        // Look for this user_id's details.
95
        $q = $this->db->query(
96
            "SELECT firstname,
97
                                lastname,
98
                                password,
99
                                email,
100
                                postcode,
101
                                url,
102
                                lastvisit,
103
                                registrationtime,
104
                                registrationtoken,
105
                                registrationip,
106
                                optin,
107
                                status,
108
                                deleted,
109
                                confirmed,
110
                                facebook_id,
111
                                facebook_token,
112 4
                                can_annotate,
113
                                organisation
114
                        FROM    users
115 4
                        WHERE   user_id = :user_id",
116
            [':user_id' => $user_id]
117
        )->first();
118 4
119 4
120 4
        if ($q) {
121 4
            // We've got a user, so set them up.
122 4
123 4
            $this->user_id              = $user_id;
124 4
            $this->firstname            = $q["firstname"];
125 4
            $this->lastname             = $q["lastname"];
126 4
            $this->password             = $q["password"];
127 4
            $this->email                = $q["email"];
128 4
            $this->postcode             = $q["postcode"];
129 4
            $this->facebook_id          = $q["facebook_id"];
130 4
            $this->facebook_token       = $q["facebook_token"];
131 4
            $this->url                  = $q["url"];
132 4
            $this->lastvisit            = $q["lastvisit"];
133 4
            $this->registrationtoken    = $q['registrationtoken'];
0 ignored issues
show
Bug Best Practice introduced by
The property registrationtoken does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
134 4
            $this->registrationtime     = $q["registrationtime"];
135
            $this->registrationip       = $q["registrationip"];
136 4
            $this->optin                = $q["optin"];
137
            $this->status               = $q["status"];
138
            $this->organisation         = $q["organisation"];
139
            $this->deleted = $q["deleted"] == 1 ? true : false;
140
            $this->confirmed = $q["confirmed"] == 1 ? true : false;
141
            $this->can_annotate = $q["can_annotate"] == 1 ? true : false;
142
143
            return true;
144
145 1
        } else {
146
            return false;
147
            twfy_debug("USER", "There is no user with an id of '" . _htmlentities($user_id) . "'");
0 ignored issues
show
Unused Code introduced by
twfy_debug('USER', 'Ther...tities($user_id) . ''') is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
148
        }
149
150
    }
151
152
    public function add($details, $confirmation_required = true) {
153
        // Adds a new user's info into the db.
154
        // Then optionally (and usually) calls another function to
155
        // send them a confirmation email.
156
157
        // $details is an associative array of all the user's details, of the form:
158 1
        // array (
159
        //      "firstname" => "Fred",
160 1
        //      "lastname"  => "Bloggs",
161
        //      etc... using the same keys as the object variable names.
162 1
        // )
163
        // The BOOL variables (eg, optin) will be true or false and will need to be
164 1
        // converted to 1/0 for MySQL.
165
        global $REMOTE_ADDR;
166
167
        $registrationtime = gmdate("YmdHis");
168 1
169 1
        $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
170
171
        if (!isset($details["status"])) {
172 1
            $details["status"] = "User";
173
        }
174
175
        if (!isset($details["facebook_id"])) {
176
            $details["facebook_id"] = "";
177
        }
178
179
        $q = $this->db->query("INSERT INTO users (
180
                firstname,
181
                lastname,
182
                email,
183
                postcode,
184
                url,
185
                password,
186
                optin,
187
                status,
188
                registrationtime,
189
                registrationip,
190
                facebook_id,
191
                deleted
192
            ) VALUES (
193
                :firstname,
194
                :lastname,
195
                :email,
196
                :postcode,
197
                :url,
198
                :password,
199
                :optin,
200 1
                :status,
201 1
                :registrationtime,
202 1
                :registrationip,
203 1
                :facebook_id,
204 1
                '0'
205 1
            )
206 1
        ", [
207 1
            ':firstname' => $details["firstname"],
208 1
            ':lastname' => $details["lastname"],
209 1
            ':email' => $details["email"],
210 1
            ':postcode' => $details["postcode"],
211
            ':url' => $details["url"],
212
            ':password' => $passwordforDB,
213 1
            ':optin' => $details["optin"],
214
            ':status' => $details["status"],
215
            ':registrationtime' => $registrationtime,
216
            ':facebook_id' => $details["facebook_id"],
217 1
            ':registrationip' => $REMOTE_ADDR,
218 1
        ]);
219 1
220
        if ($q->success()) {
221
            // Set these so we can log in.
222
            // Except we no longer automatically log new users in, we
223
            // send them an email. So this may not be required.
224
            $this->user_id = $q->insert_id();
225 1
            $this->password = $passwordforDB;
226
            $this->facebook_id = $details["facebook_id"];
227
228
            // We have to set the user's registration token.
229
            // This will be sent to them via email, so we can confirm they exist.
230
            // The token will be the first 16 characters of a hash.
231 1
232 1
            $token = substr(password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16);
233
234
            // Full stops don't work well at the end of URLs in emails, so
235 1
            // replace them. And double slash would be treated as single and
236
            // not work either. We won't be doing anything clever with the hash
237
            // stuff, just need to match this token.
238
            $token = strtr($token, './', 'Xx');
239 1
            $this->registrationtoken = $token;
0 ignored issues
show
Bug Best Practice introduced by
The property registrationtoken does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
240 1
241
            // Add that to the DB.
242
            $r = $this->db->query("UPDATE users
243 1
                            SET registrationtoken = :registrationtoken
244
                            WHERE   user_id = :user_id
245
                            ", [
246 1
                ':registrationtoken' => $this->registrationtoken,
247
                ':user_id' => $this->user_id,
248
            ]);
249
250
            if ($r->success()) {
251
                // Updated DB OK.
252
253
                if ($details['mp_alert'] && $details['postcode']) {
254
                    $MEMBER = new MEMBER(['postcode' => $details['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
255
                    $pid = $MEMBER->person_id();
256
                    # No confirmation email, but don't automatically confirm
257
                    $ALERT = new ALERT();
258 1
                    $ALERT->add([
259
                        'email' => $details['email'],
260
                        'pid' => $pid,
261
                        'pc' => $details['postcode'],
262
                    ], false, false);
263
                }
264
265
                if ($confirmation_required) {
266
                    // Right, send the email...
267
                    $success = $this->send_confirmation_email($details);
268
269
                    if ($success) {
270
                        // All is good in the world!
271 1
                        return true;
272
                    } else {
273
                        // Couldn't send the email.
274
                        return false;
275
                    }
276
                } else {
277
                    // No confirmation email needed.
278
                    return true;
279
                }
280
            } else {
281
                // Couldn't add the registration token to the DB.
282
                return false;
283
            }
284
285
        } else {
286
            // Couldn't add the user's data to the DB.
287
            return false;
288
        }
289
    }
290
291
    public function add_facebook_id($facebook_id) {
292
        $q = $this->db->query(
293
            "UPDATE users SET facebook_id = :facebook_id WHERE email = :email",
294
            [
295
                ':facebook_id' => $facebook_id,
296
                ':email' => $this->email,
297
            ]
298
        );
299
300
        if ($q->success()) {
301
            $this->facebook_id = $facebook_id;
302
303
            return $facebook_id;
304
        } else {
305
            return false;
306
        }
307
    }
308
309
    public function send_email_confirmation_email($details) {
310
        // A brief check of the facts...
311
        if (!is_numeric($this->user_id) ||
312
            !isset($details['email']) ||
313
            $details['email'] == '' ||
314
            !isset($details['token']) ||
315
            $details['token'] == '') {
316
            return false;
317
        }
318
319
        // We prefix the registration token with the user's id and '-'.
320
        // Not for any particularly good reason, but we do.
321
322
        $urltoken = $this->user_id . '-' . $details['token'];
323
324
        $confirmurl = 'https://' . DOMAIN . '/E/' . $urltoken;
325
326
        // Arrays we need to send a templated email.
327
        $data =  [
328
            'to'        => $details['email'],
329
            'template'  => 'email_confirmation',
330
        ];
331
332
        $merge =  [
333
            'CONFIRMURL'    => $confirmurl,
334
        ];
335
336
        $success = send_template_email($data, $merge);
337
338
        if ($success) {
339
            return true;
340
        } else {
341
            return false;
342
        }
343
    }
344
345
    public function send_confirmation_email($details) {
346
        // After we've add()ed a user we'll probably be sending them
347
        // a confirmation email with a link to confirm their address.
348
349
        // $details is the array we just sent to add(), and which it's
350
        // passed on to us here.
351
352
        // A brief check of the facts...
353
        if (!is_numeric($this->user_id) ||
354
            !isset($details['email']) ||
355
            $details['email'] == '') {
356
            return false;
357
        }
358
359
        // We prefix the registration token with the user's id and '-'.
360
        // Not for any particularly good reason, but we do.
361
362
        $urltoken = $this->user_id . '-' . $this->registrationtoken;
363
364
        $confirmurl = 'https://' . DOMAIN . '/U/' . $urltoken;
365
        if (isset($details['ret'])) {
366
            $confirmurl .= '?ret=' . $details['ret'];
367
        }
368
369
        // Arrays we need to send a templated email.
370
        $data =  [
371
            'to'        => $details['email'],
372
            'template'  => 'join_confirmation',
373
        ];
374
375
        $merge =  [
376
            'CONFIRMURL'    => $confirmurl,
377
        ];
378
379
        $success = send_template_email($data, $merge);
380
381
        if ($success) {
382
            return true;
383
        } else {
384
            return false;
385
        }
386
    }
387
388
389
    public function update_other_user($details) {
390
        // If someone (like an admin) is updating another user, call this
391
        // function. It checks their privileges before letting them.
392
393
        // $details is an array like that in $this->add().
394
        // It must include a 'user_id' element!
395
396
        global $THEUSER;
397
398
        if (!isset($details["user_id"])) {
399
            return false;
400
401
        } elseif ($THEUSER->is_able_to("edituser")) {
402
403
            // If the user doing the updating has appropriate privileges...
404
405
            $newdetails = $this->_update($details);
406
407
            // $newdetails will be an array of details if all went well,
408
            // false otherwise.
409
            if ($newdetails) {
410
                return true;
411
            } else {
412
                return false;
413
            }
414
415
        } else {
416
            return false;
417
418
        }
419
    }
420
421
422
423
    public function change_password($email) {
424
425
        // This function is called from the Change Password page.
426
        // It will create a new password for the user with $email address.
427
        // If all goes OK it will return the plaintext version of the password.
428
        // Otherwise it returns false.
429
430
        if ($this->email_exists($email)) {
431
432
            $this->email = $email;
433
            for (;;) {
434
435
                $pwd = null;
436
                $o = null;
437
438
                // Generates the password ....
439
                for ($x = 0; $x < 6;) {
440
                    $y = rand(1, 1000);
441
                    if($y > 350 && $y < 601) {
442
                        $d = chr(rand(48, 57));
443
                    }
444
                    if($y < 351) {
445
                        $d = chr(rand(65, 90));
446
                    }
447
                    if($y > 600) {
448
                        $d = chr(rand(97, 122));
449
                    }
450
                    if ($d != $o && !preg_match('#[O01lI]#', $d)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $d does not seem to be defined for all execution paths leading up to this point.
Loading history...
451
                        $o = $d;
452
                        $pwd .= $d;
453
                        $x++;
454
                    }
455
                }
456
457
                // If the PW fits your purpose (e.g. this regexpression) return it, else make a new one
458
                // (You can change this regular-expression how you want ....)
459
                if (preg_match("/^[a-zA-Z]{1}([a-zA-Z]+[0-9][a-zA-Z]+)+/", $pwd)) {
0 ignored issues
show
Bug introduced by
It seems like $pwd can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

459
                if (preg_match("/^[a-zA-Z]{1}([a-zA-Z]+[0-9][a-zA-Z]+)+/", /** @scrutinizer ignore-type */ $pwd)) {
Loading history...
460
                    break;
461
                }
462
463
            }
464
            $pwd = strtoupper($pwd);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pwd does not seem to be defined for all execution paths leading up to this point.
Loading history...
465
466
            // End password generating stuff.
467
468
        } else {
469
470
            // Email didn't exist.
471
            return false;
472
473
        }
474
475
        $passwordforDB = password_hash($pwd, PASSWORD_BCRYPT);
476
477
        $q = $this->db->query(
478
            "UPDATE users SET password = :password WHERE email = :email",
479
            [
480
                ':password' => $passwordforDB,
481
                ':email' => $email,
482
            ]
483
        );
484
485
        if ($q->success()) {
486
            $this->password = $pwd;
487
488
            return $pwd;
489
490
        } else {
491
            return false;
492
        }
493
494
    }
495
496
    public function send_password_reminder() {
497
        global $PAGE;
498
499
        // You'll probably have just called $this->change_password().
500
501
        if ($this->email() == '') {
502
            $PAGE->error_message("No email set for this user, so can't send a password reminder.");
503
504
            return false;
505
        }
506
507
        $data =  [
508
            'to'            => $this->email(),
509
            'template'      => 'new_password',
510
        ];
511
512
        $URL = new \MySociety\TheyWorkForYou\Url("userlogin");
513
514
        $merge =  [
515
            'EMAIL'         => $this->email(),
516
            'LOGINURL'      => "https://" . DOMAIN . $URL->generate(),
517
            'PASSWORD'      => $this->password(),
518
        ];
519
520
        // send_template_email in utility.php.
521
        $success = send_template_email($data, $merge);
522
523
        return $success;
524
525
    }
526
527
528
529 1
530
    public function id_exists($user_id) {
531
        // Returns true if there's a user with this user_id.
532 1
533 1
        if (is_numeric($user_id)) {
534 1
            $q = $this->db->query(
535
                "SELECT user_id FROM users WHERE user_id = :user_id",
536
                [':user_id' => $user_id]
537
            );
538
            if ($q->rows() > 0) {
539
                return true;
540 1
            } else {
541
                return false;
542
            }
543
        } else {
544
            return false;
545
        }
546
547
    }
548
549
550
    public function email_exists($email, $return_id = false) {
551
        // Returns true if there's a user with this email address.
552
553
        if ($email != "") {
554
            $q = $this->db->query("SELECT user_id FROM users WHERE email = :email", [':email' => $email])->first();
555
            if ($q) {
556
                if ($return_id) {
557
                    return $q['user_id'];
558
                }
559
                return true;
560
            } else {
561
                return false;
562
            }
563
        } else {
564
            return false;
565
        }
566
    }
567
568
    public function facebook_id_exists($id, $return_id = false) {
569
        // Returns true if there's a user with this facebook id.
570
571
        if ($id != "") {
572
            $q = $this->db->query("SELECT user_id FROM users WHERE facebook_id = :id", [':id' => $id])->first();
573
            if ($q) {
574
                if ($return_id) {
575
                    return $q['user_id'];
576
                }
577
                return true;
578
            } else {
579
                return false;
580
            }
581
        } else {
582
            return false;
583
        }
584
    }
585
586
    public function is_able_to($action) {
587
        // Call this function to find out if a user is allowed to do something.
588
        // It uses the user's status to return true or false.
589
        // Possible actions:
590
        //  "addcomment"
591
        //  "reportcomment"
592
        //  "edituser"
593
        global $PAGE;
594
595
        $status = $this->status();
596
597
        switch ($action) {
598
599
            // You can add more below as they're needed...
600
            // But keep them in alphabetical order!
601
602
            case "addcomment":  // Post comments.
603
604
                switch ($status) {
605
                    case "User":            return true;
606
                    case "Moderator":       return true;
607
                    case "Administrator":   return true;
608
                    case "Superuser":       return true;
609
                    default: /* Viewer */   return false;
610
                }
611
612
            case "deletecomment": // Delete comments.
613
614
                switch ($status) {
615
                    case "User":            return false;
616
                    case "Moderator":       return true;
617
                    case "Administrator":   return true;
618
                    case "Superuser":       return true;
619
                    default: /* Viewer */   return false;
620
                }
621
622
                // no break
623
            case "edituser":
624
625
                switch ($status) {
626
                    case "User":            return false;
627
                    case "Moderator":       return false;
628
                    case "Administrator":   return false;
629
                    case "Superuser":       return true;
630
                    default: /* Viewer */   return false;
631
                }
632
633
                // no break
634
            case "reportcomment":   // Report a comment for moderation.
635
636
                switch ($status) {
637
                    case "User":            return true;
638
                    case "Moderator":       return true;
639
                    case "Administrator":   return true;
640
                    case "Superuser":       return true;
641
                    default: /* Viewer */   return true;
642
                }
643
644
                // no break
645
            case "viewadminsection":    // Access pages in the Admin section.
646
647
                switch ($status) {
648
                    case "User":            return false;
649
                    case "Moderator":       return false;
650
                    case "Administrator":   return true;
651
                    case "Superuser":       return true;
652
                    default: /* Viewer */   return false;
653
                }
654
655
                // no break
656
            case "voteonhansard":   // Rate hansard things interesting/not.
657
                /* Everyone */              return true;
658
659
            case "annotate":
660
                if ($this->can_annotate) {
661
                    return true;
662
                }
663
                switch ($status) {
664
                    case "User":            return false;
665
                    case "Moderator":       return true;
666
                    case "Administrator":   return true;
667
                    case "Superuser":       return true;
668
                    default: /* Viewer */   return false;
669
                }
670
671
            default:
672
                $PAGE->error_message("You need to set permissions for '$action'!");
673
674
                return false;
675
676
677 40
        }
678
679
680 40
681
    }
682
683 40
    // Same for every user...
684
    // Just returns an array of the possible statuses a user could have.
685
    // Handy for forms where you edit/view users etc.
686
    public function possible_statuses() {
687
        // Maybe there's a way of fetching these from the DB,
688
        // so we don't duplicate them here...?
689
690 2
        $statuses =  ["Viewer", "User", "Moderator", "Administrator", "Superuser"];
691
692
        return $statuses;
693
694
    }
695
696 2
697
698
    // Functions for accessing the user's variables.
699 2
700 1
    public function user_id() {
701
        return $this->user_id;
702 1
    }
703 1
    public function firstname() {
704
        return $this->firstname;
705
    }
706
    public function lastname() {
707
        return $this->lastname;
708
    }
709 2
    public function password() {
710 2
        return $this->password;
711 2
    }
712 2
    public function email() {
713 2
        return $this->email;
714
    }
715 2
    public function postcode() {
716
        return $this->postcode;
717 2
    }
718
    public function url() {
719
        return $this->url;
720
    }
721
    public function lastvisit() {
722
        return $this->lastvisit;
723
    }
724
    public function facebook_id() {
725
        return $this->facebook_id;
726
    }
727
    public function facebook_token() {
728 2
        return $this->facebook_token;
729
    }
730
    public function facebook_user() {
731
        return $this->facebook_user;
732
    }
733
734
    public function registrationtime() {
735
        return $this->registrationtime;
736
    }
737
    public function registrationip() {
738
        return $this->registrationip;
739
    }
740
    public function optin() {
741 2
        return $this->optin;
742
    }
743
    // Don't use the status to check access privileges - use the is_able_to() function.
744
    // But you might use status() to return text to display, describing a user.
745
    // We can then change what status() does in the future if our permissions system
746
    // changes.
747
    public function status() {
748
        return $this->status;
749
    }
750
    public function deleted() {
751
        return $this->deleted;
752
    }
753
    public function confirmed() {
754 2
        return $this->confirmed;
755
    }
756
757
    public function can_annotate() {
758
        return $this->can_annotate;
759
    }
760
761
    public function organisation() {
762 2
        return $this->organisation;
763 1
    }
764 1
765
    public function postcode_is_set() {
766
        // So we can tell if the, er, postcode is set or not.
767 2
        // Could maybe put some validation in here at some point.
768
        if ($this->postcode != '') {
769
            return true;
770
        } else {
771
            return false;
772 2
        }
773 2
    }
774 2
775 2
776 2
    /////////// PRIVATE FUNCTIONS BELOW... ////////////////
777
778
    public function _update($details) {
779 2
        // Update a user's info.
780 2
        // DO NOT call this function direct.
781 2
        // Call either $this->update_other_user() or $this->update_self().
782 2
783 2
        // $details is an array like that in $this->add().
784 2
        global $PAGE;
785 2
786
        // Update email alerts if email address changed
787
        if (isset($details['email']) && $this->email != $details['email']) {
788
            $this->db->query(
789
                'UPDATE alerts SET email = :details_email WHERE email = :email',
790
                [
791 2
                    ':details_email' => $details['email'],
792 2
                    ':email' => $this->email,
793
                ]
794
            );
795
        }
796
797
        // These are used to put optional fragments of SQL in, depending
798
        // on whether we're changing those things or not.
799
        $passwordsql = "";
800
        $deletedsql = "";
801
        $confirmedsql = "";
802
        $statussql = "";
803
        $emailsql = '';
804
        $annotatesql = '';
805
806
        $params = [];
807
808
        if (isset($details["password"]) && $details["password"] != "") {
809
            // The password is being updated.
810
            // If not, the password fields on the form will be left blank
811
            // so we don't want to overwrite the user's pw in the DB!
812
813
            $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
814
815
            $passwordsql = "password = :password, ";
816
            $params[':password'] = $passwordforDB;
817
        }
818
819
        if (isset($details["deleted"])) {
820
            // 'deleted' won't always be an option (ie, if the user is updating
821
            // their own info).
822
            if ($details['deleted'] == true) {
823
                $del = '1';
824 3
            } elseif ($details['deleted'] == false) {
825
                $del = '0';
826
            }
827
            if (isset($del)) {
828 3
                $deletedsql = "deleted  = '$del', ";
829
            }
830
        }
831
832
        if (isset($details["confirmed"])) {
833
            // 'confirmed' won't always be an option (ie, if the user is updating
834
            // their own info).
835
            if ($details['confirmed'] == true) {
836
                $con = '1';
837
            } elseif ($details['confirmed'] == false) {
838
                $con = '0';
839
            }
840 3
            if (isset($con)) {
841
                $confirmedsql = "confirmed  = '$con', ";
842 3
            }
843
        }
844
845
        if (isset($details["status"]) && $details["status"] != "") {
846
            // 'status' won't always be an option (ie, if the user is updating
847
            // their own info.
848
            $statussql = "status = :status, ";
849
            $params[':status'] = $details['status'];
850 3
851
        }
852
853
        if (isset($details["can_annotate"])) {
854 3
            $annotatesql = "can_annotate = :can_annotate, organisation = :organisation, ";
855
            $params[':can_annotate'] = $details['can_annotate'] ? 1 : 0;
856 3
            $params[':organisation'] = $details['organisation'];
857
        }
858 3
859
        if (isset($details['email']) && $details['email']) {
860 3
            $emailsql = "email = :email, ";
861
            $params[':email'] = $details['email'];
862
        }
863
864
        $q = $this->db->query("UPDATE users
865
                        SET     firstname   = :firstname,
866 3
                                lastname    = :lastname,
867
                                postcode    = :postcode,
868
                                url         = :url,"
869
                                . $passwordsql
870
                                . $deletedsql
871
                                . $confirmedsql
872
                                . $emailsql
873
                                . $statussql
874
                                . $annotatesql . "
875
                                optin       = :optin
876
                        WHERE   user_id     = :user_id
877
                        ", array_merge($params, [
878 3
                                    ':firstname' => $details['firstname'],
879
                                    ':lastname' => $details['lastname'],
880
                                    ':postcode' => $details['postcode'],
881
                                    ':url' => $details['url'],
882
                                    ':optin' => $details['optin'],
883
                                    ':user_id' => $details['user_id'],
884
                                ]));
885
886
        // If we're returning to
887
        // $this->update_self() then $THEUSER will have its variables
888
        // updated if everything went well.
889
        if ($q->success()) {
890 3
            return $details;
891 3
892 3
        } else {
893
            $PAGE->error_message("Sorry, we were unable to update user id '" . _htmlentities($details["user_id"]) . "'");
894
895 3
            return false;
896
        }
897
898
899
    }
900
901 3
902
903
904
905
} // End USER class
906 3
907
908
909
910
911
912
class THEUSER extends USER {
913
    // Handles all the login/out functionality and checking for the user
914
    // who is using the site right NOW. Yes, him, over there.
915
916
    // This will become true if all goes well...
917
    public $loggedin = false;
918 3
    public $facebook_user = false;
919 3
920
921
    public function __construct() {
922
        // This function is run automatically when a THEUSER
923
        // object is instantiated.
924
925
        $this->db = new ParlDB();
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
926 3
927
        // We look at the user's cookie and see if it's valid.
928 3
        // If so, we're going to log them in.
929
930 3
        // A user's cookie is of the form:
931
        // 123.blahblahblah
932 3
        // Where '123' is a user id, and 'blahblahblah' is an md5 hash of the
933
        // encrypted password we've stored in the db.
934
        // (Maybe we could just put the encrypted pw in the cookie and md5ing
935
        // it is overkill? Whatever, it works.)
936
937
        $cookie = get_cookie_var("epuser_id"); // In includes/utility.php.
938
939
        if ($cookie == '') {
940 3
            $cookie = get_cookie_var("facebook_id");
941
            if ($cookie != '') {
942
                $this->facebook_user = true;
943
                twfy_debug("THEUSER", "is facebook login");
944
            }
945
        }
946
947
        if ($cookie == '') {
948 7
            twfy_debug("THEUSER init FAILED", "No cookie set");
949
            $this->loggedin = false;
950
951 7
        } elseif (preg_match("/([[:alnum:]]*)\.([[:alnum:]]*)/", $cookie, $matches)) {
952 2
953
            if (is_numeric($matches[1])) {
954 2
955
                $success = $this->init($matches[1]);
956 7
957
                if ($success) {
958 7
                    // We got all the user's data from the DB.
959
960
                    // But we need to check the password before we log them in.
961
                    // And make sure the user hasn't been "deleted".
962
963
                    if ($this->facebook_user) {
964
                        if (md5($this->facebook_token()) == $matches[2] && $this->deleted() == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
965
                            twfy_debug("THEUSER", "init SUCCESS: setting as logged in");
966
                            $this->loggedin = true;
967
                        } elseif (md5($this->facebook_token()) != $matches[2]) {
968
                            twfy_debug("THEUSER", "init FAILED: Facebook token doesn't match cookie");
969
                            $this->loggedin = false;
970
                        } else {
971
                            twfy_debug("THEUSER", "init FAILED: User is deleted");
972
                            $this->loggedin = false;
973
                        }
974
                    } else {
975
                        if (md5($this->password()) == $matches[2] && $this->deleted() == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
976
                            // The correct password is in the cookie,
977
                            // and the user isn't deleted, so set the user to be logged in.
978
979
                            // This would be an appropriate place to call other functions
980
                            // that might set user info that only a logged-in user is going
981
                            // to need. Their preferences and saved things or something.
982
983
984
                            twfy_debug("THEUSER init SUCCEEDED", "setting as logged in");
985
                            $this->loggedin = true;
986
987
                        } elseif (md5($this->password()) != $matches[2]) {
988
                            twfy_debug("THEUSER init FAILED", "Password doesn't match cookie");
989
                            $this->loggedin = false;
990
                        } else {
991
                            twfy_debug("THEUSER init FAILED", "User is deleted");
992
                            $this->loggedin = false;
993
                        }
994
                    }
995
996
                } else {
997
                    twfy_debug("THEUSER init FAILED", "didn't get 1 row from db");
998
                    $this->loggedin = false;
999
                }
1000
1001
            } else {
1002
                twfy_debug("THEUSER init FAILED", "cookie's user_id is not numeric");
1003 4
                $this->loggedin = false;
1004 4
            }
1005 4
1006
        } else {
1007
            twfy_debug("THEUSER init FAILED", "cookie is not of the correct form");
1008 4
            $this->loggedin = false;
1009
        }
1010
1011
        // If a user is logged in they *might* have set their own postcode.
1012
        // If they aren't logged in, or they haven't set one, then we may
1013
        // have set a postcode for them when they searched for their MP.
1014
        // If so, we'll use that as $this->postcode.
1015
        if ($this->postcode == '') {
1016
            if (get_cookie_var(POSTCODE_COOKIE) != '') {
1017
                $pc = get_cookie_var(POSTCODE_COOKIE);
1018
1019
                $this->set_postcode_cookie($pc);
1020
            }
1021
        }
1022
1023
        $this->update_lastvisit();
1024
1025
    } // End THEUSER()
1026
1027
    public function update_lastvisit() {
1028
1029
        if ($this->isloggedin()) {
1030
            // Set last_visit to now.
1031
            $date_now = gmdate("Y-m-d H:i:s");
1032
            $this->db->query(
1033
                "UPDATE users SET lastvisit = :lastvisit WHERE user_id = :user_id",
1034
                [ ':lastvisit' => $date_now, ':user_id' => $this->user_id() ]
1035
            );
1036
1037
            $this->lastvisit = $date_now;
1038
        }
1039
    }
1040
1041
    // For completeness, but it's better to call $this->isloggedin()
1042
    // if you want to check the log in status.
1043
    public function loggedin() {
1044
        return $this->loggedin;
1045
    }
1046
1047
1048
1049
    public function isloggedin() {
1050
        // Call this function to check if the user is successfully logged in.
1051
1052
        if ($this->loggedin()) {
1053
            twfy_debug("THEUSER", "isloggedin: true");
1054
1055
            return true;
1056
        } else {
1057
            twfy_debug("THEUSER", "isloggedin: false");
1058
1059
            return false;
1060
        }
1061
    }
1062
1063
1064
    public function isvalid($email, $userenteredpassword) {
1065
        // Returns true if this email and plaintext password match a user in the db.
1066
        // If false returns an array of form error messages.
1067
1068
        // We use this on the log in page to check if the details the user entered
1069
        // are correct. We can then continue with logging the user in (taking into
1070
        // account their cookie remembering settings etc) with $this->login().
1071
1072
        // This error string is shared between both email and password errors to
1073
        // prevent leaking of account existence.
1074
1075
        $error_string = 'There is no user registered with an email of ' . _htmlentities($email) . ', or the given password is incorrect. If you are subscribed to email alerts, you are not necessarily registered on the website. If you register, you will be able to manage your email alerts, as well as leave annotations.';
1076
1077
        $q = $this->db->query("SELECT user_id, password, deleted, confirmed FROM users WHERE email = :email", [':email' => $email])->first();
1078
1079
        if ($q) {
1080
            // OK.
1081
            $dbpassword = $q["password"];
1082
            if (password_verify($userenteredpassword, $dbpassword)) {
1083
                $this->user_id  = $q["user_id"];
1084
                $this->password = $dbpassword;
1085
                // We'll need these when we're going to log in.
1086
                $this->deleted  = $q["deleted"] == 1 ? true : false;
1087
                $this->confirmed = $q["confirmed"] == 1 ? true : false;
1088
1089
                return true;
1090
1091
            } else {
1092
                // Failed.
1093
                return  ["invalidemail" => $error_string];
1094
1095
            }
1096
1097
        } else {
1098
            // Failed.
1099
            return  ["invalidemail" => $error_string];
1100
        }
1101
1102
    }
1103
1104
    public function has_postcode() {
1105
        $has_postcode = false;
1106
        if ($this->isloggedin() && $this->postcode() != '' || $this->postcode_is_set()) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->isloggedin() && ...this->postcode_is_set(), Probably Intended Meaning: $this->isloggedin() && (...his->postcode_is_set())
Loading history...
1107
            $has_postcode = true;
1108
        }
1109
        return $has_postcode;
1110
    }
1111
1112
1113
    public function facebook_login($returl, $expire, $accessToken) {
1114
        global $PAGE;
1115
1116
        twfy_debug("THEUSER", "Faceook login, user_id " . $this->user_id);
1117
        twfy_debug("THEUSER", "Faceook login, facebook_id " . $this->facebook_id);
1118
        twfy_debug("THEUSER", "Faceook login, email" . $this->email);
1119
        if ($this->facebook_id() == "") {
1120
            $PAGE->error_message("We don't have a facebook id for this user.", true);
1121
1122
            return;
1123
        }
1124
1125
        twfy_debug("THEUSER", "Faceook login, facebook_token: " . $accessToken);
1126
1127
        $q = $this->db->query(
1128
            "UPDATE users SET facebook_token = :token WHERE email = :email",
1129
            [
1130
                ':token' => $accessToken,
1131
                ':email' => $this->email,
1132
            ]
1133
        );
1134
1135
        if (!$q->success()) {
1136
            $PAGE->error_message("There was a problem logging you in", true);
1137
            twfy_debug("THEUSER", "Faceook login, failed to set accessToken");
1138
1139
            return false;
1140 2
        }
1141 2
1142 2
        // facebook login users probably don't have a password
1143 2
        $cookie = $this->user_id() . "." . md5($accessToken);
1144 2
        twfy_debug("THEUSER", "Faceook login, cookie: " . $cookie);
1145
1146 2
        twfy_debug("USER", "logging in user from facebook " . $this->user_id);
1147
1148
        $this->loggedin = true;
1149 2
        $this->_login($returl, $expire, $cookie, 'facebook_id');
1150
        return true;
1151
    }
1152
1153 2
    public function login($returl, $expire) {
1154
1155 2
        // This is used to log the user in. Duh.
1156 2
        // You should already have checked the user's email and password using
1157 2
        // $this->isvalid()
1158 2
        // That will have set $this->user_id and $this->password, allowing the
1159 1
        // login to proceed...
1160 1
1161
        // $expire is either 'session' or 'never' - for the cookie.
1162
1163
        // $returl is the URL to redirect the user to after log in, generally the
1164 1
        // page they were on before. But if it doesn't exist, they'll just go to
1165
        // the front page.
1166
        global $PAGE;
1167 1
1168
        if ($returl == "") {
1169
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1170 1
            $returl = $URL->generate();
1171
        }
1172
1173
        // Various checks about the user - if they fail, we exit.
1174
        if ($this->user_id() == "" || $this->password == "") {
1175
            $PAGE->error_message("We don't have the user_id or password to make the cookie.", true);
1176 1
1177
            return;
1178
        } elseif ($this->deleted) {
1179
            $PAGE->error_message("This user has been deleted.", true);
1180
1181 1
            return;
1182 1
        } elseif (!$this->confirmed) {
1183 1
            $PAGE->error_message("You have not yet confirmed your account by clicking the link in the confirmation email we sent to you. If you don't have the email, you can <a href='/user/login/?resend=" . $this->user_id() . "'>have it resent</a>. If it still doesn't arrive, get in touch.", true);
1184 1
1185 1
            return;
1186 1
        }
1187 1
1188
        // Reminder: $this->password is actually a hashed version of the plaintext pw.
1189 1
        $cookie = $this->user_id() . "." . md5($this->password());
1190
1191 1
        $this->_login($returl, $expire, $cookie);
1192
    }
1193 1
1194
    private function _login($returl, $expire, $cookie, $cookie_name = 'epuser_id') {
1195
        // Unset any existing postcode cookie.
1196
        // This will be the postcode the user set for themselves as a non-logged-in
1197 1
        // user. We don't want it hanging around as it causes confusion.
1198
        $this->unset_postcode_cookie();
1199 1
1200
        twfy_debug("THEUSER", "expire is " . $expire);
1201
1202 1
        $cookie_expires = 0;
1203 1
        if ($expire == 'never') {
1204 1
            twfy_debug("THEUSER", "cookie never expires");
1205
            $cookie_expires = time() + 86400 * 365 * 20;
1206
        } elseif (is_int($expire) && $expire > time()) {
1207 1
            twfy_debug("THEUSER", "cookie expires at " . $expire);
1208 1
            $cookie_expires = $expire;
1209 1
        } else {
1210 1
            twfy_debug("THEUSER", "cookie expires with session");
1211
        }
1212
1213 1
        header("Location: $returl");
1214
        setcookie($cookie_name, $cookie, $cookie_expires, '/', COOKIEDOMAIN);
1215
    }
1216
1217
1218
    public function logout($returl) {
1219
1220
        // $returl is the URL to redirect the user to after log in, generally the
1221
        // page they were on before. But if it doesn't exist, they'll just go to
1222
        // the front page.
1223
1224
        if ($returl == '') {
1225
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1226
            $returl = $URL->generate();
1227
        }
1228
1229
        // get_cookie_var() is in includes/utility.php
1230
        if (get_cookie_var("epuser_id") != "") {
1231
            // They're logged in, so set the cookie to empty.
1232
            header("Location: $returl");
1233
            setcookie('epuser_id', '', time() - 86400, '/', COOKIEDOMAIN);
1234
        }
1235
1236
        if (get_cookie_var("facebook_id") != "") {
1237
            // They're logged in, so set the cookie to empty.
1238
            header("Location: $returl");
1239
            setcookie('facebook_id', '', time() - 86400, '/', COOKIEDOMAIN);
1240
        }
1241
    }
1242
1243
    public function confirm_email($token, $redirect = true) {
1244
        $arg = '';
1245
        if (strstr($token, '::')) {
1246
            $arg = '::';
1247
        }
1248
        if (strstr($token, '-')) {
1249
            $arg = '-';
1250
        }
1251
        [$user_id, $registrationtoken] = explode($arg, $token);
1252
1253
        if (!is_numeric($user_id) || $registrationtoken == '') {
1254
            return false;
1255
        }
1256
        $q = $this->db->query("SELECT expires, data
1257
            FROM    tokens
1258
            WHERE   token = :token
1259
            AND   type = 'E'
1260
        ", [':token' => $registrationtoken])->first();
1261
1262
        if ($q) {
1263
            $expires = $q['expires'];
1264
            $expire_time = strtotime($expires);
1265
            if ($expire_time < time()) {
1266
                global $PAGE;
1267
                if ($PAGE && $redirect) {
1268
                    $PAGE->error_message("Sorry, that token seems to have expired");
1269
                }
1270
1271
                return false;
1272
            }
1273
1274
            [$user_id, $email] = explode('::', $q['data']);
1275
1276
            // if we are logged in as someone else don't change the email
1277
            if ($this->user_id() != 0 && $this->user_id() != $user_id) {
1278
                return false;
1279
            }
1280
1281
            // if the user isn't logged in then try and load the
1282
            // details
1283
            if ($this->user_id() == 0 && !$this->init($user_id)) {
1284
                return false;
1285
            }
1286
1287
            $details = [
1288
                'email' => $email,
1289
                'firstname' => $this->firstname(),
1290
                'lastname' => $this->lastname(),
1291
                'postcode' => $this->postcode(),
1292
                'url' => $this->url(),
1293
                'optin' => $this->optin(),
1294
                'user_id' => $user_id,
1295
            ];
1296
            $ret = $this->_update($details);
1297
1298
            if ($ret) {
1299
                // and remove the token to be tidy
1300
                $this->db->query("DELETE
1301
                    FROM    tokens
1302
                    WHERE   token = :token
1303
                    AND   type = 'E'
1304
                ", [':token' => $registrationtoken]);
1305
1306
                $this->email = $email;
1307
1308
                # Check Stripe email
1309
                $subscription = new MySociety\TheyWorkForYou\Subscription($this);
1310
                if ($subscription->stripe) {
1311
                    $subscription->update_email($email);
1312
                }
1313
1314
                $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1315
                $URL->insert(['email' => 't']);
1316
                $redirecturl = $URL->generate();
1317
                if ($redirect) {
1318
                    $this->login($redirecturl, 'session');
1319
                } else {
1320
                    return true;
1321
                }
1322
            } else {
1323
                return false;
1324
            }
1325
        } else {
1326
            return false;
1327
        }
1328
1329
    }
1330
1331
    public function confirm($token) {
1332
        // The user has clicked the link in their confirmation email
1333
        // and the confirm page has passed the token from the URL to here.
1334
        // If all goes well they'll be confirmed and then logged in.
1335
1336
        // Split the token into its parts.
1337
        $arg = '';
1338
        if (strstr($token, '::')) {
1339
            $arg = '::';
1340
        }
1341
        if (strstr($token, '-')) {
1342
            $arg = '-';
1343
        }
1344
        [$user_id, $registrationtoken] = explode($arg, $token);
1345
1346
        if (!is_numeric($user_id) || $registrationtoken == '') {
1347
            return false;
1348
        }
1349
1350
        $q = $this->db->query("SELECT email, password, postcode
1351
                        FROM    users
1352
                        WHERE   user_id = :user_id
1353
                        AND     registrationtoken = :token
1354
                        ", [
1355
            ':user_id' => $user_id,
1356
            ':token' => $registrationtoken,
1357
        ])->first();
1358
1359
        if ($q) {
1360
1361
            // We'll need these to be set before logging the user in.
1362
            $this->user_id  = $user_id;
1363
            $this->email    = $q['email'];
1364
            $this->password = $q['password'];
1365
1366
            // Set that they're confirmed in the DB.
1367
            $r = $this->db->query("UPDATE users
1368
                            SET     confirmed = '1'
1369
                            WHERE   user_id = :user_id
1370
                            ", [':user_id' => $user_id]);
1371
1372
            if ($q['postcode']) {
1373
                try {
1374
                    $MEMBER = new MEMBER(['postcode' => $q['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
1375
                    $pid = $MEMBER->person_id();
1376
                    # This should probably be in the ALERT class
1377
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', [
1378
                        ':email' => $this->email,
1379
                        ':criteria' => 'speaker:' . $pid,
1380
                    ]);
1381
                } catch (MySociety\TheyWorkForYou\MemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1382
                }
1383
            }
1384
1385
            if ($r->success()) {
1386
1387
                $this->confirmed = true;
1388
1389
                $redirecturl = get_http_var('ret');
1390
                if (!$redirecturl || substr($redirecturl, 0, 1) != '/') {
1391
                    // Log the user in, redirecting them to the confirm page
1392
                    // where they should get a nice welcome message.
1393
                    $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1394
                    $URL->insert(['welcome' => 't']);
1395
                    $redirecturl = $URL->generate();
1396
                }
1397
1398
                $this->login($redirecturl, 'session');
1399
1400
            } else {
1401
                // Couldn't set them as confirmed in the DB.
1402
                return false;
1403
            }
1404
1405
        } else {
1406
            // Couldn't find this user in the DB. Maybe the token was
1407
            // wrong or incomplete?
1408
            return false;
1409
        }
1410
    }
1411
1412
    public function confirm_without_token() {
1413
        // If we want to confirm login without a token, e.g. during
1414
        // Facebook registration
1415
        //
1416
        // Note that this doesn't login or redirect the user.
1417
1418
        twfy_debug("THEUSER", "Confirming user without token: " . $this->user_id());
1419
        $q = $this->db->query("SELECT email, password, postcode
1420
                        FROM    users
1421 2
                        WHERE   user_id = :user_id
1422
                        ", [
1423
            ':user_id' => $this->user_id,
1424
        ])->first();
1425
1426
        if ($q) {
1427
1428 2
            twfy_debug("THEUSER", "User with ID found to confirm: " . $this->user_id());
1429
            // We'll need these to be set before logging the user in.
1430 2
            $this->email    = $q['email'];
1431
1432
            // Set that they're confirmed in the DB.
1433
            $r = $this->db->query("UPDATE users
1434 2
                            SET     confirmed = '1'
1435 2
                            WHERE   user_id = :user_id
1436 1
                            ", [':user_id' => $this->user_id]);
1437
1438
            if ($q['postcode']) {
1439 1
                try {
1440 1
                    $MEMBER = new MEMBER(['postcode' => $q['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
1441
                    $pid = $MEMBER->person_id();
1442 2
                    # This should probably be in the ALERT class
1443 2
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', [
1444
                        ':email' => $this->email,
1445
                        ':criteria' => 'speaker:' . $pid,
1446
                    ]);
1447
                } catch (MySociety\TheyWorkForYou\MemberException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1448 2
                }
1449
            }
1450
1451
            if ($r->success()) {
1452 2
                twfy_debug("THEUSER", "User with ID confirmed: " . $this->user_id());
1453 2
                $this->confirmed = true;
1454 2
                return true;
1455 2
            } else {
1456 2
                twfy_debug("THEUSER", "User with ID not confirmed: " . $this->user_id());
1457 2
                // Couldn't set them as confirmed in the DB.
1458
                return false;
1459
            }
1460
1461 2
        } else {
1462 1
            // Couldn't find this user in the DB. Maybe the token was
1463 1
            // wrong or incomplete?
1464 1
            twfy_debug("THEUSER", "User with ID not found to confirm: " . $this->user_id());
1465
            return false;
1466
        }
1467
    }
1468
1469
1470
    public function set_postcode_cookie($pc) {
1471
        // Set the user's postcode.
1472
        // Doesn't change it in the DB, as it's probably mainly for
1473
        // not-logged-in users.
1474 1
1475 1
        $this->postcode = $pc;
1476
        if (!headers_sent()) { // if in debug mode
1477
            setcookie(POSTCODE_COOKIE, $pc, time() + 7 * 86400, "/", COOKIEDOMAIN);
1478
        }
1479 1
1480 1
        twfy_debug('USER', "Set the cookie named '" . POSTCODE_COOKIE . " to '$pc' for " . COOKIEDOMAIN . " domain");
1481 1
    }
1482 1
1483
    public function unset_postcode_cookie() {
1484
        if (!headers_sent()) { // if in debug mode
1485 1
            setcookie(POSTCODE_COOKIE, '', time() - 3600, '/', COOKIEDOMAIN);
1486
        }
1487
    }
1488
1489
    // mostly here for updating from facebook where we do not need
1490
    // to confirm the email address
1491
    public function update_self_no_confirm($details) {
1492 1
        global $THEUSER;
1493
1494
        if ($this->isloggedin()) {
1495
            twfy_debug("THEUSER", "is logged in for update_self");
1496
1497
            // this is checked elsewhere but just in case we check here and
1498
            // bail out to be on the safe side
1499
            if (isset($details['email'])) {
1500
                if ($details['email'] != $this->email() && $this->email_exists($details['email'])) {
1501
                    return false;
1502
                }
1503
            }
1504
1505
            $details["user_id"] = $this->user_id;
1506
1507
            $newdetails = $this->_update($details);
1508
1509
            if ($newdetails) {
1510
                // The user's data was updated, so we'll change the object
1511
                // variables accordingly.
1512
1513
                $this->firstname        = $newdetails["firstname"];
1514
                $this->lastname         = $newdetails["lastname"];
1515
                $this->postcode         = $newdetails["postcode"];
1516
                $this->url              = $newdetails["url"];
1517
                $this->optin            = $newdetails["optin"];
1518
                $this->email            = $newdetails['email'];
1519
                if ($newdetails["password"] != "") {
1520
                    $this->password = $newdetails["password"];
1521
                }
1522
1523
                return true;
1524
            } else {
1525
                return false;
1526
            }
1527
1528
        } else {
1529
            return false;
1530
        }
1531
1532
    }
1533
1534
    public function update_self($details, $confirm_email = true) {
1535
        // If the user wants to update their details, call this function.
1536
        // It checks that they're logged in before letting them.
1537
1538
1539
        // $details is an array like that in $this->add().
1540
1541
        global $THEUSER;
1542
1543
        if ($this->isloggedin()) {
1544
1545
            // this is checked elsewhere but just in case we check here and
1546
            // bail out to be on the safe side
1547
            $email = '';
1548
            if (isset($details['email'])) {
1549
                if ($details['email'] != $this->email() && $this->email_exists($details['email'])) {
1550
                    return false;
1551
                }
1552
                $email = $details['email'];
1553
                unset($details['email']);
1554
            }
1555
            $details["user_id"] = $this->user_id;
1556
            $newdetails = $this->_update($details);
1557
1558
            // $newdetails will be an array of details if all went well,
1559
            // false otherwise.
1560
1561
            if ($newdetails) {
1562
                // The user's data was updated, so we'll change the object
1563
                // variables accordingly.
1564
1565
                $this->firstname        = $newdetails["firstname"];
1566
                $this->lastname         = $newdetails["lastname"];
1567
                $this->postcode         = $newdetails["postcode"];
1568
                $this->url              = $newdetails["url"];
1569
                $this->optin            = $newdetails["optin"];
1570
                if ($newdetails["password"] != "") {
1571
                    $this->password = $newdetails["password"];
1572
                }
1573
1574
                if ($email && $email != $this->email) {
1575
                    $token = substr(password_hash($email . microtime(), PASSWORD_BCRYPT), 29, 16);
1576
                    $data = $this->user_id() . '::' . $email;
1577
                    $r = $this->db->query("INSERT INTO tokens
1578
                        ( expires, token, type, data )
1579
                        VALUES
1580
                        (
1581
                            DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY),
1582
                            :token,
1583
                            'E',
1584
                            :data
1585
                        )
1586
                    ", [
1587
                        ':token' => $token,
1588
                        ':data' => $data,
1589
                    ]);
1590
1591
                    // send confirmation email here
1592
                    if ($r->success()) {
1593
                        $newdetails['email'] = $email;
1594
                        $newdetails['token'] = $token;
1595
                        if ($confirm_email) {
1596
                            return $this->send_email_confirmation_email($newdetails);
1597
                        } else {
1598
                            return true;
1599
                        }
1600
                    } else {
1601
                        return false;
1602
                    }
1603
                }
1604
1605
                return true;
1606
            } else {
1607
                return false;
1608
            }
1609
1610
        } else {
1611
            return false;
1612
        }
1613
1614
    }
1615
1616
}
1617
1618
// Yes, we instantiate a new global $THEUSER object when every page loads.
1619
$THEUSER = new THEUSER();
1620