Completed
Push — master ( 922be9...285e65 )
by Nick
41s queued 31s
created

USER::init()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 53
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 5.002

Importance

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

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/*
4
5
NO HTML IN THIS FILE!!
6
7
This file contains USER and THEUSER classes.
8
9
It automatically instantiates a $THEUSER global object. This refers to the person actually viewing the site. If they have a valid cookie set, $THEUSER's data will be fetched from the DB and they will be logged in. Otherwise they will have a minimum access level and will not be logged in.
10
11
12
The USER class allows us to fetch and alter data about any user (rather than $THEUSER).
13
14
To create a new user do:
15
    $USER = new USER;
16
    $USER->init($user_id);
17
18
You can then access all the user's variables with appropriately named functions, such as:
19
    $USER->user_id();
20
    $USER->email();
21
etc. Don't access the variables directly because I think that's bad.
22
23
USER is extended into the THEUSER class which is used only for the person currently using the site. ie, it adds functions for logging in and out, checking log in status, etc.
24
25
GUESTUSER:
26
In the database there should be a user with an id of 0 and a status of 'Viewer' (and probably a name of 'Guest').
27
28
The cookie set to indicate a logged in user is called "epuser_id". More on that in THEUSER().
29
30
Functions here:
31
32
USER
33
    init()              Send it a user id to fetch data from DB.
34
    add()               Add a new user to the DB.
35
    send_confirmation_email()   Done after add()ing the user.
36
    update_other_user() Update the data of another user.
37
    change_password()   Generate a new password and put in DB.
38
    id_exists()         Checks if a user_id is valid.
39
    email_exists()      Checks if a user exists with a certain email address.
40
    is_able_to()        Is the user allowed to perform this action?
41
    possible_statuses() Return an array of the possible security statuses for users.
42
    Accessor functions for each object variable (eg, user_id()  ).
43
    _update()           Private function that updates a user's data in DB.
44
45
THEUSER
46
    THEUSER()           Constructor that logs in if the cookie is correct.
47
    isloggedin()        Check if the user is logged in or not.
48
    isvalid()           Check to see if the user's login form details are OK.
49
    login()             Log the user in.
50
    logout()            Log the user out.
51
    confirm()           With the correct token, confirms the user then logs them in.
52
    update_self()       Update the user's own data in the DB.
53
    check_user_access() Check a the user is allowed to view this page.
54
55
*/
56
57
class USER {
58
59
    public $user_id = "0";         // So we have an ID for non-logged in users reporting comments etc.
60
    public $firstname = "Guest";   // So we have something to print for non-logged in users.
61
    public $lastname = "";
62
    public $password = "";         // This will be a hashed version of a plaintext pw.
63
    public $email = "";
64
    public $postcode = "";
65
    public $url = "";
66
    public $lastvisit = "";        // Last time the logged-in user loaded a page (GMT).
67
    public $registrationtime = ""; // When they registered (GMT).
68
    public $registrationip = "";   // Where they registered from.
69
    public $optin = "";            // boolean - Do they want emails from us?
70
    public $deleted = "";          // User can't log in or have their info displayed.
71
    public $confirmed = '';        // boolean - Has the user confirmed via email?
72
    public $facebook_id = '';      // Facebook ID for users who login with FB
73
    public $facebook_token = '';   // Facebook token for users who login with FB
74
    // Don't use the status to check access privileges - use the is_able_to() function.
75
    public $status = "Viewer";
76
77
    // If you add more user variables above you should also:
78
    //      Add the approrprate code to $this->add()
79
    //      Add the appropriate code to $this->_update()
80
    //      Add accessor functions way down below...
81
    //      Alter THEUSER->update_self() to update with the new vars, if appropriate.
82
    //      Change things in the add/edit/view user page.
83
84 1
    public function __construct() {
85 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...
86 1
    }
87
88 4
    public function init($user_id) {
89
        // Pass it a user id and it will fetch the user's data from the db
90
        // and put it all in the appropriate variables.
91
        // Returns true if we've found user_id in the DB, false otherwise.
92
93
        // Look for this user_id's details.
94 4
        $q = $this->db->query("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
                        array(':user_id' => $user_id))->first();
113
114
115 4
        if ($q) {
116
            // We've got a user, so set them up.
117
118 4
            $this->user_id              = $user_id;
119 4
            $this->firstname            = $q["firstname"];
120 4
            $this->lastname             = $q["lastname"];
121 4
            $this->password             = $q["password"];
122 4
            $this->email                = $q["email"];
123 4
            $this->postcode             = $q["postcode"];
124 4
            $this->facebook_id          = $q["facebook_id"];
125 4
            $this->facebook_token       = $q["facebook_token"];
126 4
            $this->url                  = $q["url"];
127 4
            $this->lastvisit            = $q["lastvisit"];
128 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...
129 4
            $this->registrationtime     = $q["registrationtime"];
130 4
            $this->registrationip       = $q["registrationip"];
131 4
            $this->optin = $q["optin"] == 1 ? true : false;
132 4
            $this->status               = $q["status"];
133 4
            $this->deleted = $q["deleted"] == 1 ? true : false;
134 4
            $this->confirmed = $q["confirmed"] == 1 ? true : false;
135
136 4
            return true;
137
138
        } else {
139
            return false;
140
            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...
141
        }
142
143
    }
144
145 1
    public function add($details, $confirmation_required=true) {
146
        // Adds a new user's info into the db.
147
        // Then optionally (and usually) calls another function to
148
        // send them a confirmation email.
149
150
        // $details is an associative array of all the user's details, of the form:
151
        // array (
152
        //      "firstname" => "Fred",
153
        //      "lastname"  => "Bloggs",
154
        //      etc... using the same keys as the object variable names.
155
        // )
156
        // The BOOL variables (eg, optin) will be true or false and will need to be
157
        // converted to 1/0 for MySQL.
158 1
        global $REMOTE_ADDR;
159
160 1
        $registrationtime = gmdate("YmdHis");
161
162 1
        $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
163
164 1
        if (!isset($details["status"])) {
165
            $details["status"] = "User";
166
        }
167
168 1
        if (!isset($details["facebook_id"])) {
169 1
            $details["facebook_id"] = "";
170
        }
171
172 1
        $optin = $details["optin"] == true ? 1 : 0;
173
174 1
        $q = $this->db->query("INSERT INTO users (
175
                firstname,
176
                lastname,
177
                email,
178
                postcode,
179
                url,
180
                password,
181
                optin,
182
                status,
183
                registrationtime,
184
                registrationip,
185
                facebook_id,
186
                deleted
187
            ) VALUES (
188
                :firstname,
189
                :lastname,
190
                :email,
191
                :postcode,
192
                :url,
193
                :password,
194
                :optin,
195
                :status,
196
                :registrationtime,
197
                :registrationip,
198
                :facebook_id,
199
                '0'
200
            )
201
        ", array(
202 1
            ':firstname' => $details["firstname"],
203 1
            ':lastname' => $details["lastname"],
204 1
            ':email' => $details["email"],
205 1
            ':postcode' => $details["postcode"],
206 1
            ':url' => $details["url"],
207 1
            ':password' => $passwordforDB,
208 1
            ':optin' => $optin,
209 1
            ':status' => $details["status"],
210 1
            ':registrationtime' => $registrationtime,
211 1
            ':facebook_id' => $details["facebook_id"],
212 1
            ':registrationip' => $REMOTE_ADDR
213
        ));
