USER::registrationtime()   A
last analyzed

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
nc 1
nop 0
dl 0
loc 2
rs 10
c 1
b 0
f 0
ccs 0
cts 0
cp 0
crap 2
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
    // Don't use the status to check access privileges - use the is_able_to() function.
74
    public $status = "Viewer";
75
76
    // If you add more user variables above you should also:
77
    //      Add the approrprate code to $this->add()
78
    //      Add the appropriate code to $this->_update()
79
    //      Add accessor functions way down below...
80
    //      Alter THEUSER->update_self() to update with the new vars, if appropriate.
81
    //      Change things in the add/edit/view user page.
82
83
    public function __construct() {
84 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...
85 1
    }
86 1
87
    public function init($user_id) {
88 4
        // Pass it a user id and it will fetch the user's data from the db
89
        // and put it all in the appropriate variables.
90
        // Returns true if we've found user_id in the DB, false otherwise.
91
92
        // Look for this user_id's details.
93
        $q = $this->db->query(
94 4
            "SELECT firstname,
95
                                lastname,
96
                                password,
97
                                email,
98
                                postcode,
99
                                url,
100
                                lastvisit,
101
                                registrationtime,
102
                                registrationtoken,
103
                                registrationip,
104
                                optin,
105
                                status,
106
                                deleted,
107
                                confirmed,
108
                                facebook_id,
109
                                facebook_token
110
                        FROM    users
111
                        WHERE   user_id = :user_id",
112 4
            [':user_id' => $user_id]
113
        )->first();
114
115 4
116
        if ($q) {
117
            // We've got a user, so set them up.
118 4
119 4
            $this->user_id              = $user_id;
120 4
            $this->firstname            = $q["firstname"];
121 4
            $this->lastname             = $q["lastname"];
122 4
            $this->password             = $q["password"];
123 4
            $this->email                = $q["email"];
124 4
            $this->postcode             = $q["postcode"];
125 4
            $this->facebook_id          = $q["facebook_id"];
126 4
            $this->facebook_token       = $q["facebook_token"];
127 4
            $this->url                  = $q["url"];
128 4
            $this->lastvisit            = $q["lastvisit"];
129 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...
130 4
            $this->registrationtime     = $q["registrationtime"];
131 4
            $this->registrationip       = $q["registrationip"];
132 4
            $this->optin                = $q["optin"];
133 4
            $this->status               = $q["status"];
134 4
            $this->deleted = $q["deleted"] == 1 ? true : false;
135
            $this->confirmed = $q["confirmed"] == 1 ? true : false;
136 4
137
            return true;
138
139
        } else {
140
            return false;
141
            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...
142
        }
143
144
    }
145 1
146
    public function add($details, $confirmation_required = true) {
147
        // Adds a new user's info into the db.
148
        // Then optionally (and usually) calls another function to
149
        // send them a confirmation email.
150
151
        // $details is an associative array of all the user's details, of the form:
152
        // array (
153
        //      "firstname" => "Fred",
154
        //      "lastname"  => "Bloggs",
155
        //      etc... using the same keys as the object variable names.
156
        // )
157
        // The BOOL variables (eg, optin) will be true or false and will need to be
158 1
        // converted to 1/0 for MySQL.
159
        global $REMOTE_ADDR;
160 1
161
        $registrationtime = gmdate("YmdHis");
162 1
163
        $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
164 1
165
        if (!isset($details["status"])) {
166
            $details["status"] = "User";
167
        }
168 1
169 1
        if (!isset($details["facebook_id"])) {
170
            $details["facebook_id"] = "";
171
        }
172 1
173
        $q = $this->db->query("INSERT INTO users (
174
                firstname,
175
                lastname,
176
                email,
177
                postcode,
178
                url,
179
                password,
180
                optin,
181
                status,
182
                registrationtime,
183
                registrationip,
184
                facebook_id,
185
                deleted
186
            ) VALUES (
187
                :firstname,
188
                :lastname,
189
                :email,
190
                :postcode,
191
                :url,
192
                :password,
193
                :optin,
194
                :status,
195
                :registrationtime,
196
                :registrationip,
197
                :facebook_id,
198
                '0'
199
            )
200 1
        ", [
201 1
            ':firstname' => $details["firstname"],
202 1
            ':lastname' => $details["lastname"],
203 1
            ':email' => $details["email"],
204 1
            ':postcode' => $details["postcode"],
205 1
            ':url' => $details["url"],
206 1
            ':password' => $passwordforDB,
207 1
            ':optin' => $details["optin"],
208 1
            ':status' => $details["status"],
209 1
            ':registrationtime' => $registrationtime,
210 1
            ':facebook_id' => $details["facebook_id"],
211
            ':registrationip' => $REMOTE_ADDR,
212
        ]);
