Passed
Pull Request — master (#1920)
by Struan
04:54
created

USER::can_annotate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
ccs 0
cts 1
cp 0
crap 2
rs 10
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 "deletecomment": // Delete comments.
603
604
                switch ($status) {
605
                    case "User":            return false;
606
                    case "Moderator":       return true;
607
                    case "Administrator":   return true;
608
                    case "Superuser":       return true;
609
                    default: /* Viewer */   return false;
610
                }
611
612
                // no break
613
            case "edituser":
614
615
                switch ($status) {
616
                    case "User":            return false;
617
                    case "Moderator":       return false;
618
                    case "Administrator":   return false;
619
                    case "Superuser":       return true;
620
                    default: /* Viewer */   return false;
621
                }
622
623
                // no break
624
            case "reportcomment":   // Report a comment for moderation.
625
626
                switch ($status) {
627
                    case "User":            return true;
628
                    case "Moderator":       return true;
629
                    case "Administrator":   return true;
630
                    case "Superuser":       return true;
631
                    default: /* Viewer */   return true;
632
                }
633
634
                // no break
635
            case "viewadminsection":    // Access pages in the Admin section.
636
637
                switch ($status) {
638
                    case "User":            return false;
639
                    case "Moderator":       return false;
640
                    case "Administrator":   return true;
641
                    case "Superuser":       return true;
642
                    default: /* Viewer */   return false;
643
                }
644
645
                // no break
646
            case "voteonhansard":   // Rate hansard things interesting/not.
647
                /* Everyone */              return true;
648
649
            case "annotate":
650
                if ($this->can_annotate) {
651
                    return true;
652
                }
653
                switch ($status) {
654
                    case "User":            return false;
655
                    case "Moderator":       return true;
656
                    case "Administrator":   return true;
657
                    case "Superuser":       return true;
658
                    default: /* Viewer */   return false;
659
                }
660
661
                // no break
662
            default:
663
                $PAGE->error_message("You need to set permissions for '$action'!");
664
665
                return false;
666
667
668
        }
669
670
671
672
    }
673
674
    // Same for every user...
675
    // Just returns an array of the possible statuses a user could have.
676
    // Handy for forms where you edit/view users etc.
677 40
    public function possible_statuses() {
678
        // Maybe there's a way of fetching these from the DB,
679
        // so we don't duplicate them here...?
680 40
681
        $statuses =  ["Viewer", "User", "Moderator", "Administrator", "Superuser"];
682
683 40
        return $statuses;
684
685
    }
686
687
688
689
    // Functions for accessing the user's variables.
690 2
691
    public function user_id() {
692
        return $this->user_id;
693
    }
694
    public function firstname() {
695
        return $this->firstname;
696 2
    }
697
    public function lastname() {
698
        return $this->lastname;
699 2
    }
700 1
    public function password() {
701
        return $this->password;
702 1
    }
703 1
    public function email() {
704
        return $this->email;
705
    }
706
    public function postcode() {
707
        return $this->postcode;
708
    }
709 2
    public function url() {
710 2
        return $this->url;
711 2
    }
712 2
    public function lastvisit() {
713 2
        return $this->lastvisit;
714
    }
715 2
    public function facebook_id() {
716
        return $this->facebook_id;
717 2
    }
718
    public function facebook_token() {
719
        return $this->facebook_token;
720
    }
721
    public function facebook_user() {
722
        return $this->facebook_user;
723
    }
724
725
    public function registrationtime() {
726
        return $this->registrationtime;
727
    }
728 2
    public function registrationip() {
729
        return $this->registrationip;
730
    }
731
    public function optin() {
732
        return $this->optin;
733
    }
734
    // Don't use the status to check access privileges - use the is_able_to() function.
735
    // But you might use status() to return text to display, describing a user.
736
    // We can then change what status() does in the future if our permissions system
737
    // changes.
738
    public function status() {
739
        return $this->status;
740
    }
741 2
    public function deleted() {
742
        return $this->deleted;
743
    }
744
    public function confirmed() {
745
        return $this->confirmed;
746
    }
747
748
    public function can_annotate() {
749
        return $this->can_annotate;
750
    }
751
752
    public function organisation() {
753
        return $this->organisation;
754 2
    }
