Failed Conditions
Branch master (3ce7e2)
by Nick
14:43
created

USER::send_email_confirmation_email()   C

Complexity

Conditions 7
Paths 3

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 20
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 35
rs 6.7272
ccs 0
cts 18
cp 0
crap 56
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 57 and the first side effect is on line 1533.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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 {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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 $emailpublic = "";      // boolean - can other users see this user's email?
65
    public $postcode = "";
66
    public $url = "";
67
    public $lastvisit = "";        // Last time the logged-in user loaded a page (GMT).
68
    public $registrationtime = ""; // When they registered (GMT).
69
    public $registrationip = "";   // Where they registered from.
70
    public $optin = "";            // boolean - Do they want emails from us?
71
    public $deleted = "";          // User can't log in or have their info displayed.
72
    public $confirmed = '';        // boolean - Has the user confirmed via email?
73
    public $facebook_id = '';      // Facebook ID for users who login with FB
74
    public $facebook_token = '';   // Facebook token for users who login with FB
75
    // Don't use the status to check access privileges - use the is_able_to() function.
76
    public $status = "Viewer";
77
78
    // If you add more user variables above you should also:
79
    //      Add the approrprate code to $this->add()
80
    //      Add the appropriate code to $this->_update()
81
    //      Add accessor functions way down below...
82
    //      Alter THEUSER->update_self() to update with the new vars, if appropriate.
83
    //      Change things in the add/edit/view user page.
84
85 1
    public function __construct() {
86 1
        $this->db = new ParlDB;
0 ignored issues
show
Bug Best Practice introduced by
The property db does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
87 1
    }
88
89 4
    public function init($user_id) {
90
        // Pass it a user id and it will fetch the user's data from the db
91
        // and put it all in the appropriate variables.
92
        // Returns true if we've found user_id in the DB, false otherwise.
93
94
        // Look for this user_id's details.
95 4
        $q = $this->db->query("SELECT firstname,
96
                                lastname,
97
                                password,
98
                                email,
99
                                emailpublic,
100
                                postcode,
101
                                url,
102
                                lastvisit,
103
                                registrationtime,
104
                                registrationtoken,
105
                                registrationip,
106
                                optin,
107
                                status,
108
                                deleted,
109
                                confirmed,
110
                                facebook_id,
111
                                facebook_token
112
                        FROM    users
113 4
                        WHERE   user_id = :user_id",
114 4
                        array(':user_id' => $user_id));
115
116
117 4
        if ($q->rows() == 1) {
118
            // We've got a user, so set them up.
119
120 4
            $this->user_id              = $user_id;
121 4
            $this->firstname            = $q->field(0,"firstname");
122 4
            $this->lastname             = $q->field(0,"lastname");
123 4
            $this->password             = $q->field(0,"password");
124 4
            $this->email                = $q->field(0,"email");
125 4
            $this->emailpublic = $q->field(0,"emailpublic") == 1 ? true : false;
126 4
            $this->postcode             = $q->field(0,"postcode");
127 4
            $this->facebook_id          = $q->field(0,"facebook_id");
128 4
            $this->facebook_token       = $q->field(0,"facebook_token");
129 4
            $this->url                  = $q->field(0,"url");
130 4
            $this->lastvisit            = $q->field(0,"lastvisit");
131 4
            $this->registrationtoken    = $q->field(0, '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...
132 4
            $this->registrationtime     = $q->field(0,"registrationtime");
133 4
            $this->registrationip       = $q->field(0,"registrationip");
134 4
            $this->optin = $q->field(0,"optin") == 1 ? true : false;
135 4
            $this->status               = $q->field(0,"status");
136 4
            $this->deleted = $q->field(0,"deleted") == 1 ? true : false;
137 4
            $this->confirmed = $q->field(0,"confirmed") == 1 ? true : false;
138
139 4
            return true;
140
141
        } elseif ($q->rows() > 1) {
142
            // And, yes, if we've ended up with more than one row returned
143
            // we're going to show an error too, just in case.
144
            // *Should* never happen...
145
            return false;
146
            twfy_debug("USER", "There is more than one 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...
147
148
        } else {
149
            return false;
150
            twfy_debug("USER", "There is no user with an id of '" . _htmlentities($user_id) . "'");
151
        }
152
153
    }
154
155 1
    public function add($details, $confirmation_required=true) {
156
        // Adds a new user's info into the db.
157
        // Then optionally (and usually) calls another function to
158
        // send them a confirmation email.
159
160
        // $details is an associative array of all the user's details, of the form:
161
        // array (
162
        //      "firstname" => "Fred",
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
163
        //      "lastname"  => "Bloggs",
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
164
        //      etc... using the same keys as the object variable names.
165
        // )
166
        // The BOOL variables (eg, optin) will be true or false and will need to be
167
        // converted to 1/0 for MySQL.
168 1
        global $REMOTE_ADDR;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
169
170 1
        $registrationtime = gmdate("YmdHis");
171
172 1
        $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
173
174 1
        if (!isset($details["status"])) {
175
            $details["status"] = "User";
176
        }
177
178 1
        if (!isset($details["facebook_id"])) {
179 1
            $details["facebook_id"] = "";
180 1
        }
181
182 1
        $optin = $details["optin"] == true ? 1 : 0;
183
184 1
        $emailpublic = $details["emailpublic"] == true ? 1 : 0;
185
186 1
        $q = $this->db->query("INSERT INTO users (
187
                firstname,
188
                lastname,
189
                email,
190
                emailpublic,
191
                postcode,
192
                url,
193
                password,
194
                optin,
195
                status,
196
                registrationtime,
197
                registrationip,
198
                facebook_id,
199
                deleted
200
            ) VALUES (
201
                :firstname,
202
                :lastname,
203
                :email,
204
                :emailpublic,
205
                :postcode,
206
                :url,
207
                :password,
208
                :optin,
209
                :status,
210
                :registrationtime,
211
                :registrationip,
212
                :facebook_id,
213
                '0'
214
            )
215 1
        ", array(
216 1
            ':firstname' => $details["firstname"],
217 1
            ':lastname' => $details["lastname"],
218 1
            ':email' => $details["email"],
219 1
            ':emailpublic' => $emailpublic,
220 1
            ':postcode' => $details["postcode"],
221 1
            ':url' => $details["url"],
222 1
            ':password' => $passwordforDB,
223 1
            ':optin' => $optin,
224 1
            ':status' => $details["status"],
225 1
            ':registrationtime' => $registrationtime,
226 1
            ':facebook_id' => $details["facebook_id"],
227
            ':registrationip' => $REMOTE_ADDR
228 1
        ));
229
230 1
        if ($q->success()) {
231
            // Set these so we can log in.
232
            // Except we no longer automatically log new users in, we
233
            // send them an email. So this may not be required.
234 1
            $this->user_id = $q->insert_id();
235 1
            $this->password = $passwordforDB;
236 1
            $this->facebook_id = $details["facebook_id"];
237
238
            // We have to set the user's registration token.
239
            // This will be sent to them via email, so we can confirm they exist.
240
            // The token will be the first 16 characters of a hash.
241
242 1
            $token = substr( password_hash($details["email"] . microtime(), PASSWORD_BCRYPT), 29, 16 );
243
244
            // Full stops don't work well at the end of URLs in emails,
245
            // so replace them. We won't be doing anything clever with the hash
246
            // stuff, just need to match this token.
247
248 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...
249
250
            // Add that to the DB.
251 1
            $r = $this->db->query("UPDATE users
252
                            SET registrationtoken = :registrationtoken
253
                            WHERE   user_id = :user_id
254 1
                            ", array (
255 1
                                ':registrationtoken' => $this->registrationtoken,
256 1
                                ':user_id' => $this->user_id
257 1
                            ));
258
259 1
            if ($r->success()) {
260
                // Updated DB OK.
261
262 1
                if ($details['mp_alert'] && $details['postcode']) {
263
                    $MEMBER = new MEMBER(array('postcode'=>$details['postcode'], 'house'=>HOUSE_TYPE_COMMONS));
264
                    $pid = $MEMBER->person_id();
265
                    # No confirmation email, but don't automatically confirm
266
                    $ALERT = new ALERT;
267
                    $ALERT->add(array(
268
                        'email' => $details['email'],
269
                        'pid' => $pid,
270
                        'pc' => $details['postcode'],
271
                    ), false, false);
272
                }
273
274 1
                if ($confirmation_required) {
275
                    // Right, send the email...
276
                    $success = $this->send_confirmation_email($details);
277
278
                    if ($success) {
279
                        // All is good in the world!
280
                        return true;
281
                    } else {
282
                        // Couldn't send the email.
283
                        return false;
284
                    }
285
                } else {
286
                    // No confirmation email needed.
287 1
                    return true;
288
                }
289
            } else {
290
                // Couldn't add the registration token to the DB.
291
                return false;
292
            }
293
294
        } else {
295
            // Couldn't add the user's data to the DB.
296
            return false;
297
        }
298
    }