213 1
214
        if ($q->success()) {
215
            // Set these so we can log in.
216
            // Except we no longer automatically log new users in, we
217 1
            // send them an email. So this may not be required.
218 1
            $this->user_id = $q->insert_id();
219 1
            $this->password = $passwordforDB;
220
            $this->facebook_id = $details["facebook_id"];
221
222
            // We have to set the user's registration token.
223
            // This will be sent to them via email, so we can confirm they exist.
224
            // The token will be the first 16 characters of a hash.
225 1
226
            $token = substr(password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16);
227
228
            // Full stops don't work well at the end of URLs in emails, so
229
            // replace them. And double slash would be treated as single and
230
            // not work either. We won't be doing anything clever with the hash
231 1
            // stuff, just need to match this token.
232 1
            $token = strtr($token, './', 'Xx');
233
            $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...
234
235 1
            // Add that to the DB.
236
            $r = $this->db->query("UPDATE users
237
                            SET registrationtoken = :registrationtoken
238
                            WHERE   user_id = :user_id
239 1
                            ", [
240 1
                ':registrationtoken' => $this->registrationtoken,
241
                ':user_id' => $this->user_id,
242
            ]);
243 1
244
            if ($r->success()) {
245
                // Updated DB OK.
246 1
247
                if ($details['mp_alert'] && $details['postcode']) {
248
                    $MEMBER = new MEMBER(['postcode' => $details['postcode'], 'house' => HOUSE_TYPE_COMMONS]);
249
                    $pid = $MEMBER->person_id();
250
                    # No confirmation email, but don't automatically confirm
251
                    $ALERT = new ALERT();
252
                    $ALERT->add([
253
                        'email' => $details['email'],
254
                        'pid' => $pid,
255
                        'pc' => $details['postcode'],
256
                    ], false, false);
257
                }
258 1
259
                if ($confirmation_required) {
260
                    // Right, send the email...
261
                    $success = $this->send_confirmation_email($details);
262
263
                    if ($success) {
264
                        // All is good in the world!
265
                        return true;
266
                    } else {
267
                        // Couldn't send the email.
268
                        return false;
269
                    }
270
                } else {
271 1
                    // No confirmation email needed.
272
                    return true;
273
                }
274
            } else {
275
                // Couldn't add the registration token to the DB.
276
                return false;
277
            }
278
279
        } else {
280
            // Couldn't add the user's data to the DB.
281
            return false;
282
        }
283
    }
284
285
    public function add_facebook_id($facebook_id) {
286
        $q = $this->db->query(
287
            "UPDATE users SET facebook_id = :facebook_id WHERE email = :email",
288
            [
289
                ':facebook_id' => $facebook_id,
290
                ':email' => $this->email,
291
            ]
292
        );
293
294
        if ($q->success()) {
295
            $this->facebook_id = $facebook_id;
296
297
            return $facebook_id;
298
        } else {
299
            return false;
300
        }
301
    }