755
756
    public function postcode_is_set() {
757
        // So we can tell if the, er, postcode is set or not.
758
        // Could maybe put some validation in here at some point.
759
        if ($this->postcode != '') {
760
            return true;
761
        } else {
762 2
            return false;
763 1
        }
764 1
    }
765
766
767 2
    /////////// PRIVATE FUNCTIONS BELOW... ////////////////
768
769
    public function _update($details) {
770
        // Update a user's info.
771
        // DO NOT call this function direct.
772 2
        // Call either $this->update_other_user() or $this->update_self().
773 2
774 2
        // $details is an array like that in $this->add().
775 2
        global $PAGE;
776 2
777
        // Update email alerts if email address changed
778
        if (isset($details['email']) && $this->email != $details['email']) {
779 2
            $this->db->query(
780 2
                'UPDATE alerts SET email = :details_email WHERE email = :email',
781 2
                [
782 2
                    ':details_email' => $details['email'],
783 2
                    ':email' => $this->email,
784 2
                ]
785 2
            );
786
        }
787
788
        // These are used to put optional fragments of SQL in, depending
789
        // on whether we're changing those things or not.
790
        $passwordsql = "";
791 2
        $deletedsql = "";
792 2
        $confirmedsql = "";
793
        $statussql = "";
794
        $emailsql = '';
795
        $annotatesql = '';
796
797
        $params = [];
798
799
        if (isset($details["password"]) && $details["password"] != "") {
800
            // The password is being updated.
801
            // If not, the password fields on the form will be left blank
802
            // so we don't want to overwrite the user's pw in the DB!
803
804
            $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
805
806
            $passwordsql = "password = :password, ";
807
            $params[':password'] = $passwordforDB;
808
        }
809
810
        if (isset($details["deleted"])) {
811
            // 'deleted' won't always be an option (ie, if the user is updating
812
            // their own info).
813
            if ($details['deleted'] == true) {
814
                $del = '1';
815
            } elseif ($details['deleted'] == false) {
816
                $del = '0';
817
            }
818
            if (isset($del)) {
819
                $deletedsql = "deleted  = '$del', ";
820
            }
821
        }
822
823
        if (isset($details["confirmed"])) {
824 3
            // 'confirmed' won't always be an option (ie, if the user is updating
825
            // their own info).
826
            if ($details['confirmed'] == true) {
827
                $con = '1';
828 3
            } elseif ($details['confirmed'] == false) {
829
                $con = '0';
830
            }
831
            if (isset($con)) {
832
                $confirmedsql = "confirmed  = '$con', ";
833
            }
834
        }
835
836
        if (isset($details["status"]) && $details["status"] != "") {
837
            // 'status' won't always be an option (ie, if the user is updating
838
            // their own info.
839
            $statussql = "status = :status, ";
840 3
            $params[':status'] = $details['status'];
841
842 3
        }
843
844
        if (isset($details["can_annotate"])) {
845
            $annotatesql = "can_annotate = :can_annotate, organisation = :organisation, ";
846
            $params[':can_annotate'] = $details['can_annotate'] ? 1 : 0;
847
            $params[':organisation'] = $details['organisation'];
848
        }
849
850 3
        if (isset($details['email']) && $details['email']) {
851
            $emailsql = "email = :email, ";
852
            $params[':email'] = $details['email'];
853
        }
854 3
855
        $q = $this->db->query("UPDATE users
856 3
                        SET     firstname   = :firstname,
857
                                lastname    = :lastname,
858 3
                                postcode    = :postcode,
859
                                url         = :url,"
860 3
                                . $passwordsql
861
                                . $deletedsql
862
                                . $confirmedsql
863
                                . $emailsql
864
                                . $statussql
865
                                . $annotatesql . "
866 3
                                optin       = :optin
867
                        WHERE   user_id     = :user_id
868
                        ", array_merge($params, [
869
                                    ':firstname' => $details['firstname'],
870
                                    ':lastname' => $details['lastname'],
871
                                    ':postcode' => $details['postcode'],
872
                                    ':url' => $details['url'],
873
                                    ':optin' => $details['optin'],
874
                                    ':user_id' => $details['user_id'],
875
                                ]));
876
877
        // If we're returning to
878 3
        // $this->update_self() then $THEUSER will have its variables
879
        // updated if everything went well.