299
300
    public function add_facebook_id($facebook_id) {
301
        $q = $this->db->query ("UPDATE users SET facebook_id = :facebook_id WHERE email = :email",
302
            array(
303
                ':facebook_id' => $facebook_id,
304
                ':email' => $this->email
305
            ));
306
307
        if ($q->success()) {
308
            $this->facebook_id = $facebook_id;
309
310
            return $facebook_id;
311
        } else {
312
            return false;
313
        }
314
    }
315
316
    public function send_email_confirmation_email($details) {
317
        // A brief check of the facts...
318
        if (!is_numeric($this->user_id) ||
319
            !isset($details['email']) ||
320
            $details['email'] == '' ||
321
            !isset($details['token']) ||
322
            $details['token'] == '' ) {
323
            return false;
324
        }
325
326
        // We prefix the registration token with the user's id and '-'.
327
        // Not for any particularly good reason, but we do.
328
329
        $urltoken = $this->user_id . '-' . $details['token'];
330
331
        $confirmurl = 'https://' . DOMAIN . '/E/' . $urltoken;
1 ignored issue
show
Bug introduced by
The constant DOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
332
333
        // Arrays we need to send a templated email.
334
        $data = array (
335
            'to'        => $details['email'],
336
            'template'  => 'email_confirmation'
337
        );
338
339
        $merge = array (
340
            'FIRSTNAME'     => $details['firstname'],
341
            'LASTNAME'      => $details['lastname'],
342
            'CONFIRMURL'    => $confirmurl
343
        );
344
345
        $success = send_template_email($data, $merge);
346
347
        if ($success) {
348
            return true;
349
        } else {
350
            return false;
351
        }
352
    }
353
354
    public function send_confirmation_email($details) {
355
        // After we've add()ed a user we'll probably be sending them
356
        // a confirmation email with a link to confirm their address.
357
358
        // $details is the array we just sent to add(), and which it's
359
        // passed on to us here.
360
361
        // A brief check of the facts...
362
        if (!is_numeric($this->user_id) ||
363
            !isset($details['email']) ||
364
            $details['email'] == '') {
365
            return false;
366
        }
367
368
        // We prefix the registration token with the user's id and '-'.
369
        // Not for any particularly good reason, but we do.
370
371
        $urltoken = $this->user_id . '-' . $this->registrationtoken;
372
373
        $confirmurl = 'https://' . DOMAIN . '/U/' . $urltoken;
1 ignored issue
show
Bug introduced by
The constant DOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
374
375
        // Arrays we need to send a templated email.
376
        $data = array (
377
            'to'        => $details['email'],
378
            'template'  => 'join_confirmation'
379
        );
380
381
        $merge = array (
382
            'FIRSTNAME'     => $details['firstname'],
383
            'LASTNAME'      => $details['lastname'],
384
            'CONFIRMURL'    => $confirmurl
385
        );
386
387
        $success = send_template_email($data, $merge);
388
389
        if ($success) {
390
            return true;
391
        } else {
392
            return false;
393
        }
394
    }
395
396
397
    public function update_other_user($details) {
398
        // If someone (like an admin) is updating another user, call this
399
        // function. It checks their privileges before letting them.
400
401
        // $details is an array like that in $this->add().
402
        // It must include a 'user_id' element!
403
404
        global $THEUSER;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
405
406
        if (!isset($details["user_id"])) {
407
            return false;
408
409
        } elseif ($THEUSER->is_able_to("edituser")) {
410
411
            // If the user doing the updating has appropriate privileges...
412
413
            $newdetails = $this->_update($details);
414
415
            // $newdetails will be an array of details if all went well,
416
            // false otherwise.
417
            if ($newdetails) {
418
                return true;
419
            } else {
420
                return false;
421
            }
422
423
        } else {
424
            return false;
425
426
        }
427
    }