302
303
    public function send_email_confirmation_email($details) {
304
        // A brief check of the facts...
305
        if (!is_numeric($this->user_id) ||
306
            !isset($details['email']) ||
307
            $details['email'] == '' ||
308
            !isset($details['token']) ||
309
            $details['token'] == '') {
310
            return false;
311
        }
312
313
        // We prefix the registration token with the user's id and '-'.
314
        // Not for any particularly good reason, but we do.
315
316
        $urltoken = $this->user_id . '-' . $details['token'];
317
318
        $confirmurl = 'https://' . DOMAIN . '/E/' . $urltoken;
319
320
        // Arrays we need to send a templated email.
321
        $data =  [
322
            'to'        => $details['email'],
323
            'template'  => 'email_confirmation',
324
        ];
325
326
        $merge =  [
327
            'CONFIRMURL'    => $confirmurl,
328
        ];
329
330
        $success = send_template_email($data, $merge);
331
332
        if ($success) {
333
            return true;
334
        } else {
335
            return false;
336
        }
337
    }
338
339
    public function send_confirmation_email($details) {
340
        // After we've add()ed a user we'll probably be sending them
341
        // a confirmation email with a link to confirm their address.
342
343
        // $details is the array we just sent to add(), and which it's
344
        // passed on to us here.
345
346
        // A brief check of the facts...
347
        if (!is_numeric($this->user_id) ||
348
            !isset($details['email']) ||
349
            $details['email'] == '') {
350
            return false;
351
        }
352
353
        // We prefix the registration token with the user's id and '-'.
354
        // Not for any particularly good reason, but we do.
355
356
        $urltoken = $this->user_id . '-' . $this->registrationtoken;
357
358
        $confirmurl = 'https://' . DOMAIN . '/U/' . $urltoken;
359
        if (isset($details['ret'])) {
360
            $confirmurl .= '?ret=' . $details['ret'];
361
        }
362
363
        // Arrays we need to send a templated email.
364
        $data =  [
365
            'to'        => $details['email'],
366
            'template'  => 'join_confirmation',
367
        ];
368
369
        $merge =  [
370
            'CONFIRMURL'    => $confirmurl,
371
        ];
372
373
        $success = send_template_email($data, $merge);
374
375
        if ($success) {
376
            return true;
377
        } else {
378
            return false;
379
        }
380
    }
381
382
383
    public function update_other_user($details) {
384
        // If someone (like an admin) is updating another user, call this
385
        // function. It checks their privileges before letting them.
386
387
        // $details is an array like that in $this->add().
388
        // It must include a 'user_id' element!
389
390
        global $THEUSER;
391
392
        if (!isset($details["user_id"])) {
393
            return false;
394
395
        } elseif ($THEUSER->is_able_to("edituser")) {
396
397
            // If the user doing the updating has appropriate privileges...
398
399
            $newdetails = $this->_update($details);
400
401
            // $newdetails will be an array of details if all went well,
402
            // false otherwise.
403
            if ($newdetails) {
404
                return true;
405
            } else {
406
                return false;
407
            }
408
409
        } else {
410
            return false;
411
412
        }
413
    }
414
415
416
417
    public function change_password($email) {
418
419
        // This function is called from the Change Password page.
420
        // It will create a new password for the user with $email address.
421
        // If all goes OK it will return the plaintext version of the password.
422
        // Otherwise it returns false.
423
424
        if ($this->email_exists($email)) {
425
426
            $this->email = $email;
427
            for (;;) {
428
429
                $pwd = null;
430
                $o = null;
431
432
                // Generates the password ....
433
                for ($x = 0; $x < 6;) {
434
                    $y = rand(1, 1000);
435
                    if($y > 350 && $y < 601) {
436
                        $d = chr(rand(48, 57));
437
                    }
438
                    if($y < 351) {
439
                        $d = chr(rand(65, 90));
440
                    }
441
                    if($y > 600) {
442
                        $d = chr(rand(97, 122));
443
                    }
444
                    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...
445
                        $o = $d;
446
                        $pwd .= $d;
447
                        $x++;
448
                    }
449
                }
450
451
                // If the PW fits your purpose (e.g. this regexpression) return it, else make a new one
452
                // (You can change this regular-expression how you want ....)
453
                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

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