880
        if ($q->success()) {
881
            return $details;
882
883
        } else {
884
            $PAGE->error_message("Sorry, we were unable to update user id '" . _htmlentities($details["user_id"]) . "'");
885
886
            return false;
887
        }
888
889
890 3
    }
891 3
892 3
893
894
895 3
896
} // End USER class
897
898
899
900
901 3
902
903
class THEUSER extends USER {
904
    // Handles all the login/out functionality and checking for the user
905
    // who is using the site right NOW. Yes, him, over there.
906 3
907
    // This will become true if all goes well...
908
    public $loggedin = false;
909
    public $facebook_user = false;
910
911
912
    public function __construct() {
913
        // This function is run automatically when a THEUSER
914
        // object is instantiated.
915
916
        $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...
917
918 3
        // We look at the user's cookie and see if it's valid.
919 3
        // If so, we're going to log them in.
920
921
        // A user's cookie is of the form:
922
        // 123.blahblahblah
923
        // Where '123' is a user id, and 'blahblahblah' is an md5 hash of the
924
        // encrypted password we've stored in the db.
925
        // (Maybe we could just put the encrypted pw in the cookie and md5ing
926 3
        // it is overkill? Whatever, it works.)
927
928 3
        $cookie = get_cookie_var("epuser_id"); // In includes/utility.php.
929
930 3
        if ($cookie == '') {
931
            $cookie = get_cookie_var("facebook_id");
932 3
            if ($cookie != '') {
933
                $this->facebook_user = true;
934
                twfy_debug("THEUSER", "is facebook login");
935
            }
936
        }
937
938
        if ($cookie == '') {
939
            twfy_debug("THEUSER init FAILED", "No cookie set");
940 3
            $this->loggedin = false;
941
942
        } elseif (preg_match("/([[:alnum:]]*)\.([[:alnum:]]*)/", $cookie, $matches)) {
943
944
            if (is_numeric($matches[1])) {
945
946
                $success = $this->init($matches[1]);
947
948 7
                if ($success) {
949
                    // We got all the user's data from the DB.
950
951 7
                    // But we need to check the password before we log them in.
952 2
                    // And make sure the user hasn't been "deleted".
953
954 2
                    if ($this->facebook_user) {
955
                        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...
956 7
                            twfy_debug("THEUSER", "init SUCCESS: setting as logged in");
957
                            $this->loggedin = true;
958 7
                        } elseif (md5($this->facebook_token()) != $matches[2]) {
959
                            twfy_debug("THEUSER", "init FAILED: Facebook token doesn't match cookie");
960
                            $this->loggedin = false;
961
                        } else {
962
                            twfy_debug("THEUSER", "init FAILED: User is deleted");
963
                            $this->loggedin = false;
964
                        }
965
                    } else {
966
                        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...
967
                            // The correct password is in the cookie,
968
                            // and the user isn't deleted, so set the user to be logged in.
969
970
                            // This would be an appropriate place to call other functions
971
                            // that might set user info that only a logged-in user is going
972
                            // to need. Their preferences and saved things or something.
973
974
975
                            twfy_debug("THEUSER init SUCCEEDED", "setting as logged in");
976
                            $this->loggedin = true;
977
978
                        } elseif (md5($this->password()) != $matches[2]) {
979
                            twfy_debug("THEUSER init FAILED", "Password doesn't match cookie");
980
                            $this->loggedin = false;
981
                        } else {
982
                            twfy_debug("THEUSER init FAILED", "User is deleted");
983
                            $this->loggedin = false;
984
                        }
985
                    }
986
987
                } else {
988
                    twfy_debug("THEUSER init FAILED", "didn't get 1 row from db");
989
                    $this->loggedin = false;
990
                }
991
992
            } else {
993
                twfy_debug("THEUSER init FAILED", "cookie's user_id is not numeric");
994
                $this->loggedin = false;
995
            }
996
997
        } else {
998
            twfy_debug("THEUSER init FAILED", "cookie is not of the correct form");
999
            $this->loggedin = false;
1000
        }
1001
1002
        // If a user is logged in they *might* have set their own postcode.
1003 4
        // If they aren't logged in, or they haven't set one, then we may
1004 4
        // have set a postcode for them when they searched for their MP.
1005 4
        // If so, we'll use that as $this->postcode.