428
429
430
431
    public function change_password($email) {
432
433
        // This function is called from the Change Password page.
434
        // It will create a new password for the user with $email address.
435
        // If all goes OK it will return the plaintext version of the password.
436
        // Otherwise it returns false.
437
438
        if ($this->email_exists($email)) {
439
440
            $this->email = $email;
441
            for (;;) {
442
443
                $pwd=null;
444
                $o=null;
445
446
                // Generates the password ....
447
                for ($x=0; $x < 6;) {
448
                    $y = rand(1,1000);
449
                    if($y>350 && $y<601) $d=chr(rand(48,57));
450
                    if($y<351) $d=chr(rand(65,90));
451
                    if($y>600) $d=chr(rand(97,122));
452
                    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...
453
                        $o=$d; $pwd.=$d; $x++;
454
                    }
455
                }
456
457
                // If the PW fits your purpose (e.g. this regexpression) return it, else make a new one
458
                // (You can change this regular-expression how you want ....)
459
                if (preg_match("/^[a-zA-Z]{1}([a-zA-Z]+[0-9][a-zA-Z]+)+/",$pwd)) {
460
                    break;
461
                }
462
463
            }
464
            $pwd = strtoupper($pwd);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pwd does not seem to be defined for all execution paths leading up to this point.
Loading history...
465
466
        // End password generating stuff.
467
468
        } else {
469
470
            // Email didn't exist.
471
            return false;
472
473
        }
474
475
        $passwordforDB = password_hash($pwd, PASSWORD_BCRYPT);
476
477
        $q = $this->db->query ("UPDATE users SET password = :password WHERE email = :email",
478
            array(
479
                ':password' => $passwordforDB,
480
                ':email' => $email
481
            ));
482
483
        if ($q->success()) {
484
            $this->password = $pwd;
485
486
            return $pwd;
487
488
        } else {
489
            return false;
490
        }
491
492
    }
493
494
    public function send_password_reminder() {
495
        global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
496
497
        // You'll probably have just called $this->change_password().
498
499
        if ($this->email() == '') {
500
            $PAGE->error_message("No email set for this user, so can't send a password reminder.");
501
502
            return false;
503
        }
504
505
        $data = array (
506
            'to'            => $this->email(),
507
            'template'      => 'new_password'
508
        );
509
510
        $URL = new \MySociety\TheyWorkForYou\Url("userlogin");
511
512
        $merge = array (
513
            'EMAIL'         => $this->email(),
514
            'LOGINURL'      => "https://" . DOMAIN . $URL->generate(),
1 ignored issue
show
Bug introduced by
The constant DOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
515
            'PASSWORD'      => $this->password()
516
        );
517
518
        // send_template_email in utility.php.
519
        $success = send_template_email($data, $merge);
520
521
        return $success;
522
523
    }
524
525
526
527
528
    public function id_exists($user_id) {
529
        // Returns true if there's a user with this user_id.
530
531
        if (is_numeric($user_id)) {
532
            $q = $this->db->query("SELECT user_id FROM users WHERE user_id = :user_id",
533
                array(':user_id' => $user_id));
534
            if ($q->rows() > 0) {
535
                return true;
536
            } else {
537
                return false;
538
            }
539
        } else {
540
            return false;
541
        }
542
543
    }
544
545
546 1
    public function email_exists($email, $return_id = false) {
547
        // Returns true if there's a user with this email address.
548
549 1
        if ($email != "") {
550 1
            $q = $this->db->query("SELECT user_id FROM users WHERE email = :email", array(':email' => $email));
551 1
            if ($q->rows() > 0) {
552
                if ($return_id) {
553
                    $row = $q->row(0);
554
555
                    return $row['user_id'];
556
                }
557
558
                return true;
559
            } else {
560 1
                return false;
561
            }
562
        } else {
563
            return false;
564
        }
565
566
    }
567
568
    public function facebook_id_exists($id, $return_id = false) {
569
        // Returns true if there's a user with this facebook id.
570
571
        if ($id!= "") {
572
            $q = $this->db->query("SELECT user_id FROM users WHERE facebook_id = :id", array(':id' => $id));
573
            if ($q->rows() > 0) {
574
                if ($return_id) {
575
                    $row = $q->row(0);
576
577
                    return $row['user_id'];
578
                }
579
580
                return true;
581
            } else {
582
                return false;
583
            }
584
        } else {
585
            return false;
586
        }
587
588
    }