214
215 1
        if ($q->success()) {
216
            // Set these so we can log in.
217
            // Except we no longer automatically log new users in, we
218
            // send them an email. So this may not be required.
219 1
            $this->user_id = $q->insert_id();
220 1
            $this->password = $passwordforDB;
221 1
            $this->facebook_id = $details["facebook_id"];
222
223
            // We have to set the user's registration token.
224
            // This will be sent to them via email, so we can confirm they exist.
225
            // The token will be the first 16 characters of a hash.
226
227 1
            $token = substr( password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16 );
228
229
            // Full stops don't work well at the end of URLs in emails,
230
            // so replace them. We won't be doing anything clever with the hash
231
            // stuff, just need to match this token.
232
233 1
            $this->registrationtoken = strtr($token, '.', 'X');
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
            // Add that to the DB.
236 1
            $r = $this->db->query("UPDATE users
237
                            SET registrationtoken = :registrationtoken
238
                            WHERE   user_id = :user_id
239
                            ", array (
240 1
                                ':registrationtoken' => $this->registrationtoken,
241 1
                                ':user_id' => $this->user_id
242
                            ));
243
244 1
            if ($r->success()) {
245
                // Updated DB OK.
246
247 1
                if ($details['mp_alert'] && $details['postcode']) {
248
                    $MEMBER = new MEMBER(array('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(array(
253
                        'email' => $details['email'],
254
                        'pid' => $pid,
255
                        'pc' => $details['postcode'],
256
                    ), false, false);
257
                }
258
259 1
                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
                    // No confirmation email needed.
272 1
                    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 ("UPDATE users SET facebook_id = :facebook_id WHERE email = :email",
287
            array(
288
                ':facebook_id' => $facebook_id,
289
                ':email' => $this->email
290
            ));
291
292
        if ($q->success()) {
293
            $this->facebook_id = $facebook_id;
294
295
            return $facebook_id;
296
        } else {
297
            return false;
298
        }
299
    }
300
301
    public function send_email_confirmation_email($details) {
302
        // A brief check of the facts...
303
        if (!is_numeric($this->user_id) ||
304
            !isset($details['email']) ||
305
            $details['email'] == '' ||
306
            !isset($details['token']) ||
307
            $details['token'] == '' ) {
308
            return false;
309
        }
310
311
        // We prefix the registration token with the user's id and '-'.
312
        // Not for any particularly good reason, but we do.
313
314
        $urltoken = $this->user_id . '-' . $details['token'];
315
316
        $confirmurl = 'https://' . DOMAIN . '/E/' . $urltoken;
317
318
        // Arrays we need to send a templated email.
319
        $data = array (
320
            'to'        => $details['email'],
321
            'template'  => 'email_confirmation'
322
        );
323
324
        $merge = array (
325
            'FIRSTNAME'     => $details['firstname'],
326
            'LASTNAME'      => $details['lastname'],
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 = array (
365
            'to'        => $details['email'],
366
            'template'  => 'join_confirmation'
367
        );
368
369
        $merge = array (
370
            'FIRSTNAME'     => $details['firstname'],
371
            'LASTNAME'      => $details['lastname'],
372
            'CONFIRMURL'    => $confirmurl
373
        );
374
375
        $success = send_template_email($data, $merge);
376
377
        if ($success) {
378
            return true;
379
        } else {
380
            return false;
381
        }
382
    }
383
384
385
    public function update_other_user($details) {
386
        // If someone (like an admin) is updating another user, call this
387
        // function. It checks their privileges before letting them.
388
389
        // $details is an array like that in $this->add().
390
        // It must include a 'user_id' element!
391
392
        global $THEUSER;
393
394
        if (!isset($details["user_id"])) {
395
            return false;
396
397
        } elseif ($THEUSER->is_able_to("edituser")) {
398
399
            // If the user doing the updating has appropriate privileges...
400
401
            $newdetails = $this->_update($details);
402
403
            // $newdetails will be an array of details if all went well,
404
            // false otherwise.
405
            if ($newdetails) {
406
                return true;
407
            } else {
408
                return false;
409
            }
410
411
        } else {
412
            return false;
413
414
        }
415
    }
416
417
418
419
    public function change_password($email) {
420
421
        // This function is called from the Change Password page.
422
        // It will create a new password for the user with $email address.
423
        // If all goes OK it will return the plaintext version of the password.
424
        // Otherwise it returns false.
425
426
        if ($this->email_exists($email)) {
427
428
            $this->email = $email;
429
            for (;;) {
430
431
                $pwd=null;
432
                $o=null;
433
434
                // Generates the password ....
435
                for ($x=0; $x < 6;) {
436
                    $y = rand(1,1000);
437
                    if($y>350 && $y<601) $d=chr(rand(48,57));
438
                    if($y<351) $d=chr(rand(65,90));
439
                    if($y>600) $d=chr(rand(97,122));
440
                    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...
441
                        $o=$d; $pwd.=$d; $x++;
442
                    }
443
                }
444
445
                // If the PW fits your purpose (e.g. this regexpression) return it, else make a new one
446
                // (You can change this regular-expression how you want ....)
447
                if (preg_match("/^[a-zA-Z]{1}([a-zA-Z]+[0-9][a-zA-Z]+)+/",$pwd)) {
448
                    break;
449
                }
450
451
            }
452
            $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...
453
454
        // End password generating stuff.
455
456
        } else {
457
458
            // Email didn't exist.
459
            return false;
460
461
        }
462
463
        $passwordforDB = password_hash($pwd, PASSWORD_BCRYPT);
464
465
        $q = $this->db->query ("UPDATE users SET password = :password WHERE email = :email",
466
            array(
467
                ':password' => $passwordforDB,
468
                ':email' => $email
469
            ));
470
471
        if ($q->success()) {
472
            $this->password = $pwd;
473
474
            return $pwd;
475
476
        } else {
477
            return false;
478
        }
479
480
    }