1006
        if ($this->postcode == '') {
1007
            if (get_cookie_var(POSTCODE_COOKIE) != '') {
1008 4
                $pc = get_cookie_var(POSTCODE_COOKIE);
1009
1010
                $this->set_postcode_cookie($pc);
1011
            }
1012
        }
1013
1014
        $this->update_lastvisit();
1015
1016
    } // End THEUSER()
1017
1018
    public function update_lastvisit() {
1019
1020
        if ($this->isloggedin()) {
1021
            // Set last_visit to now.
1022
            $date_now = gmdate("Y-m-d H:i:s");
1023
            $this->db->query(
1024
                "UPDATE users SET lastvisit = :lastvisit WHERE user_id = :user_id",
1025
                [ ':lastvisit' => $date_now, ':user_id' => $this->user_id() ]
1026
            );
1027
1028
            $this->lastvisit = $date_now;
1029
        }
1030
    }
1031
1032
    // For completeness, but it's better to call $this->isloggedin()
1033
    // if you want to check the log in status.
1034
    public function loggedin() {
1035
        return $this->loggedin;
1036
    }
1037
1038
1039
1040
    public function isloggedin() {
1041
        // Call this function to check if the user is successfully logged in.
1042
1043
        if ($this->loggedin()) {
1044
            twfy_debug("THEUSER", "isloggedin: true");
1045
1046
            return true;
1047
        } else {
1048
            twfy_debug("THEUSER", "isloggedin: false");
1049
1050
            return false;
1051
        }
1052
    }
1053
1054
1055
    public function isvalid($email, $userenteredpassword) {
1056
        // Returns true if this email and plaintext password match a user in the db.
1057
        // If false returns an array of form error messages.
1058
1059
        // We use this on the log in page to check if the details the user entered
1060
        // are correct. We can then continue with logging the user in (taking into
1061
        // account their cookie remembering settings etc) with $this->login().
1062
1063
        // This error string is shared between both email and password errors to
1064
        // prevent leaking of account existence.
1065
1066
        $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.';
1067
1068
        $q = $this->db->query("SELECT user_id, password, deleted, confirmed FROM users WHERE email = :email", [':email' => $email])->first();
1069
1070
        if ($q) {
1071
            // OK.
1072
            $dbpassword = $q["password"];
1073
            if (password_verify($userenteredpassword, $dbpassword)) {
1074
                $this->user_id  = $q["user_id"];
1075
                $this->password = $dbpassword;
1076
                // We'll need these when we're going to log in.
1077
                $this->deleted  = $q["deleted"] == 1 ? true : false;
1078
                $this->confirmed = $q["confirmed"] == 1 ? true : false;
1079
1080
                return true;
1081
1082
            } else {
1083
                // Failed.
1084
                return  ["invalidemail" => $error_string];
1085
1086
            }
1087
1088
        } else {
1089
            // Failed.
1090
            return  ["invalidemail" => $error_string];
1091
        }
1092
1093
    }
1094
1095
    public function has_postcode() {
1096
        $has_postcode = false;
1097
        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...
1098
            $has_postcode = true;
1099
        }
1100
        return $has_postcode;
1101
    }
1102
1103
1104
    public function facebook_login($returl, $expire, $accessToken) {
1105
        global $PAGE;
1106
1107
        twfy_debug("THEUSER", "Faceook login, user_id " . $this->user_id);
1108
        twfy_debug("THEUSER", "Faceook login, facebook_id " . $this->facebook_id);
1109
        twfy_debug("THEUSER", "Faceook login, email" . $this->email);
1110
        if ($this->facebook_id() == "") {
1111
            $PAGE->error_message("We don't have a facebook id for this user.", true);
1112
1113
            return;
1114
        }
1115
1116
        twfy_debug("THEUSER", "Faceook login, facebook_token: " . $accessToken);
1117
1118
        $q = $this->db->query(
1119
            "UPDATE users SET facebook_token = :token WHERE email = :email",
1120
            [
1121
                ':token' => $accessToken,
1122
                ':email' => $this->email,
1123
            ]
1124
        );
1125
1126
        if (!$q->success()) {
1127
            $PAGE->error_message("There was a problem logging you in", true);
1128
            twfy_debug("THEUSER", "Faceook login, failed to set accessToken");
1129
1130
            return false;
1131
        }
1132
1133
        // facebook login users probably don't have a password
1134
        $cookie = $this->user_id() . "." . md5($accessToken);
1135
        twfy_debug("THEUSER", "Faceook login, cookie: " . $cookie);
1136
1137
        twfy_debug("USER", "logging in user from facebook " . $this->user_id);
1138
1139
        $this->loggedin = true;
1140 2
        $this->_login($returl, $expire, $cookie, 'facebook_id');
1141 2
        return true;
1142 2
    }