589
590
    public function is_able_to($action) {
591
        // Call this function to find out if a user is allowed to do something.
592
        // It uses the user's status to return true or false.
593
        // Possible actions:
594
        //  "addcomment"
595
        //  "reportcomment"
596
        //  "edituser"
597
        global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
598
599
        $status = $this->status();
600
601
        switch ($action) {
602
603
            // You can add more below as they're needed...
604
            // But keep them in alphabetical order!
605
606
            case "deletecomment": // Delete comments.
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
607
608
                switch ($status) {
609
                    case "User":            return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
610
                    case "Moderator":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
611
                    case "Administrator":   return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
612
                    case "Superuser":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
613
                    default: /* Viewer */   return false;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
614
                }
615
616
            case "edituser":
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
617
618
                switch ($status) {
619
                    case "User":            return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
620
                    case "Moderator":       return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
621
                    case "Administrator":   return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
622
                    case "Superuser":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
623
                    default: /* Viewer */   return false;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
624
                }
625
626
            case "reportcomment":   // Report a comment for moderation.
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
627
628
                switch ($status) {
629
                    case "User":            return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
630
                    case "Moderator":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
631
                    case "Administrator":   return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
632
                    case "Superuser":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
633
                    default: /* Viewer */   return true;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
634
                }
635
636
            case "viewadminsection":    // Access pages in the Admin section.
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
637
638
                switch ($status) {
639
                    case "User":            return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
640
                    case "Moderator":       return false;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
641
                    case "Administrator":   return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
642
                    case "Superuser":       return true;
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
643
                    default: /* Viewer */   return false;
0 ignored issues
show
Coding Style introduced by
The default body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a default statement must start on the line immediately following the statement.

switch ($expr) {
    default:
        doSomething(); //right
        break;
}


switch ($expr) {
    default:

        doSomething(); //wrong
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
644
                }
645
646
            case "voteonhansard":   // Rate hansard things interesting/not.
647
                /* Everyone */              return true;
0 ignored issues
show
Coding Style introduced by
Terminating statement must be on a line by itself

As per the PSR-2 coding standard, the break (or other terminating) statement must be on a line of its own.

switch ($expr) {
     case "A":
         doSomething();
         break; //wrong
     case "B":
         doSomething();
         break; //right
     case "C:":
         doSomething();
         return true; //right
 }

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
648
649
            default:
650
                $PAGE->error_message ("You need to set permissions for '$action'!");
651
652
                return false;
653
654
655
        }
656
657
658
659
    }
660
661
    // Same for every user...
662
    // Just returns an array of the possible statuses a user could have.
663
    // Handy for forms where you edit/view users etc.
664
    public function possible_statuses() {
665
        // Maybe there's a way of fetching these from the DB,
666
        // so we don't duplicate them here...?
667
668
        $statuses = array ("Viewer", "User", "Moderator", "Administrator", "Superuser");
669
670
        return $statuses;
671
672
    }
673
674
675
676
    // Functions for accessing the user's variables.
677
678
    public function user_id() { return $this->user_id; }
679
    public function firstname() { return $this->firstname; }
680
    public function lastname() { return $this->lastname; }
681
    public function password() { return $this->password; }
682
    public function email() { return $this->email; }
683
    public function emailpublic() { return $this->emailpublic; }
684
    public function postcode() { return $this->postcode; }
685
    public function url() { return $this->url; }
686
    public function lastvisit() { return $this->lastvisit; }
687
    public function facebook_id() { return $this->facebook_id; }
688
    public function facebook_token() { return $this->facebook_token; }
689
    public function facebook_user() { return $this->facebook_user; }
690
691
    public function registrationtime() { return $this->registrationtime; }
692
    public function registrationip() { return $this->registrationip; }
693
    public function optin() { return $this->optin; }
694
    // Don't use the status to check access privileges - use the is_able_to() function.
695
    // But you might use status() to return text to display, describing a user.
696
    // We can then change what status() does in the future if our permissions system
697
    // changes.
698
    public function status() { return $this->status; }
699
    public function deleted() { return $this->deleted; }
700
    public function confirmed() { return $this->confirmed; }
701
702
703 19
    public function postcode_is_set() {
704
        // So we can tell if the, er, postcode is set or not.
705
        // Could maybe put some validation in here at some point.
706 19
        if ($this->postcode != '') {
707
            return true;
708
        } else {
709 19
            return false;
710
        }
711
    }
712
713
714
/////////// PRIVATE FUNCTIONS BELOW... ////////////////
715
716 2
    public function _update($details) {
717
        // Update a user's info.
718
        // DO NOT call this function direct.
719
        // Call either $this->update_other_user() or $this->update_self().
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
720
721
        // $details is an array like that in $this->add().
722 2
        global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
723
724
        // Update email alerts if email address changed
725 2
        if (isset($details['email']) && $this->email != $details['email']) {
726 1
            $this->db->query('UPDATE alerts SET email = :details_email WHERE email = :email',
727
            array(
728 1
                ':details_email' => $details['email'],
729 1
                ':email' => $this->email
730 1
            ));
731 1
        }
732
733
        // These are used to put optional fragments of SQL in, depending
734
        // on whether we're changing those things or not.
735 2
        $passwordsql = "";
736 2
        $deletedsql = "";
737 2
        $confirmedsql = "";
738 2
        $statussql = "";
739 2
        $emailsql = '';
740
741 2
        $params = array();
742
743 2
        if (isset($details["password"]) && $details["password"] != "") {
744
            // The password is being updated.
745
            // If not, the password fields on the form will be left blank
746
            // so we don't want to overwrite the user's pw in the DB!
747
748
            $passwordforDB = password_hash($details["password"], PASSWORD_BCRYPT);
749
750
            $passwordsql = "password = :password, ";
751
            $params[':password'] = $passwordforDB;
752
        }
753
754 2
        if (isset($details["deleted"])) {
755
            // 'deleted' won't always be an option (ie, if the user is updating
756
            // their own info).
757
            if ($details['deleted'] == true) {
758
                $del = '1';
759
            } elseif ($details['deleted'] == false) {
760
                $del = '0';
761
            }
762
            if (isset($del)) {
763
                $deletedsql = "deleted  = '$del', ";
764
            }
765
        }
766
767 2
        if (isset($details["confirmed"])) {
768
            // 'confirmed' won't always be an option (ie, if the user is updating
769
            // their own info).
770
            if ($details['confirmed'] == true) {
771
                $con = '1';
772
            } elseif ($details['confirmed'] == false) {
773
                $con = '0';
774
            }
775
            if (isset($con)) {
776
                $confirmedsql = "confirmed  = '$con', ";
777
            }
778
        }
779
780 2
        if (isset($details["status"]) && $details["status"] != "") {
781
            // 'status' won't always be an option (ie, if the user is updating
782
            // their own info.
783
            $statussql = "status = :status, ";
784
            $params[':status'] = $details['status'];
785
786
        }
787
788 2
        if (isset($details['email']) && $details['email']) {
789 1
            $emailsql = "email = :email, ";
790 1
            $params[':email'] = $details['email'];
791 1
        }
792
793
        // Convert internal true/false variables to MySQL BOOL 1/0 variables.
794 2
        $emailpublic = $details["emailpublic"] == true ? 1 : 0;
795 2
        $optin = $details["optin"] == true ? 1 : 0;
796
797 2
        $q = $this->db->query("UPDATE users
798
                        SET     firstname   = :firstname,
799
                                lastname    = :lastname,
800
                                emailpublic = :emailpublic,
801
                                postcode    = :postcode,
802
                                url         = :url,"
803
                                . $passwordsql
804 2
                                . $deletedsql
805 2
                                . $confirmedsql
806 2
                                . $emailsql
807 2
                                . $statussql . "
808
                                optin       = :optin
809
                        WHERE   user_id     = :user_id
810 2
                        ", array_merge($params, array(
811 2
                            ':firstname' => $details['firstname'],
812 2
                            ':lastname' => $details['lastname'],
813 2
                            ':emailpublic' => $emailpublic,
814 2
                            ':postcode' => $details['postcode'],
815 2
                            ':url' => $details['url'],
816 2
                            ':optin' => $optin,
817 2
                            ':user_id' => $details['user_id']
818 2
                        )));
819
820
        // If we're returning to
821
        // $this->update_self() then $THEUSER will have its variables
822
        // updated if everything went well.
823 2
        if ($q->success()) {
824 2
            return $details;
825
826
        } else {
827
            $PAGE->error_message ("Sorry, we were unable to update user id '" . _htmlentities($details["user_id"]) . "'");
828
829
            return false;
830
        }
831
832
833
    }