481
482
    public function send_password_reminder() {
483
        global $PAGE;
484
485
        // You'll probably have just called $this->change_password().
486
487
        if ($this->email() == '') {
488
            $PAGE->error_message("No email set for this user, so can't send a password reminder.");
489
490
            return false;
491
        }
492
493
        $data = array (
494
            'to'            => $this->email(),
495
            'template'      => 'new_password'
496
        );
497
498
        $URL = new \MySociety\TheyWorkForYou\Url("userlogin");
499
500
        $merge = array (
501
            'EMAIL'         => $this->email(),
502
            'LOGINURL'      => "https://" . DOMAIN . $URL->generate(),
503
            'PASSWORD'      => $this->password()
504
        );
505
506
        // send_template_email in utility.php.
507
        $success = send_template_email($data, $merge);
508
509
        return $success;
510
511
    }
512
513
514
515
516
    public function id_exists($user_id) {
517
        // Returns true if there's a user with this user_id.
518
519
        if (is_numeric($user_id)) {
520
            $q = $this->db->query("SELECT user_id FROM users WHERE user_id = :user_id",
521
                array(':user_id' => $user_id));
522
            if ($q->rows() > 0) {
523
                return true;
524
            } else {
525
                return false;
526
            }
527
        } else {
528
            return false;
529
        }
530
531
    }
532
533
534 1
    public function email_exists($email, $return_id = false) {
535
        // Returns true if there's a user with this email address.
536
537 1
        if ($email != "") {
538 1
            $q = $this->db->query("SELECT user_id FROM users WHERE email = :email", array(':email' => $email))->first();
539 1
            if ($q) {
540
                if ($return_id) {
541
                    return $q['user_id'];
542
                }
543
                return true;
544
            } else {
545 1
                return false;
546
            }
547
        } else {
548
            return false;
549
        }
550
    }
551
552
    public function facebook_id_exists($id, $return_id = false) {
553
        // Returns true if there's a user with this facebook id.
554
555
        if ($id!= "") {
556
            $q = $this->db->query("SELECT user_id FROM users WHERE facebook_id = :id", array(':id' => $id))->first();
557
            if ($q) {
558
                if ($return_id) {
559
                    return $q['user_id'];
560
                }
561
                return true;
562
            } else {
563
                return false;
564
            }
565
        } else {
566
            return false;
567
        }
568
    }
569
570
    public function is_able_to($action) {
571
        // Call this function to find out if a user is allowed to do something.
572
        // It uses the user's status to return true or false.
573
        // Possible actions:
574
        //  "addcomment"
575
        //  "reportcomment"
576
        //  "edituser"
577
        global $PAGE;
578
579
        $status = $this->status();
580
581
        switch ($action) {
582
583
            // You can add more below as they're needed...
584
            // But keep them in alphabetical order!
585
586
            case "deletecomment": // Delete comments.
587
588
                switch ($status) {
589
                    case "User":            return false;
590
                    case "Moderator":       return true;
591
                    case "Administrator":   return true;
592
                    case "Superuser":       return true;
593
                    default: /* Viewer */   return false;
594
                }
595
596
            case "edituser":
597
598
                switch ($status) {
599
                    case "User":            return false;
600
                    case "Moderator":       return false;
601
                    case "Administrator":   return false;
602
                    case "Superuser":       return true;
603
                    default: /* Viewer */   return false;
604
                }
605
606
            case "reportcomment":   // Report a comment for moderation.
607
608
                switch ($status) {
609
                    case "User":            return true;
610
                    case "Moderator":       return true;
611
                    case "Administrator":   return true;
612
                    case "Superuser":       return true;
613
                    default: /* Viewer */   return true;
614
                }
615
616
            case "viewadminsection":    // Access pages in the Admin section.
617
618
                switch ($status) {
619
                    case "User":            return false;
620
                    case "Moderator":       return false;
621
                    case "Administrator":   return true;
622
                    case "Superuser":       return true;
623
                    default: /* Viewer */   return false;
624
                }
625
626
            case "voteonhansard":   // Rate hansard things interesting/not.
627
                /* Everyone */              return true;
628
629
            default:
630
                $PAGE->error_message ("You need to set permissions for '$action'!");
631
632
                return false;
633
634
635
        }
636
637
638
639
    }
640
641
    // Same for every user...
642
    // Just returns an array of the possible statuses a user could have.
643
    // Handy for forms where you edit/view users etc.
644
    public function possible_statuses() {
645
        // Maybe there's a way of fetching these from the DB,
646
        // so we don't duplicate them here...?
647
648
        $statuses = array ("Viewer", "User", "Moderator", "Administrator", "Superuser");
649
650
        return $statuses;
651
652
    }
653
654
655
656
    // Functions for accessing the user's variables.
657
658
    public function user_id() { return $this->user_id; }
659
    public function firstname() { return $this->firstname; }
660
    public function lastname() { return $this->lastname; }
661
    public function password() { return $this->password; }
