Passed
Pull Request — master (#1920)
by Struan
05:39
created

USER::url()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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