834
835
836
837
838
839
} // End USER class
840
841
842
843
844
845
846
class THEUSER extends USER {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
847
848
    // Handles all the login/out functionality and checking for the user
849
    // who is using the site right NOW. Yes, him, over there.
850
851
    // This will become true if all goes well...
852
    public $loggedin = false;
853
    public $facebook_user = false;
854
855
856 3
    public function __construct() {
857
        // This function is run automatically when a THEUSER
858
        // object is instantiated.
859
860 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...
861
862
        // We look at the user's cookie and see if it's valid.
863
        // If so, we're going to log them in.
864
865
        // A user's cookie is of the form:
866
        // 123.blahblahblah
867
        // Where '123' is a user id, and 'blahblahblah' is an md5 hash of the
868
        // encrypted password we've stored in the db.
869
        // (Maybe we could just put the encrypted pw in the cookie and md5ing
870
        // it is overkill? Whatever, it works.)
871
872 3
        $cookie = get_cookie_var("epuser_id"); // In includes/utility.php.
873
874 3
        if ($cookie == '') {
875
            $cookie = get_cookie_var("facebook_id");
876
            if ($cookie != '') {
877
              $this->facebook_user = True;
878
              twfy_debug("THEUSER", "is facebook login");
879
            }
880
        }
881
882 3
        if ($cookie == '') {
883
            twfy_debug("THEUSER init FAILED", "No cookie set");
884
            $this->loggedin = false;
885
886 3
        } elseif (preg_match("/([[:alnum:]]*)\.([[:alnum:]]*)/", $cookie, $matches)) {
887
888 3
            if (is_numeric($matches[1])) {
889
890 3
                $success = $this->init($matches[1]);
891
892 3
                if ($success) {
893
                    // We got all the user's data from the DB.
894
895
                    // But we need to check the password before we log them in.
896
                    // And make sure the user hasn't been "deleted".
897
898 3
                    if ($this->facebook_user) {
899
                      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...
900
                          twfy_debug ("THEUSER", "init SUCCESS: setting as logged in");
901
                          $this->loggedin = true;
902
                      } elseif (md5 ($this->facebook_token()) != $matches[2]) {
903
                          twfy_debug ("THEUSER", "init FAILED: Facebook token doesn't match cookie");
904
                          $this->loggedin = false;
905
                      } else {
906
                          twfy_debug ("THEUSER", "init FAILED: User is deleted");
907
                          $this->loggedin = false;
908
                      }
909
                    } else {
910 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...
911
                          // The correct password is in the cookie,
912
                          // and the user isn't deleted, so set the user to be logged in.
913
914
                          // This would be an appropriate place to call other functions
915
                          // that might set user info that only a logged-in user is going
916
                          // to need. Their preferences and saved things or something.
917
918
919
                          twfy_debug ("THEUSER init SUCCEEDED", "setting as logged in");
920
                          $this->loggedin = true;
921
922 3
                      } elseif (md5 ($this->password()) != $matches[2]) {
923 3
                          twfy_debug ("THEUSER init FAILED", "Password doesn't match cookie");
924 3
                          $this->loggedin = false;
925 3
                      } else {
926
                          twfy_debug ("THEUSER init FAILED", "User is deleted");
927
                          $this->loggedin = false;
928
                      }
929
                    }
930
931 3
                } else {
932
                    twfy_debug ("THEUSER init FAILED", "didn't get 1 row from db");
933
                    $this->loggedin = false;
934
                }
935
936 3
            } else {
937
                twfy_debug ("THEUSER init FAILED", "cookie's user_id is not numeric");
938
                $this->loggedin = false;
939
            }
940
941 3
        } else {
942
            twfy_debug ("THEUSER init FAILED", "cookie is not of the correct form");
943
            $this->loggedin = false;
944
        }
945
946
        // If a user is logged in they *might* have set their own postcode.
947
        // If they aren't logged in, or they haven't set one, then we may
948
        // have set a postcode for them when they searched for their MP.
949
        // If so, we'll use that as $this->postcode.
950 3
        if ($this->postcode == '') {
951 3
            if (get_cookie_var(POSTCODE_COOKIE) != '') {
952
                $pc = get_cookie_var(POSTCODE_COOKIE);
953
954
                $this->set_postcode_cookie($pc);
955
            }
956 3
        }
957
958 3
        $this->update_lastvisit();
959
960 3
    } // End THEUSER()
961
962 3
    public function update_lastvisit() {
963
964 3
        if ($this->isloggedin()) {
965
            // Set last_visit to now.
966
            $date_now = gmdate("Y-m-d H:i:s");
967
            $q = $this->db->query("UPDATE users
0 ignored issues
show
Unused Code introduced by
The assignment to $q is dead and can be removed.
Loading history...
968
                            SET     lastvisit = '$date_now'
969
                            WHERE   user_id = '" . $this->user_id() . "'");
970
971
            $this->lastvisit = $date_now;
972
        }
973 3
    }
974
975
    // For completeness, but it's better to call $this->isloggedin()
976
    // if you want to check the log in status.
977
    public function loggedin() { return $this->loggedin; }
978
979
980
981 7
    public function isloggedin() {
982
        // Call this function to check if the user is successfully logged in.
983
984 7
        if ($this->loggedin()) {
985 2
            twfy_debug("THEUSER", "isloggedin: true");
986
987 2
            return true;
988
        } else {
989 7
            twfy_debug("THEUSER", "isloggedin: false");
990
991 7
            return false;
992
        }
993
    }
994
995
996
    public function isvalid($email, $userenteredpassword) {
997
        // Returns true if this email and plaintext password match a user in the db.
998
        // If false returns an array of form error messages.
999
1000
        // We use this on the log in page to check if the details the user entered
1001
        // are correct. We can then continue with logging the user in (taking into
1002
        // account their cookie remembering settings etc) with $this->login().
1003
1004
        // This error string is shared between both email and password errors to
1005
        // prevent leaking of account existence.
1006
1007
        $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.';
1008
1009
        $q = $this->db->query("SELECT user_id, password, deleted, confirmed FROM users WHERE email = :email", array(':email' => $email));
1010
1011
        if ($q->rows() == 1) {
1012
            // OK.
1013
            $dbpassword = $q->field(0,"password");
1014
            if (password_verify($userenteredpassword, $dbpassword)) {
1015
                $this->user_id  = $q->field(0,"user_id");
1016
                $this->password = $dbpassword;
1017
                // We'll need these when we're going to log in.
1018
                $this->deleted  = $q->field(0,"deleted") == 1 ? true : false;
1019
                $this->confirmed = $q->field(0,"confirmed") == 1 ? true : false;
1020
1021
                return true;
1022
1023
            } else {
1024
                // Failed.
1025
                return array ("invalidemail" => $error_string);
1026
1027
            }
1028
1029
        } else {
1030
            // Failed.
1031
            return array ("invalidemail" => $error_string);
1032
        }
1033
1034
    }
1035
1036 4
    public function has_postcode() {
1037 4
        $has_postcode = false;
1038 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...
1039
            $has_postcode = true;
1040
        }
1041 4
        return $has_postcode;
1042
    }