662
    public function email() { return $this->email; }
663
    public function postcode() { return $this->postcode; }
664
    public function url() { return $this->url; }
665
    public function lastvisit() { return $this->lastvisit; }
666
    public function facebook_id() { return $this->facebook_id; }
667
    public function facebook_token() { return $this->facebook_token; }
668
    public function facebook_user() { return $this->facebook_user; }
669
670
    public function registrationtime() { return $this->registrationtime; }
671
    public function registrationip() { return $this->registrationip; }
672
    public function optin() { return $this->optin; }
673
    // Don't use the status to check access privileges - use the is_able_to() function.
674
    // But you might use status() to return text to display, describing a user.
675
    // We can then change what status() does in the future if our permissions system
676
    // changes.
677
    public function status() { return $this->status; }
678
    public function deleted() { return $this->deleted; }
679
    public function confirmed() { return $this->confirmed; }
680
681
682 19
    public function postcode_is_set() {
683
        // So we can tell if the, er, postcode is set or not.
684
        // Could maybe put some validation in here at some point.
685 19
        if ($this->postcode != '') {
686
            return true;
687
        } else {
688 19
            return false;
689
        }
690
    }
691
692
693
/////////// PRIVATE FUNCTIONS BELOW... ////////////////
694
695 2
    public function _update($details) {
696
        // Update a user's info.
697
        // DO NOT call this function direct.
698
        // Call either $this->update_other_user() or $this->update_self().
699
700
        // $details is an array like that in $this->add().
701 2
        global $PAGE;
702
703
        // Update email alerts if email address changed
704 2
        if (isset($details['email']) && $this->email != $details['email']) {
705 1
            $this->db->query('UPDATE alerts SET email = :details_email WHERE email = :email',
706
            array(
707 1
                ':details_email' => $details['email'],
708 1
                ':email' => $this->email
709
            ));
710
        }
711
712
        // These are used to put optional fragments of SQL in, depending
713
        // on whether we're changing those things or not.
714 2
        $passwordsql = "";
715 2
        $deletedsql = "";
716 2
        $confirmedsql = "";
717 2
        $statussql = "";
718 2
        $emailsql = '';
719
720 2
        $params = array();
721
722 2
        if (isset($details["password"]) && $details["password"] != "") {
723
            // The password is being updated.
724
            // If not, the password fields on the form will be left blank
725
            // so we don't want to overwrite the user's pw in the DB!
726
727
            $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
728
729
            $passwordsql = "password = :password, ";
730
            $params[':password'] = $passwordforDB;
731
        }
732
733 2
        if (isset($details["deleted"])) {
734
            // 'deleted' won't always be an option (ie, if the user is updating
735
            // their own info).
736
            if ($details['deleted'] == true) {
737
                $del = '1';
738
            } elseif ($details['deleted'] == false) {
739
                $del = '0';
740
            }
741
            if (isset($del)) {
742
                $deletedsql = "deleted  = '$del', ";
743
            }
744
        }
745
746 2
        if (isset($details["confirmed"])) {
747
            // 'confirmed' won't always be an option (ie, if the user is updating
748
            // their own info).
749
            if ($details['confirmed'] == true) {
750
                $con = '1';
751
            } elseif ($details['confirmed'] == false) {
752
                $con = '0';
753
            }
754
            if (isset($con)) {
755
                $confirmedsql = "confirmed  = '$con', ";
756
            }
757
        }
758
759 2
        if (isset($details["status"]) && $details["status"] != "") {
760
            // 'status' won't always be an option (ie, if the user is updating
761
            // their own info.
762
            $statussql = "status = :status, ";
763
            $params[':status'] = $details['status'];
764
765
        }
766
767 2
        if (isset($details['email']) && $details['email']) {
768 1
            $emailsql = "email = :email, ";
769 1
            $params[':email'] = $details['email'];
770
        }
771
772
        // Convert internal true/false variables to MySQL BOOL 1/0 variables.
773 2
        $optin = $details["optin"] == true ? 1 : 0;
774
775 2
        $q = $this->db->query("UPDATE users
776
                        SET     firstname   = :firstname,
777
                                lastname    = :lastname,
778
                                postcode    = :postcode,
779
                                url         = :url,"
780 2
                                . $passwordsql
781 2
                                . $deletedsql
782 2
                                . $confirmedsql
783 2
                                . $emailsql
784 2
                                . $statussql . "
785
                                optin       = :optin
786
                        WHERE   user_id     = :user_id
787 2
                        ", array_merge($params, array(
788 2
                            ':firstname' => $details['firstname'],
789 2
                            ':lastname' => $details['lastname'],
790 2
                            ':postcode' => $details['postcode'],
791 2
                            ':url' => $details['url'],
792 2
                            ':optin' => $optin,
793 2
                            ':user_id' => $details['user_id']
794
                        )));
795
796
        // If we're returning to
797
        // $this->update_self() then $THEUSER will have its variables
798
        // updated if everything went well.
799 2
        if ($q->success()) {
800 2
            return $details;
801
802
        } else {
803
            $PAGE->error_message ("Sorry, we were unable to update user id '" . _htmlentities($details["user_id"]) . "'");
804
805
            return false;
806
        }
807
808
809
    }