1143 2
1144 2
    public function login($returl, $expire) {
1145
1146 2
        // This is used to log the user in. Duh.
1147
        // You should already have checked the user's email and password using
1148
        // $this->isvalid()
1149 2
        // That will have set $this->user_id and $this->password, allowing the
1150
        // login to proceed...
1151
1152
        // $expire is either 'session' or 'never' - for the cookie.
1153 2
1154
        // $returl is the URL to redirect the user to after log in, generally the
1155 2
        // page they were on before. But if it doesn't exist, they'll just go to
1156 2
        // the front page.
1157 2
        global $PAGE;
1158 2
1159 1
        if ($returl == "") {
1160 1
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1161
            $returl = $URL->generate();
1162
        }
1163
1164 1
        // Various checks about the user - if they fail, we exit.
1165
        if ($this->user_id() == "" || $this->password == "") {
1166
            $PAGE->error_message("We don't have the user_id or password to make the cookie.", true);
1167 1
1168
            return;
1169
        } elseif ($this->deleted) {
1170 1
            $PAGE->error_message("This user has been deleted.", true);
1171
1172
            return;
1173
        } elseif (!$this->confirmed) {
1174
            $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);
1175
1176 1
            return;
1177
        }
1178
1179
        // Reminder: $this->password is actually a hashed version of the plaintext pw.
1180
        $cookie = $this->user_id() . "." . md5($this->password());
1181 1
1182 1
        $this->_login($returl, $expire, $cookie);
1183 1
    }
1184 1
1185 1
    private function _login($returl, $expire, $cookie, $cookie_name = 'epuser_id') {
1186 1
        // Unset any existing postcode cookie.
1187 1
        // This will be the postcode the user set for themselves as a non-logged-in
1188
        // user. We don't want it hanging around as it causes confusion.
1189 1
        $this->unset_postcode_cookie();
1190
1191 1
        twfy_debug("THEUSER", "expire is " . $expire);
1192
1193 1
        $cookie_expires = 0;
1194
        if ($expire == 'never') {
1195
            twfy_debug("THEUSER", "cookie never expires");
1196
            $cookie_expires = time() + 86400 * 365 * 20;
1197 1
        } elseif (is_int($expire) && $expire > time()) {
1198
            twfy_debug("THEUSER", "cookie expires at " . $expire);
1199 1
            $cookie_expires = $expire;
1200
        } else {
1201
            twfy_debug("THEUSER", "cookie expires with session");
1202 1
        }
1203 1
1204 1
        header("Location: $returl");
1205
        setcookie($cookie_name, $cookie, $cookie_expires, '/', COOKIEDOMAIN);
1206
    }
1207 1
1208 1
1209 1
    public function logout($returl) {
1210 1
1211
        // $returl is the URL to redirect the user to after log in, generally the
1212
        // page they were on before. But if it doesn't exist, they'll just go to
1213 1
        // the front page.
1214
1215
        if ($returl == '') {
1216
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1217
            $returl = $URL->generate();
1218
        }
1219
1220
        // get_cookie_var() is in includes/utility.php
1221
        if (get_cookie_var("epuser_id") != "") {
1222
            // They're logged in, so set the cookie to empty.
1223
            header("Location: $returl");
1224
            setcookie('epuser_id', '', time() - 86400, '/', COOKIEDOMAIN);
1225
        }
1226
1227
        if (get_cookie_var("facebook_id") != "") {
1228
            // They're logged in, so set the cookie to empty.
1229
            header("Location: $returl");
1230
            setcookie('facebook_id', '', time() - 86400, '/', COOKIEDOMAIN);
1231
        }
1232
    }