1043
1044
1045
    public function facebook_login($returl="", $expire, $accessToken) {
1046
        global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1047
1048
        twfy_debug("THEUSER", "Faceook login, user_id " . $this->user_id);
1049
        twfy_debug("THEUSER", "Faceook login, facebook_id " . $this->facebook_id);
1050
        twfy_debug("THEUSER", "Faceook login, email" . $this->email);
1051
        if ($this->facebook_id() == "") {
1052
            $PAGE->error_message ("We don't have a facebook id for this user.", true);
1053
1054
            return;
1055
        }
1056
1057
        twfy_debug("THEUSER", "Faceook login, facebook_token: " . $accessToken);
1058
1059
        $q = $this->db->query ("UPDATE users SET facebook_token = :token WHERE email = :email",
1060
            array(
1061
                ':token' => $accessToken,
1062
                ':email' => $this->email
1063
            ));
1064
1065
        if (!$q->success()) {
1066
            $PAGE->error_message ("There was a problem logging you in", true);
1067
            twfy_debug("THEUSER", "Faceook login, failed to set accessToken");
1068
1069
            return false;
1070
        }
1071
1072
        // facebook login users probably don't have a password
1073
        $cookie = $this->user_id() . "." . md5 ($accessToken);
1074
        twfy_debug("THEUSER", "Faceook login, cookie: " . $cookie);
1075
1076
        twfy_debug("USER", "logging in user from facebook " . $this->user_id);
1077
1078
        $this->loggedin = True;
1079
        $this->_login($returl, $expire, $cookie, 'facebook_id');
1080
        return true;
1081
    }
1082
1083
    public function login($returl="", $expire) {
1084
1085
        // This is used to log the user in. Duh.
1086
        // You should already have checked the user's email and password using
1087
        // $this->isvalid()
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1088
        // That will have set $this->user_id and $this->password, allowing the
1089
        // login to proceed...
1090
1091
        // $expire is either 'session' or 'never' - for the cookie.
1092
1093
        // $returl is the URL to redirect the user to after log in, generally the
1094
        // page they were on before. But if it doesn't exist, they'll just go to
1095
        // the front page.
1096
        global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1097
1098
        if ($returl == "") {
1099
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1100
            $returl = $URL->generate();
1101
        }
1102
1103
        // Various checks about the user - if they fail, we exit.
1104
        if ($this->user_id() == "" || $this->password == "") {
1105
            $PAGE->error_message ("We don't have the user_id or password to make the cookie.", true);
1106
1107
            return;
1108
        } elseif ($this->deleted) {
1109
            $PAGE->error_message ("This user has been deleted.", true);
1110
1111
            return;
1112
        } elseif (!$this->confirmed) {
1113
            $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);
1114
1115
            return;
1116
        }
1117
1118
        // Reminder: $this->password is actually a hashed version of the plaintext pw.
1119
        $cookie = $this->user_id() . "." . md5 ($this->password());
1120
1121
        $this->_login($returl, $expire, $cookie);
1122
    }
1123
1124
    private function _login($returl, $expire, $cookie, $cookie_name = 'epuser_id') {
1125
        // Unset any existing postcode cookie.
1126
        // This will be the postcode the user set for themselves as a non-logged-in
1127
        // user. We don't want it hanging around as it causes confusion.
1128
        $this->unset_postcode_cookie();
1129
1130
        twfy_debug("THEUSER", "expire is " . $expire);
1131
1132
        $cookie_expires = 0;
1133
        if ($expire == 'never') {
1134
            twfy_debug("THEUSER", "cookie never expires");
1135
            $cookie_expires = time()+86400*365*20;
1136
        } elseif (is_int($expire) && $expire > time()) {
1137
            twfy_debug("THEUSER", "cookie expires at " . $expire);
1138
            $cookie_expires = $expire;
1139
        } else {
1140
            twfy_debug("THEUSER", "cookie expires with session");
1141
        }
1142
1143
        header("Location: $returl");
1144
        setcookie($cookie_name, $cookie, $cookie_expires, '/', COOKIEDOMAIN);
1 ignored issue
show
Bug introduced by
The constant COOKIEDOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1145
    }
1146
1147
1148
    public function logout($returl) {
1149
1150
        // $returl is the URL to redirect the user to after log in, generally the
1151
        // page they were on before. But if it doesn't exist, they'll just go to
1152
        // the front page.
1153
1154
        if ($returl == '') {
1155
            $URL = new \MySociety\TheyWorkForYou\Url("home");
1156
            $returl = $URL->generate();
1157
        }
1158
1159
        // get_cookie_var() is in includes/utility.php
1160
        if (get_cookie_var("epuser_id") != "") {
1161
            // They're logged in, so set the cookie to empty.
1162
            header("Location: $returl");
1163
            setcookie('epuser_id', '', time() - 86400, '/', COOKIEDOMAIN);
1 ignored issue
show
Bug introduced by
The constant COOKIEDOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1164
        }
1165
1166
        if (get_cookie_var("facebook_id") != "") {
1167
            // They're logged in, so set the cookie to empty.
1168
            header("Location: $returl");
1169
            setcookie('facebook_id', '', time() - 86400, '/', COOKIEDOMAIN);
1170
        }
1171
    }