810
811
812
813
814
815
} // End USER class
816
817
818
819
820
821
822
class THEUSER extends USER {
823
824
    // Handles all the login/out functionality and checking for the user
825
    // who is using the site right NOW. Yes, him, over there.
826
827
    // This will become true if all goes well...
828
    public $loggedin = false;
829
    public $facebook_user = false;
830
831
832 3
    public function __construct() {
833
        // This function is run automatically when a THEUSER
834
        // object is instantiated.
835
836 3
        $this->db = new ParlDB;
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
837
838
        // We look at the user's cookie and see if it's valid.
839
        // If so, we're going to log them in.
840
841
        // A user's cookie is of the form:
842
        // 123.blahblahblah
843
        // Where '123' is a user id, and 'blahblahblah' is an md5 hash of the
844
        // encrypted password we've stored in the db.
845
        // (Maybe we could just put the encrypted pw in the cookie and md5ing
846
        // it is overkill? Whatever, it works.)
847
848 3
        $cookie = get_cookie_var("epuser_id"); // In includes/utility.php.
849
850 3
        if ($cookie == '') {
851
            $cookie = get_cookie_var("facebook_id");
852
            if ($cookie != '') {
853
              $this->facebook_user = True;
854
              twfy_debug("THEUSER", "is facebook login");
855
            }
856
        }
857
858 3
        if ($cookie == '') {
859
            twfy_debug("THEUSER init FAILED", "No cookie set");
860
            $this->loggedin = false;
861
862 3
        } elseif (preg_match("/([[:alnum:]]*)\.([[:alnum:]]*)/", $cookie, $matches)) {
863
864 3
            if (is_numeric($matches[1])) {
865
866 3
                $success = $this->init($matches[1]);
867
868 3
                if ($success) {
869
                    // We got all the user's data from the DB.
870
871
                    // But we need to check the password before we log them in.
872
                    // And make sure the user hasn't been "deleted".
873
874 3
                    if ($this->facebook_user) {
875
                      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...
876
                          twfy_debug ("THEUSER", "init SUCCESS: setting as logged in");
877
                          $this->loggedin = true;
878
                      } elseif (md5 ($this->facebook_token()) != $matches[2]) {
879
                          twfy_debug ("THEUSER", "init FAILED: Facebook token doesn't match cookie");
880
                          $this->loggedin = false;
881
                      } else {
882
                          twfy_debug ("THEUSER", "init FAILED: User is deleted");
883
                          $this->loggedin = false;
884
                      }
885
                    } else {
886 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...
887
                          // The correct password is in the cookie,
888
                          // and the user isn't deleted, so set the user to be logged in.
889
890
                          // This would be an appropriate place to call other functions
891
                          // that might set user info that only a logged-in user is going
892
                          // to need. Their preferences and saved things or something.
893
894
895
                          twfy_debug ("THEUSER init SUCCEEDED", "setting as logged in");
896
                          $this->loggedin = true;
897
898 3
                      } elseif (md5 ($this->password()) != $matches[2]) {
899 3
                          twfy_debug ("THEUSER init FAILED", "Password doesn't match cookie");
900 3
                          $this->loggedin = false;
901
                      } else {
902
                          twfy_debug ("THEUSER init FAILED", "User is deleted");
903 3
                          $this->loggedin = false;
904
                      }
905
                    }
906
907
                } else {
908
                    twfy_debug ("THEUSER init FAILED", "didn't get 1 row from db");
909 3
                    $this->loggedin = false;
910
                }
911
912
            } else {
913
                twfy_debug ("THEUSER init FAILED", "cookie's user_id is not numeric");
914 3
                $this->loggedin = false;
915
            }
916
917
        } else {
918
            twfy_debug ("THEUSER init FAILED", "cookie is not of the correct form");
919
            $this->loggedin = false;
920
        }
921
922
        // If a user is logged in they *might* have set their own postcode.
923
        // If they aren't logged in, or they haven't set one, then we may
924
        // have set a postcode for them when they searched for their MP.
925
        // If so, we'll use that as $this->postcode.
926 3
        if ($this->postcode == '') {
927 3
            if (get_cookie_var(POSTCODE_COOKIE) != '') {
928
                $pc = get_cookie_var(POSTCODE_COOKIE);
929
930
                $this->set_postcode_cookie($pc);
931
            }
932
        }
933
934 3
        $this->update_lastvisit();
935
936 3
    } // End THEUSER()
937
938 3
    public function update_lastvisit() {
939
940 3
        if ($this->isloggedin()) {
941
            // Set last_visit to now.
942
            $date_now = gmdate("Y-m-d H:i:s");
943
            $this->db->query("UPDATE users SET lastvisit = :lastvisit WHERE user_id = :user_id",
944
                [ ':lastvisit' => $date_now, ':user_id' => $this->user_id() ]);
945
946
            $this->lastvisit = $date_now;
947
        }
948 3
    }
949
950
    // For completeness, but it's better to call $this->isloggedin()
951
    // if you want to check the log in status.
952
    public function loggedin() { return $this->loggedin; }
953
954
955
956 7
    public function isloggedin() {
957
        // Call this function to check if the user is successfully logged in.
958
959 7
        if ($this->loggedin()) {
960 2
            twfy_debug("THEUSER", "isloggedin: true");
961
962 2
            return true;
963
        } else {
964 7
            twfy_debug("THEUSER", "isloggedin: false");
965
966 7
            return false;
967
        }
968
    }
969
970
971
    public function isvalid($email, $userenteredpassword) {
972
        // Returns true if this email and plaintext password match a user in the db.
973
        // If false returns an array of form error messages.
974
975
        // We use this on the log in page to check if the details the user entered
976
        // are correct. We can then continue with logging the user in (taking into
977
        // account their cookie remembering settings etc) with $this->login().
978
979
        // This error string is shared between both email and password errors to
980
        // prevent leaking of account existence.
981
982
        $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.';
983
984
        $q = $this->db->query("SELECT user_id, password, deleted, confirmed FROM users WHERE email = :email", array(':email' => $email))->first();
985
986
        if ($q) {
987
            // OK.
988
            $dbpassword = $q["password"];
989
            if (password_verify($userenteredpassword, $dbpassword)) {
990
                $this->user_id  = $q["user_id"];
991
                $this->password = $dbpassword;
992
                // We'll need these when we're going to log in.
993
                $this->deleted  = $q["deleted"] == 1 ? true : false;
994
                $this->confirmed = $q["confirmed"] == 1 ? true : false;
995
996
                return true;
997
998
            } else {
999
                // Failed.
1000
                return array ("invalidemail" => $error_string);
1001
1002
            }
1003
1004
        } else {
1005
            // Failed.
1006
            return array ("invalidemail" => $error_string);
1007
        }
1008
1009
    }
1010
1011 4
    public function has_postcode() {
1012 4
        $has_postcode = false;
1013 4
        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...
1014
            $has_postcode = true;
1015
        }
1016 4
        return $has_postcode;
1017
    }
