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