1172
1173 2
    public function confirm_email($token, $redirect=true) {
1174 2
        $arg = '';
1175 2
        if (strstr($token, '::')) $arg = '::';
1176 2
        if (strstr($token, '-')) $arg = '-';
1177 2
        list($user_id, $registrationtoken) = explode($arg, $token);
1178
1179 2
        if (!is_numeric($user_id) || $registrationtoken == '') {
1180
            return false;
1181
        }
1182 2
        $q = $this->db->query("SELECT expires, data
1183
            FROM    tokens
1184
            WHERE   token = :token
1185
            AND   type = 'E'
1186 2
        ", array (':token' => $registrationtoken));
1187
1188 2
        if ($q->rows() == 1) {
1189 2
            $expires = $q->field(0, 'expires');
1190 2
            $expire_time = strtotime($expires);
1191 2
            if ( $expire_time < time() ) {
1192 1
                global $PAGE;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1193 1
                if ($PAGE && $redirect) {
1194
                    $PAGE->error_message ("Sorry, that token seems to have expired");
1195
                }
1196
1197 1
                return false;
1198
            }
1199
1200 1
            list( $user_id, $email ) = explode('::', $q->field(0, 'data'));
1201
1202
            // if we are logged in as someone else don't change the email
1203 1
            if ( $this->user_id() != 0 && $this->user_id() != $user_id ) {
1204
                return false;
1205
            }
1206
1207
            // if the user isn't logged in then try and load the
1208
            // details
1209 1
            if ($this->user_id() == 0 && !$this->init($user_id)) {
1210
                return false;
1211
            }
1212
1213
            $details = array(
1214 1
                'email' => $email,
1215 1
                'firstname' => $this->firstname(),
1216 1
                'lastname' => $this->lastname(),
1217 1
                'postcode' => $this->postcode(),
1218 1
                'url' => $this->url(),
1219 1
                'optin' => $this->optin(),
1220 1
                'user_id' => $user_id,
1221 1
                'emailpublic' => $this->emailpublic()
1222 1
            );
1223 1
            $ret = $this->_update($details);
1224
1225 1
            if ($ret) {
1226
                // and remove the token to be tidy
1227 1
                $q = $this->db->query("DELETE
0 ignored issues
show
Unused Code introduced by
The assignment to $q is dead and can be removed.
Loading history...
1228
                    FROM    tokens
1229
                    WHERE   token = :token
1230
                    AND   type = 'E'
1231 1
                ", array(':token' => $registrationtoken));
1232
1233 1
                $this->email = $email;
1234 1
                $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1235 1
                $URL->insert(array('email'=>'t'));
1236 1
                $redirecturl = $URL->generate();
1237 1
                if ($redirect) {
1238
                    $this->login($redirecturl, 'session');
1239
                } else {
1240 1
                    return true;
1241
                }
1242
            } else {
1243
                return false;
1244
            }
1245
        } else {
1246
            return false;
1247
        }
1248
1249
    }
1250
1251
    public function confirm($token) {
1252
        // The user has clicked the link in their confirmation email
1253
        // and the confirm page has passed the token from the URL to here.
1254
        // If all goes well they'll be confirmed and then logged in.
1255
1256
        // Split the token into its parts.
1257
        $arg = '';
1258
        if (strstr($token, '::')) $arg = '::';
1259
        if (strstr($token, '-')) $arg = '-';
1260
        list($user_id, $registrationtoken) = explode($arg, $token);
1261
1262
        if (!is_numeric($user_id) || $registrationtoken == '') {
1263
            return false;
1264
        }
1265
1266
        $q = $this->db->query("SELECT email, password, postcode
1267
                        FROM    users
1268
                        WHERE   user_id = :user_id
1269
                        AND     registrationtoken = :token
1270
                        ", array(
1271
                            ':user_id' => $user_id,
1272
                            ':token' => $registrationtoken
1273
                        ));
1274
1275
        if ($q->rows() == 1) {
1276
1277
            // We'll need these to be set before logging the user in.
1278
            $this->user_id  = $user_id;
1279
            $this->email    = $q->field(0, 'email');
1280
            $this->password = $q->field(0, 'password');
1281
1282
            // Set that they're confirmed in the DB.
1283
            $r = $this->db->query("UPDATE users
1284
                            SET     confirmed = '1'
1285
                            WHERE   user_id = :user_id
1286
                            ", array(':user_id' => $user_id));
1287
1288
            if ($q->field(0, 'postcode')) {
1289
                try {
1290
                    $MEMBER = new MEMBER(array('postcode'=>$q->field(0, 'postcode'), 'house'=>HOUSE_TYPE_COMMONS));
1291
                    $pid = $MEMBER->person_id();
1292
                    # This should probably be in the ALERT class
1293
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', array(
1294
                            ':email' => $this->email,
1295
                            ':criteria' => 'speaker:' . $pid
1296
                        ));
1297
                } 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...
1298
                }
1299
            }
1300
1301
            if ($r->success()) {
1302
1303
                $this->confirmed = true;
1304
1305
                // Log the user in, redirecting them to the confirm page
1306
                // where they should get a nice welcome message.
1307
                $URL = new \MySociety\TheyWorkForYou\Url('userconfirmed');
1308
                $URL->insert(array('welcome'=>'t'));
1309
                $redirecturl = $URL->generate();
1310
1311
                $this->login($redirecturl, 'session');
1312
1313
            } else {
1314
                // Couldn't set them as confirmed in the DB.
1315
                return false;
1316
            }
1317
1318
        } else {
1319
            // Couldn't find this user in the DB. Maybe the token was
1320
            // wrong or incomplete?
1321
            return false;
1322
        }
1323
    }
1324
1325
    public function confirm_without_token() {
1326
        // If we want to confirm login without a token, e.g. during
1327
        // Facebook registration
1328
        //
1329
        // Note that this doesn't login or redirect the user.
1330
1331
        twfy_debug("THEUSER", "Confirming user without token: " . $this->user_id());
1332
        $q = $this->db->query("SELECT email, password, postcode
1333
                        FROM    users
1334
                        WHERE   user_id = :user_id
1335
                        ", array(
1336
                            ':user_id' => $this->user_id,
1337
                        ));
1338
1339
        if ($q->rows() == 1) {
1340
1341
            twfy_debug("THEUSER", "User with ID found to confirm: " . $this->user_id());
1342
            // We'll need these to be set before logging the user in.
1343
            $this->email    = $q->field(0, 'email');
1344
1345
            // Set that they're confirmed in the DB.
1346
            $r = $this->db->query("UPDATE users
1347
                            SET     confirmed = '1'
1348
                            WHERE   user_id = :user_id
1349
                            ", array(':user_id' => $this->user_id));
1350
1351
            if ($q->field(0, 'postcode')) {
1352
                try {
1353
                    $MEMBER = new MEMBER(array('postcode'=>$q->field(0, 'postcode'), 'house'=>HOUSE_TYPE_COMMONS));
1354
                    $pid = $MEMBER->person_id();
1355
                    # This should probably be in the ALERT class
1356
                    $this->db->query('update alerts set confirmed=1 where email = :email and criteria = :criteria', array(
1357
                            ':email' => $this->email,
1358
                            ':criteria' => 'speaker:' . $pid
1359
                        ));
1360
                } 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...
1361
                }
1362
            }
1363
1364
            if ($r->success()) {
1365
                twfy_debug("THEUSER", "User with ID confirmed: " . $this->user_id());
1366
                $this->confirmed = true;
1367
                return true;
1368
            } else {
1369
                twfy_debug("THEUSER", "User with ID not confirmed: " . $this->user_id());
1370
                // Couldn't set them as confirmed in the DB.
1371
                return false;
1372
            }
1373
1374
        } else {
1375
            // Couldn't find this user in the DB. Maybe the token was
1376
            // wrong or incomplete?
1377
            twfy_debug("THEUSER", "User with ID not found to confirm: " . $this->user_id());
1378
            return false;
1379
        }