1018
1019
1020
    public function facebook_login($returl="", $expire, $accessToken) {
1021
        global $PAGE;
1022
1023
        twfy_debug("THEUSER", "Faceook login, user_id " . $this->user_id);
1024
        twfy_debug("THEUSER", "Faceook login, facebook_id " . $this->facebook_id);
1025
        twfy_debug("THEUSER", "Faceook login, email" . $this->email);
1026
        if ($this->facebook_id() == "") {
1027
            $PAGE->error_message ("We don't have a facebook id for this user.", true);
1028
1029
            return;
1030
        }
1031
1032
        twfy_debug("THEUSER", "Faceook login, facebook_token: " . $accessToken);
1033
1034
        $q = $this->db->query ("UPDATE users SET facebook_token = :token WHERE email = :email",
1035
            array(
1036
                ':token' => $accessToken,
1037
                ':email' => $this->email
1038
            ));
1039
1040
        if (!$q->success()) {
1041
            $PAGE->error_message ("There was a problem logging you in", true);
1042
            twfy_debug("THEUSER", "Faceook login, failed to set accessToken");
1043
1044
            return false;
1045
        }
1046
1047
        // facebook login users probably don't have a password
1048
        $cookie = $this->user_id() . "." . md5 ($accessToken);
1049
        twfy_debug("THEUSER", "Faceook login, cookie: " . $cookie);
1050
1051
        twfy_debug("USER", "logging in user from facebook " . $this->user_id);
1052
1053
        $this->loggedin = True;
1054
        $this->_login($returl, $expire, $cookie, 'facebook_id');
1055
        return true;
1056
    }
1057
1058
    public function login($returl="", $expire) {
1059
1060
        // This is used to log the user in. Duh.
1061
        // You should already have checked the user's email and password using
1062
        // $this->isvalid()
1063
        // That will have set $this->user_id and $this->password, allowing the
1064
        // login to proceed...
1065
1066
        // $expire is either 'session' or 'never' - for the cookie.
1067
1068
        // $returl is the URL to redirect the user to after log in, generally the
1069
        // page they were on before. But if it doesn't exist, they'll just go to
1070
        // the front page.
1071
        global $PAGE;
1072
1073
        if ($returl == "") {
1074
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1075
            $returl = $URL->generate();
1076
        }
1077
1078
        // Various checks about the user - if they fail, we exit.
1079
        if ($this->user_id() == "" || $this->password == "") {
1080
            $PAGE->error_message ("We don't have the user_id or password to make the cookie.", true);
1081
1082
            return;
1083
        } elseif ($this->deleted) {
1084
            $PAGE->error_message ("This user has been deleted.", true);
1085
1086
            return;
1087
        } elseif (!$this->confirmed) {
1088
            $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);
1089
1090
            return;
1091
        }
1092
1093
        // Reminder: $this->password is actually a hashed version of the plaintext pw.
1094
        $cookie = $this->user_id() . "." . md5 ($this->password());
1095
1096
        $this->_login($returl, $expire, $cookie);
1097
    }
1098
1099
    private function _login($returl, $expire, $cookie, $cookie_name = 'epuser_id') {
1100
        // Unset any existing postcode cookie.
1101
        // This will be the postcode the user set for themselves as a non-logged-in
1102
        // user. We don't want it hanging around as it causes confusion.
1103
        $this->unset_postcode_cookie();
1104
1105
        twfy_debug("THEUSER", "expire is " . $expire);
1106
1107
        $cookie_expires = 0;
1108
        if ($expire == 'never') {
1109
            twfy_debug("THEUSER", "cookie never expires");
1110
            $cookie_expires = time()+86400*365*20;
1111
        } elseif (is_int($expire) && $expire > time()) {
1112
            twfy_debug("THEUSER", "cookie expires at " . $expire);
1113
            $cookie_expires = $expire;
1114
        } else {
1115
            twfy_debug("THEUSER", "cookie expires with session");
1116
        }
1117
1118
        header("Location: $returl");
1119
        setcookie($cookie_name, $cookie, $cookie_expires, '/', COOKIEDOMAIN);
1120
    }
1121
1122
1123
    public function logout($returl) {
1124
1125
        // $returl is the URL to redirect the user to after log in, generally the
1126
        // page they were on before. But if it doesn't exist, they'll just go to
1127
        // the front page.
1128
1129
        if ($returl == '') {
1130
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1131
            $returl = $URL->generate();
1132
        }
1133
1134
        // get_cookie_var() is in includes/utility.php
1135
        if (get_cookie_var("epuser_id") != "") {
1136
            // They're logged in, so set the cookie to empty.
1137
            header("Location: $returl");
1138
            setcookie('epuser_id', '', time() - 86400, '/', COOKIEDOMAIN);
1139
        }
1140
1141
        if (get_cookie_var("facebook_id") != "") {
1142
            // They're logged in, so set the cookie to empty.
1143
            header("Location: $returl");
1144
            setcookie('facebook_id', '', time() - 86400, '/', COOKIEDOMAIN);
1145
        }
1146
    }