1233
1234
    public function confirm_email($token, $redirect = true) {
1235
        $arg = '';
1236
        if (strstr($token, '::')) {
1237
            $arg = '::';
1238
        }
1239
        if (strstr($token, '-')) {
1240
            $arg = '-';
1241
        }
1242
        [$user_id, $registrationtoken] = explode($arg, $token);
1243
1244
        if (!is_numeric($user_id) || $registrationtoken == '') {
1245
            return false;
1246
        }
1247
        $q = $this->db->query("SELECT expires, data
1248
            FROM    tokens
1249
            WHERE   token = :token
1250
            AND   type = 'E'
1251
        ", [':token' => $registrationtoken])->first();
1252
1253
        if ($q) {
1254
            $expires = $q['expires'];
1255
            $expire_time = strtotime($expires);
1256
            if ($expire_time < time()) {
1257
                global $PAGE;
1258
                if ($PAGE && $redirect) {
1259
                    $PAGE->error_message("Sorry, that token seems to have expired");
1260
                }
1261
1262
                return false;
1263
            }
1264
1265
            [$user_id, $email] = explode('::', $q['data']);
1266
1267
            // if we are logged in as someone else don't change the email
1268
            if ($this->user_id() != 0 && $this->user_id() != $user_id) {
1269
                return false;
1270
            }
1271
1272
            // if the user isn't logged in then try and load the
1273
            // details
1274
            if ($this->user_id() == 0 && !$this->init($user_id)) {
1275
                return false;
1276
            }
1277
1278
            $details = [
1279
                'email' => $email,
1280
                'firstname' => $this->firstname(),
1281
                'lastname' => $this->lastname(),
1282
                'postcode' => $this->postcode(),
1283
                'url' => $this->url(),
1284
                'optin' => $this->optin(),
1285
                'user_id' => $user_id,
1286
            ];
1287
            $ret = $this->_update($details);
1288
1289
            if ($ret) {
1290
                // and remove the token to be tidy
1291
                $this->db->query("DELETE
1292
                    FROM    tokens
1293
                    WHERE   token = :token
1294
                    AND   type = 'E'
1295
                ", [':token' => $registrationtoken]);
1296
1297
                $this->email = $email;
1298
1299
                # Check Stripe email
1300
                $subscription = new MySociety\TheyWorkForYou\Subscription($this);
1301
                if ($subscription->stripe) {
1302
                    $subscription->update_email($email);
1303
                }
1304
1305
                $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1306
                $URL->insert(['email' => 't']);
1307
                $redirecturl = $URL->generate();
1308
                if ($redirect) {
1309
                    $this->login($redirecturl, 'session');
1310
                } else {
1311
                    return true;
1312
                }
1313
            } else {
1314
                return false;
1315
            }
1316
        } else {
1317
            return false;
1318
        }
1319
1320
    }
1321
1322
    public function confirm($token) {
1323
        // The user has clicked the link in their confirmation email
1324
        // and the confirm page has passed the token from the URL to here.
1325
        // If all goes well they'll be confirmed and then logged in.
1326
1327
        // Split the token into its parts.
1328
        $arg = '';
1329
        if (strstr($token, '::')) {
1330
            $arg = '::';
1331
        }
1332
        if (strstr($token, '-')) {
1333
            $arg = '-';
1334
        }
1335
        [$user_id, $registrationtoken] = explode($arg, $token);
1336
1337
        if (!is_numeric($user_id) || $registrationtoken == '') {
1338
            return false;
1339
        }
1340
1341
        $q = $this->db->query("SELECT email, password, postcode
1342
                        FROM    users
1343
                        WHERE   user_id = :user_id
1344
                        AND     registrationtoken = :token
1345
                        ", [
1346
            ':user_id' => $user_id,
1347
            ':token' => $registrationtoken,
1348
        ])->first();
1349
1350
        if ($q) {
1351
1352
            // We'll need these to be set before logging the user in.
1353
            $this->user_id  = $user_id;
1354
            $this->email    = $q['email'];
1355
            $this->password = $q['password'];
1356
1357
            // Set that they're confirmed in the DB.
1358
            $r = $this->db->query("UPDATE users
1359
                            SET     confirmed = '1'
1360
                            WHERE   user_id = :user_id
1361
                            ", [':user_id' => $user_id]);
1362
1363
            if ($q['postcode']) {
1364
                try {
1365
                    $MEMBER = new MEMBER(['postcode' => $q['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
1366
                    $pid = $MEMBER->person_id();
1367
                    # This should probably be in the ALERT class
1368
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', [
1369
                        ':email' => $this->email,
1370
                        ':criteria' => 'speaker:' . $pid,
1371
                    ]);
1372
                } 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...
1373
                }
1374
            }
1375
1376
            if ($r->success()) {
1377
1378
                $this->confirmed = true;
1379
1380
                $redirecturl = get_http_var('ret');
1381
                if (!$redirecturl || substr($redirecturl, 0, 1) != '/') {
1382
                    // Log the user in, redirecting them to the confirm page
1383
                    // where they should get a nice welcome message.
1384
                    $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1385
                    $URL->insert(['welcome' => 't']);
1386
                    $redirecturl = $URL->generate();
1387
                }
1388
1389
                $this->login($redirecturl, 'session');
1390
1391
            } else {
1392
                // Couldn't set them as confirmed in the DB.
1393
                return false;
1394
            }
1395
1396
        } else {
1397
            // Couldn't find this user in the DB. Maybe the token was
1398
            // wrong or incomplete?
1399
            return false;
1400
        }
1401
    }
1402
1403
    public function confirm_without_token() {
1404
        // If we want to confirm login without a token, e.g. during
1405
        // Facebook registration
1406
        //
1407
        // Note that this doesn't login or redirect the user.
1408
1409
        twfy_debug("THEUSER", "Confirming user without token: " . $this->user_id());
1410
        $q = $this->db->query("SELECT email, password, postcode
1411
                        FROM    users
1412
                        WHERE   user_id = :user_id
1413
                        ", [
1414
            ':user_id' => $this->user_id,
1415
        ])->first();
1416
1417
        if ($q) {
1418
1419
            twfy_debug("THEUSER", "User with ID found to confirm: " . $this->user_id());
1420
            // We'll need these to be set before logging the user in.
1421 2
            $this->email    = $q['email'];
1422
1423
            // Set that they're confirmed in the DB.
1424
            $r = $this->db->query("UPDATE users
1425
                            SET     confirmed = '1'
1426
                            WHERE   user_id = :user_id
1427
                            ", [':user_id' => $this->user_id]);
1428 2
1429
            if ($q['postcode']) {
1430 2
                try {
1431
                    $MEMBER = new MEMBER(['postcode' => $q['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
1432
                    $pid = $MEMBER->person_id();
1433
                    # This should probably be in the ALERT class
1434 2
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', [
1435 2
                        ':email' => $this->email,
1436 1
                        ':criteria' => 'speaker:' . $pid,
1437
                    ]);
1438
                } 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...
1439 1
                }
1440 1
            }
1441
1442 2
            if ($r->success()) {
1443 2
                twfy_debug("THEUSER", "User with ID confirmed: " . $this->user_id());
1444
                $this->confirmed = true;
1445
                return true;
1446
            } else {
1447
                twfy_debug("THEUSER", "User with ID not confirmed: " . $this->user_id());
1448 2
                // Couldn't set them as confirmed in the DB.
1449
                return false;
1450
            }
1451
1452 2
        } else {
1453 2
            // Couldn't find this user in the DB. Maybe the token was
1454 2
            // wrong or incomplete?
1455 2
            twfy_debug("THEUSER", "User with ID not found to confirm: " . $this->user_id());
1456 2
            return false;
1457 2
        }
1458
    }
1459
1460
1461 2
    public function set_postcode_cookie($pc) {
1462 1
        // Set the user's postcode.
1463 1
        // Doesn't change it in the DB, as it's probably mainly for
1464 1
        // not-logged-in users.
1465
1466
        $this->postcode = $pc;
1467
        if (!headers_sent()) { // if in debug mode
1468
            setcookie(POSTCODE_COOKIE, $pc, time() + 7 * 86400, "/", COOKIEDOMAIN);
1469
        }
1470
1471
        twfy_debug('USER', "Set the cookie named '" . POSTCODE_COOKIE . " to '$pc' for " . COOKIEDOMAIN . " domain");
1472
    }
1473
1474 1
    public function unset_postcode_cookie() {
1475 1
        if (!headers_sent()) { // if in debug mode
1476
            setcookie(POSTCODE_COOKIE, '', time() - 3600, '/', COOKIEDOMAIN);
1477
        }
1478
    }
1479 1
1480 1
    // mostly here for updating from facebook where we do not need
1481 1
    // to confirm the email address
1482 1
    public function update_self_no_confirm($details) {
1483
        global $THEUSER;
1484
1485 1
        if ($this->isloggedin()) {
1486
            twfy_debug("THEUSER", "is logged in for update_self");
1487
1488
            // this is checked elsewhere but just in case we check here and
1489
            // bail out to be on the safe side
1490
            if (isset($details['email'])) {
1491
                if ($details['email'] != $this->email() && $this->email_exists($details['email'])) {
1492 1
                    return false;
1493
                }
1494
            }
1495
1496
            $details["user_id"] = $this->user_id;
1497
1498
            $newdetails = $this->_update($details);
1499
1500
            if ($newdetails) {
1501
                // The user's data was updated, so we'll change the object
1502
                // variables accordingly.
1503
1504
                $this->firstname        = $newdetails["firstname"];
1505
                $this->lastname         = $newdetails["lastname"];
1506
                $this->postcode         = $newdetails["postcode"];
1507
                $this->url              = $newdetails["url"];
1508
                $this->optin            = $newdetails["optin"];
1509
                $this->email            = $newdetails['email'];
1510
                if ($newdetails["password"] != "") {
1511
                    $this->password = $newdetails["password"];
1512
                }
1513
1514
                return true;
1515
            } else {
1516
                return false;
1517
            }
1518
1519
        } else {
1520
            return false;
1521
        }
1522
1523
    }
1524
1525
    public function update_self($details, $confirm_email = true) {
1526
        // If the user wants to update their details, call this function.
1527
        // It checks that they're logged in before letting them.
1528
1529
1530
        // $details is an array like that in $this->add().
1531
1532
        global $THEUSER;
1533
1534
        if ($this->isloggedin()) {
1535
1536
            // this is checked elsewhere but just in case we check here and
1537
            // bail out to be on the safe side
1538
            $email = '';
1539
            if (isset($details['email'])) {
1540
                if ($details['email'] != $this->email() && $this->email_exists($details['email'])) {
1541
                    return false;
1542
                }
1543
                $email = $details['email'];
1544
                unset($details['email']);
1545
            }
1546
            $details["user_id"] = $this->user_id;
1547
            $newdetails = $this->_update($details);
1548
1549
            // $newdetails will be an array of details if all went well,
1550
            // false otherwise.
1551
1552
            if ($newdetails) {
1553
                // The user's data was updated, so we'll change the object
1554
                // variables accordingly.
1555
1556
                $this->firstname        = $newdetails["firstname"];
1557
                $this->lastname         = $newdetails["lastname"];
1558
                $this->postcode         = $newdetails["postcode"];
1559
                $this->url              = $newdetails["url"];
1560
                $this->optin            = $newdetails["optin"];
1561
                if ($newdetails["password"] != "") {
1562
                    $this->password = $newdetails["password"];
1563
                }
1564
1565
                if ($email && $email != $this->email) {
1566
                    $token = substr(password_hash($email . microtime(), PASSWORD_BCRYPT), 29, 16);
1567
                    $data = $this->user_id() . '::' . $email;
1568
                    $r = $this->db->query("INSERT INTO tokens
1569
                        ( expires, token, type, data )
1570
                        VALUES
1571
                        (
1572
                            DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY),
1573
                            :token,
1574
                            'E',
1575
                            :data
1576
                        )
1577
                    ", [
1578
                        ':token' => $token,
1579
                        ':data' => $data,
1580
                    ]);
1581
1582
                    // send confirmation email here
1583
                    if ($r->success()) {
1584
                        $newdetails['email'] = $email;
1585
                        $newdetails['token'] = $token;
1586
                        if ($confirm_email) {
1587
                            return $this->send_email_confirmation_email($newdetails);
1588
                        } else {
1589
                            return true;
1590
                        }
1591
                    } else {
1592
                        return false;
1593
                    }
1594
                }
1595
1596
                return true;
1597
            } else {
1598
                return false;
1599
            }
1600
1601
        } else {
1602
            return false;
1603
        }
1604
1605
    }
1606
1607
}
1608
1609
// Yes, we instantiate a new global $THEUSER object when every page loads.
1610
$THEUSER = new THEUSER();
1611