1380
    }
1381
1382
1383
    public function set_postcode_cookie($pc) {
1384
        // Set the user's postcode.
1385
        // Doesn't change it in the DB, as it's probably mainly for
1386
        // not-logged-in users.
1387
1388
        $this->postcode = $pc;
1389
        if (!headers_sent()) // if in debug mode
1390
            setcookie (POSTCODE_COOKIE, $pc, time()+7*86400, "/", COOKIEDOMAIN);
1 ignored issue
show
Bug introduced by
The constant COOKIEDOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1391
1392
        twfy_debug('USER', "Set the cookie named '" . POSTCODE_COOKIE . " to '$pc' for " . COOKIEDOMAIN . " domain");
1393
    }
1394
1395
    public function unset_postcode_cookie() {
1396
        if (!headers_sent()) // if in debug mode
1397
            setcookie (POSTCODE_COOKIE, '', time() - 3600, '/', COOKIEDOMAIN);
1 ignored issue
show
Bug introduced by
The constant COOKIEDOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1398
    }
1399
1400
    // mostly here for updating from facebook where we do not need
1401
    // to confirm the email address
1402
    public function update_self_no_confirm($details) {
1403
        global $THEUSER;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1404
1405
        if ($this->isloggedin()) {
1406
            twfy_debug("THEUSER", "is logged in for update_self");
1407
1408
            // this is checked elsewhere but just in case we check here and
1409
            // bail out to be on the safe side
1410
            if ( isset($details['email'] ) ) {
1411
                if ( $details['email'] != $this->email() && $this->email_exists( $details['email'] ) ) {
1412
                    return false;
1413
                }
1414
            }
1415
1416
            $details["user_id"] = $this->user_id;
1417
1418
            $newdetails = $this->_update($details);
1419
1420
            if ($newdetails) {
1421
                // The user's data was updated, so we'll change the object
1422
                // variables accordingly.
1423
1424
                $this->firstname        = $newdetails["firstname"];
1425
                $this->lastname         = $newdetails["lastname"];
1426
                $this->emailpublic      = $newdetails["emailpublic"];
1427
                $this->postcode         = $newdetails["postcode"];
1428
                $this->url              = $newdetails["url"];
1429
                $this->optin            = $newdetails["optin"];
1430
                $this->email            = $newdetails['email'];
1431
                if ($newdetails["password"] != "") {
1432
                    $this->password = $newdetails["password"];
1433
                }
1434
1435
                return true;
1436
            } else {
1437
                return false;
1438
            }
1439
1440
        } else {
1441
            return false;
1442
        }
1443
1444
    }
1445
1446 2
    public function update_self($details, $confirm_email = true) {
1447
        // If the user wants to update their details, call this function.
1448
        // It checks that they're logged in before letting them.
1449
1450
1451
        // $details is an array like that in $this->add().
1452
1453 2
        global $THEUSER;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1454
1455 2
        if ($this->isloggedin()) {
1456
1457
            // this is checked elsewhere but just in case we check here and
1458
            // bail out to be on the safe side
1459 2
            $email = '';
1460 2
            if ( isset($details['email'] ) ) {
1461 1
                if ( $details['email'] != $this->email() && $this->email_exists( $details['email'] ) ) {
1462
                    return false;
1463
                }
1464 1
                $email = $details['email'];
1465 1
                unset($details['email']);
1466 1
            }
1467 2
            $details["user_id"] = $this->user_id;
1468
1469 2
            $newdetails = $this->_update($details);
1470
1471
            // $newdetails will be an array of details if all went well,
1472
            // false otherwise.
1473
1474 2
            if ($newdetails) {
1475
                // The user's data was updated, so we'll change the object
1476
                // variables accordingly.
1477
1478 2
                $this->firstname        = $newdetails["firstname"];
1479 2
                $this->lastname         = $newdetails["lastname"];
1480 2
                $this->emailpublic      = $newdetails["emailpublic"];
1481 2
                $this->postcode         = $newdetails["postcode"];
1482 2
                $this->url              = $newdetails["url"];
1483 2
                $this->optin            = $newdetails["optin"];
1484 2
                if ($newdetails["password"] != "") {
1485
                    $this->password = $newdetails["password"];
1486
                }
1487
1488 2
                if ($email && $email != $this->email) {
1489 1
                    $token = substr( password_hash($email . microtime(), PASSWORD_BCRYPT), 29, 16 );
1490 1
                    $data = $this->user_id() . '::' . $email;
1491 1
                    $r = $this->db->query("INSERT INTO tokens
1492
                        ( expires, token, type, data )
1493
                        VALUES
1494
                        (
1495
                            DATE_ADD(CURRENT_DATE(), INTERVAL 30 DAY),
1496
                            :token,
1497
                            'E',
1498
                            :data
1499
                        )
1500 1
                    ", array(
1501 1
                        ':token' => $token,
1502
                        ':data' => $data
1503 1
                    ));
1504
1505
                    // send confirmation email here
1506 1
                    if ( $r->success() ) {
1507 1
                        $newdetails['email'] = $email;
1508 1
                        $newdetails['token'] = $token;
1509 1
                        if ($confirm_email) {
1510
                            return $this->send_email_confirmation_email($newdetails);
1511
                        } else {
1512 1
                            return true;
1513
                        }
1514
                    } else {
1515
                        return false;
1516
                    }
1517
                }
1518
1519 1
                return true;
1520
            } else {
1521
                return false;
1522
            }
1523
1524
        } else {
1525
            return false;
1526
        }
1527
1528
    }
1529
1530
}
1531
1532
// Yes, we instantiate a new global $THEUSER object when every page loads.
1533
$THEUSER = new THEUSER;
1534