1147
1148 2
    public function confirm_email($token, $redirect=true) {
1149 2
        $arg = '';
1150 2
        if (strstr($token, '::')) $arg = '::';
1151 2
        if (strstr($token, '-')) $arg = '-';
1152 2
        list($user_id, $registrationtoken) = explode($arg, $token);
1153
1154 2
        if (!is_numeric($user_id) || $registrationtoken == '') {
1155
            return false;
1156
        }
1157 2
        $q = $this->db->query("SELECT expires, data
1158
            FROM    tokens
1159
            WHERE   token = :token
1160
            AND   type = 'E'
1161 2
        ", array (':token' => $registrationtoken))->first();
1162
1163 2
        if ($q) {
1164 2
            $expires = $q['expires'];
1165 2
            $expire_time = strtotime($expires);
1166 2
            if ( $expire_time < time() ) {
1167 1
                global $PAGE;
1168 1
                if ($PAGE && $redirect) {
1169
                    $PAGE->error_message ("Sorry, that token seems to have expired");
1170
                }
1171
1172 1
                return false;
1173
            }
1174
1175 1
            list( $user_id, $email ) = explode('::', $q['data']);
1176
1177
            // if we are logged in as someone else don't change the email
1178 1
            if ( $this->user_id() != 0 && $this->user_id() != $user_id ) {
1179
                return false;
1180
            }
1181
1182
            // if the user isn't logged in then try and load the
1183
            // details
1184 1
            if ($this->user_id() == 0 && !$this->init($user_id)) {
1185
                return false;
1186
            }
1187
1188
            $details = array(
1189 1
                'email' => $email,
1190 1
                'firstname' => $this->firstname(),
1191 1
                'lastname' => $this->lastname(),
1192 1
                'postcode' => $this->postcode(),
1193 1
                'url' => $this->url(),
1194 1
                'optin' => $this->optin(),
1195 1
                'user_id' => $user_id,
1196
            );
1197 1
            $ret = $this->_update($details);
1198
1199 1
            if ($ret) {
1200
                // and remove the token to be tidy
1201 1
                $this->db->query("DELETE
1202
                    FROM    tokens
1203
                    WHERE   token = :token
1204
                    AND   type = 'E'
1205 1
                ", array(':token' => $registrationtoken));
1206
1207 1
                $this->email = $email;
1208 1
                $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1209 1
                $URL->insert(array('email'=>'t'));
1210 1
                $redirecturl = $URL->generate();
1211 1
                if ($redirect) {
1212
                    $this->login($redirecturl, 'session');
1213
                } else {
1214 1
                    return true;
1215
                }
1216
            } else {
1217
                return false;
1218
            }
1219
        } else {
1220
            return false;
1221
        }
1222
1223
    }
1224
1225
    public function confirm($token) {
1226
        // The user has clicked the link in their confirmation email
1227
        // and the confirm page has passed the token from the URL to here.
1228
        // If all goes well they'll be confirmed and then logged in.
1229
1230
        // Split the token into its parts.
1231
        $arg = '';
1232
        if (strstr($token, '::')) $arg = '::';
1233
        if (strstr($token, '-')) $arg = '-';
1234
        list($user_id, $registrationtoken) = explode($arg, $token);
1235
1236
        if (!is_numeric($user_id) || $registrationtoken == '') {
1237
            return false;
1238
        }
1239
1240
        $q = $this->db->query("SELECT email, password, postcode
1241
                        FROM    users
1242
                        WHERE   user_id = :user_id
1243
                        AND     registrationtoken = :token
1244
                        ", array(
1245
                            ':user_id' => $user_id,
1246
                            ':token' => $registrationtoken
1247
                        ))->first();
1248
1249
        if ($q) {
1250
1251
            // We'll need these to be set before logging the user in.
1252
            $this->user_id  = $user_id;
1253
            $this->email    = $q['email'];
1254
            $this->password = $q['password'];
1255
1256
            // Set that they're confirmed in the DB.
1257
            $r = $this->db->query("UPDATE users
1258
                            SET     confirmed = '1'
1259
                            WHERE   user_id = :user_id
1260
                            ", array(':user_id' => $user_id));
1261
1262
            if ($q['postcode']) {
1263
                try {
1264
                    $MEMBER = new MEMBER(array('postcode'=>$q['postcode'], 'house'=>HOUSE_TYPE_COMMONS));
1265
                    $pid = $MEMBER->person_id();
1266
                    # This should probably be in the ALERT class
1267
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', array(
1268
                            ':email' => $this->email,
1269
                            ':criteria' => 'speaker:' . $pid
1270
                        ));
1271
                } 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...
1272
                }
1273
            }
1274
1275
            if ($r->success()) {
1276
1277
                $this->confirmed = true;
1278
1279
                $redirecturl = get_http_var('ret');
1280
                if (!$redirecturl || substr($redirecturl, 0, 1) != '/') {
0 ignored issues
show
Bug introduced by
It seems like $redirecturl can also be of type array and array; however, parameter $string of substr() 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

1280
                if (!$redirecturl || substr(/** @scrutinizer ignore-type */ $redirecturl, 0, 1) != '/') {
Loading history...
1281
                    // Log the user in, redirecting them to the confirm page
1282
                    // where they should get a nice welcome message.
1283
                    $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1284
                    $URL->insert(array('welcome'=>'t'));
1285
                    $redirecturl = $URL->generate();
1286
                }
1287
1288
                $this->login($redirecturl, 'session');
1289
1290
            } else {
1291
                // Couldn't set them as confirmed in the DB.
1292
                return false;
1293
            }
1294
1295
        } else {
1296
            // Couldn't find this user in the DB. Maybe the token was
1297
            // wrong or incomplete?
1298
            return false;
1299
        }
1300
    }
1301
1302
    public function confirm_without_token() {
1303
        // If we want to confirm login without a token, e.g. during
1304
        // Facebook registration
1305
        //
1306
        // Note that this doesn't login or redirect the user.
1307
1308
        twfy_debug("THEUSER", "Confirming user without token: " . $this->user_id());
1309
        $q = $this->db->query("SELECT email, password, postcode
1310
                        FROM    users
1311
                        WHERE   user_id = :user_id
1312
                        ", array(
1313
                            ':user_id' => $this->user_id,
1314
                        ))->first();
1315
1316
        if ($q) {
1317
1318
            twfy_debug("THEUSER", "User with ID found to confirm: " . $this->user_id());
1319
            // We'll need these to be set before logging the user in.
1320
            $this->email    = $q['email'];
1321
1322
            // Set that they're confirmed in the DB.
1323
            $r = $this->db->query("UPDATE users
1324
                            SET     confirmed = '1'
1325
                            WHERE   user_id = :user_id
1326
                            ", array(':user_id' => $this->user_id));
1327
1328
            if ($q['postcode']) {
1329
                try {
1330
                    $MEMBER = new MEMBER(array('postcode'=>$q['postcode'], 'house'=>HOUSE_TYPE_COMMONS));
1331
                    $pid = $MEMBER->person_id();
1332
                    # This should probably be in the ALERT class
1333
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', array(
1334
                            ':email' => $this->email,
1335
                            ':criteria' => 'speaker:' . $pid
1336
                        ));
1337
                } 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...
1338
                }
1339
            }
1340
1341
            if ($r->success()) {
1342
                twfy_debug("THEUSER", "User with ID confirmed: " . $this->user_id());
1343
                $this->confirmed = true;
1344
                return true;
1345
            } else {
1346
                twfy_debug("THEUSER", "User with ID not confirmed: " . $this->user_id());
1347
                // Couldn't set them as confirmed in the DB.
1348
                return false;
1349
            }
1350
1351
        } else {
1352
            // Couldn't find this user in the DB. Maybe the token was
1353
            // wrong or incomplete?
1354
            twfy_debug("THEUSER", "User with ID not found to confirm: " . $this->user_id());
1355
            return false;
1356
        }
1357
    }
1358
1359
1360
    public function set_postcode_cookie($pc) {
1361
        // Set the user's postcode.
1362
        // Doesn't change it in the DB, as it's probably mainly for
1363
        // not-logged-in users.
1364
1365
        $this->postcode = $pc;
1366
        if (!headers_sent()) // if in debug mode
1367
            setcookie (POSTCODE_COOKIE, $pc, time()+7*86400, "/", COOKIEDOMAIN);
1368
1369
        twfy_debug('USER', "Set the cookie named '" . POSTCODE_COOKIE . " to '$pc' for " . COOKIEDOMAIN . " domain");
1370
    }
1371
1372
    public function unset_postcode_cookie() {
1373
        if (!headers_sent()) // if in debug mode
1374
            setcookie (POSTCODE_COOKIE, '', time() - 3600, '/', COOKIEDOMAIN);
1375
    }
1376
1377
    // mostly here for updating from facebook where we do not need
1378
    // to confirm the email address
1379
    public function update_self_no_confirm($details) {
1380
        global $THEUSER;
1381
1382
        if ($this->isloggedin()) {
1383
            twfy_debug("THEUSER", "is logged in for update_self");
1384
1385
            // this is checked elsewhere but just in case we check here and
1386
            // bail out to be on the safe side
1387
            if ( isset($details['email'] ) ) {
1388
                if ( $details['email'] != $this->email() && $this->email_exists( $details['email'] ) ) {
1389
                    return false;
1390
                }
1391
            }
1392
1393
            $details["user_id"] = $this->user_id;
1394
1395
            $newdetails = $this->_update($details);
1396
1397
            if ($newdetails) {
1398
                // The user's data was updated, so we'll change the object
1399
                // variables accordingly.
1400
1401
                $this->firstname        = $newdetails["firstname"];
1402
                $this->lastname         = $newdetails["lastname"];
1403
                $this->postcode         = $newdetails["postcode"];
1404
                $this->url              = $newdetails["url"];
1405
                $this->optin            = $newdetails["optin"];
1406
                $this->email            = $newdetails['email'];
1407
                if ($newdetails["password"] != "") {
1408
                    $this->password = $newdetails["password"];
1409
                }
1410
1411
                return true;
1412
            } else {
1413
                return false;
1414
            }
1415
1416
        } else {
1417
            return false;
1418
        }
1419
1420
    }
1421
1422 2
    public function update_self($details, $confirm_email = true) {
1423
        // If the user wants to update their details, call this function.
1424
        // It checks that they're logged in before letting them.
1425
1426
1427
        // $details is an array like that in $this->add().
1428
1429 2
        global $THEUSER;
1430
1431 2
        if ($this->isloggedin()) {
1432
1433
            // this is checked elsewhere but just in case we check here and
1434
            // bail out to be on the safe side
1435 2
            $email = '';
1436 2
            if ( isset($details['email'] ) ) {
1437 1
                if ( $details['email'] != $this->email() && $this->email_exists( $details['email'] ) ) {
1438
                    return false;
1439
                }
1440 1
                $email = $details['email'];
1441 1
                unset($details['email']);
1442
            }
1443 2
            $details["user_id"] = $this->user_id;
1444
1445 2
            $newdetails = $this->_update($details);
1446
1447
            // $newdetails will be an array of details if all went well,
1448
            // false otherwise.
1449
1450 2
            if ($newdetails) {
1451
                // The user's data was updated, so we'll change the object
1452
                // variables accordingly.
1453
1454 2
                $this->firstname        = $newdetails["firstname"];
1455 2
                $this->lastname         = $newdetails["lastname"];
1456 2
                $this->postcode         = $newdetails["postcode"];
1457 2
                $this->url              = $newdetails["url"];
1458 2
                $this->optin            = $newdetails["optin"];
1459 2
                if ($newdetails["password"] != "") {
1460
                    $this->password = $newdetails["password"];
1461
                }
1462
1463 2
                if ($email && $email != $this->email) {
1464 1
                    $token = substr( password_hash($email . microtime(), PASSWORD_BCRYPT), 29, 16 );
1465 1
                    $data = $this->user_id() . '::' . $email;
1466 1
                    $r = $this->db->query("INSERT INTO tokens
1467
                        ( expires, token, type, data )
1468
                        VALUES
1469
                        (
1470
                            DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY),
1471
                            :token,
1472
                            'E',
1473
                            :data
1474
                        )
1475
                    ", array(
1476 1
                        ':token' => $token,
1477 1
                        ':data' => $data
1478
                    ));
1479
1480
                    // send confirmation email here
1481 1
                    if ( $r->success() ) {
1482 1
                        $newdetails['email'] = $email;
1483 1
                        $newdetails['token'] = $token;
1484 1
                        if ($confirm_email) {
1485
                            return $this->send_email_confirmation_email($newdetails);
1486
                        } else {
1487 1
                            return true;
1488
                        }
1489
                    } else {
1490
                        return false;
1491
                    }
1492
                }
1493
1494 1
                return true;
1495
            } else {
1496
                return false;
1497
            }
1498
1499
        } else {
1500
            return false;
1501
        }
1502
1503
    }
1504
1505
}
1506
1507
// Yes, we instantiate a new global $THEUSER object when every page loads.
1508
$THEUSER = new THEUSER;
1509