Failed Conditions
Push — newinternal ( b66232...216d62 )
by Simon
16:33 queued 06:35
created

User::getConfirmationDiff()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\DataObjects;
10
11
use DateTime;
12
use Exception;
13
use Waca\DataObject;
14
use Waca\Exceptions\OptimisticLockFailedException;
15
use Waca\IdentificationVerifier;
16
use Waca\PdoDatabase;
17
use Waca\WebRequest;
18
19
/**
20
 * User data object
21
 */
22
class User extends DataObject
23
{
24
    const STATUS_ACTIVE = 'Active';
25
    const STATUS_SUSPENDED = 'Suspended';
26
    const STATUS_DECLINED = 'Declined';
27
    const STATUS_NEW = 'New';
28
    const CREATION_MANUAL = 0;
29
    const CREATION_OAUTH = 1;
30
    const CREATION_BOT = 2;
31
    private $username;
32
    private $email;
33
    private $status = self::STATUS_NEW;
34
    private $onwikiname;
35
    private $welcome_sig = "";
36
    private $lastactive = "0000-00-00 00:00:00";
37
    private $forcelogout = 0;
38
    private $forceidentified = null;
39
    private $welcome_template = 0;
40
    private $abortpref = 0;
41
    private $confirmationdiff = 0;
42
    private $emailsig = "";
43
    private $creationmode = 0;
44
    /** @var User Cache variable of the current user - it's never going to change in the middle of a request. */
45
    private static $currentUser;
46
    #region Object load methods
47
48
    /**
49
     * Gets the currently logged in user
50
     *
51
     * @param PdoDatabase $database
52
     *
53
     * @return User|CommunityUser
54
     */
55
    public static function getCurrent(PdoDatabase $database)
56
    {
57
        if (self::$currentUser === null) {
58
            $sessionId = WebRequest::getSessionUserId();
59
60
            if ($sessionId !== null) {
61
                /** @var User $user */
62
                $user = self::getById($sessionId, $database);
63
64
                if ($user === false) {
65
                    self::$currentUser = new CommunityUser();
66
                }
67
                else {
68
                    self::$currentUser = $user;
69
                }
70
            }
71
            else {
72
                $anonymousCoward = new CommunityUser();
73
74
                self::$currentUser = $anonymousCoward;
75
            }
76
        }
77
78
        return self::$currentUser;
79
    }
80
81
    /**
82
     * Gets a user by their user ID
83
     *
84
     * Pass -1 to get the community user.
85
     *
86
     * @param int|null    $id
87
     * @param PdoDatabase $database
88
     *
89
     * @return User|false
90
     */
91
    public static function getById($id, PdoDatabase $database)
92
    {
93
        if ($id === null || $id == -1) {
94
            return new CommunityUser();
95
        }
96
97
        /** @var User|false $user */
98
        $user = parent::getById($id, $database);
99
100
        return $user;
101
    }
102
103
    /**
104
     * @return CommunityUser
105
     */
106
    public static function getCommunity()
107
    {
108
        return new CommunityUser();
109
    }
110
111
    /**
112
     * Gets a user by their username
113
     *
114
     * @param  string      $username
115
     * @param  PdoDatabase $database
116
     *
117
     * @return CommunityUser|User|false
118
     */
119
    public static function getByUsername($username, PdoDatabase $database)
120
    {
121
        global $communityUsername;
122
        if ($username == $communityUsername) {
123
            return new CommunityUser();
124
        }
125
126
        $statement = $database->prepare("SELECT * FROM user WHERE username = :id LIMIT 1;");
127
        $statement->bindValue(":id", $username);
128
129
        $statement->execute();
130
131
        $resultObject = $statement->fetchObject(get_called_class());
132
133
        if ($resultObject != false) {
134
            $resultObject->setDatabase($database);
135
        }
136
137
        return $resultObject;
138
    }
139
140
    /**
141
     * Gets a user by their on-wiki username.
142
     *
143
     * @param string      $username
144
     * @param PdoDatabase $database
145
     *
146
     * @return User|false
147
     */
148 View Code Duplication
    public static function getByOnWikiUsername($username, PdoDatabase $database)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
149
    {
150
        $statement = $database->prepare("SELECT * FROM user WHERE onwikiname = :id LIMIT 1;");
151
        $statement->bindValue(":id", $username);
152
        $statement->execute();
153
154
        $resultObject = $statement->fetchObject(get_called_class());
155
156
        if ($resultObject != false) {
157
            $resultObject->setDatabase($database);
158
159
            return $resultObject;
160
        }
161
162
        return false;
163
    }
164
165
    #endregion
166
167
    /**
168
     * Saves the current object
169
     *
170
     * @throws Exception
171
     */
172
    public function save()
173
    {
174
        if ($this->isNew()) {
175
            // insert
176
            $statement = $this->dbObject->prepare(<<<SQL
177
				INSERT INTO `user` ( 
178
					username, email, status, onwikiname, welcome_sig, 
179
					lastactive, forcelogout, forceidentified,
180
					welcome_template, abortpref, confirmationdiff, emailsig, creationmode
181
				) VALUES (
182
					:username, :email, :status, :onwikiname, :welcome_sig,
183
					:lastactive, :forcelogout, :forceidentified,
184
					:welcome_template, :abortpref, :confirmationdiff, :emailsig, :creationmode
185
				);
186
SQL
187
            );
188
            $statement->bindValue(":username", $this->username);
189
            $statement->bindValue(":email", $this->email);
190
            $statement->bindValue(":status", $this->status);
191
            $statement->bindValue(":onwikiname", $this->onwikiname);
192
            $statement->bindValue(":welcome_sig", $this->welcome_sig);
193
            $statement->bindValue(":lastactive", $this->lastactive);
194
            $statement->bindValue(":forcelogout", $this->forcelogout);
195
            $statement->bindValue(":forceidentified", $this->forceidentified);
196
            $statement->bindValue(":welcome_template", $this->welcome_template);
197
            $statement->bindValue(":abortpref", $this->abortpref);
198
            $statement->bindValue(":confirmationdiff", $this->confirmationdiff);
199
            $statement->bindValue(":emailsig", $this->emailsig);
200
            $statement->bindValue(":creationmode", $this->creationmode);
201
202
            if ($statement->execute()) {
203
                $this->id = (int)$this->dbObject->lastInsertId();
204
            }
205
            else {
206
                throw new Exception($statement->errorInfo());
207
            }
208
        }
209 View Code Duplication
        else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
            // update
211
            $statement = $this->dbObject->prepare(<<<SQL
212
				UPDATE `user` SET 
213
					username = :username, email = :email, 
214
					status = :status,
215
					onwikiname = :onwikiname, welcome_sig = :welcome_sig, 
216
					lastactive = :lastactive, forcelogout = :forcelogout, 
217
					forceidentified = :forceidentified,
218
					welcome_template = :welcome_template, abortpref = :abortpref, 
219
					confirmationdiff = :confirmationdiff, emailsig = :emailsig, 
220
					creationmode = :creationmode, updateversion = updateversion + 1
221
				WHERE id = :id AND updateversion = :updateversion
222
				LIMIT 1;
223
SQL
224
            );
225
            $statement->bindValue(":forceidentified", $this->forceidentified);
226
227
            $statement->bindValue(':id', $this->id);
228
            $statement->bindValue(':updateversion', $this->updateversion);
229
230
            $statement->bindValue(':username', $this->username);
231
            $statement->bindValue(':email', $this->email);
232
            $statement->bindValue(':status', $this->status);
233
            $statement->bindValue(':onwikiname', $this->onwikiname);
234
            $statement->bindValue(':welcome_sig', $this->welcome_sig);
235
            $statement->bindValue(':lastactive', $this->lastactive);
236
            $statement->bindValue(':forcelogout', $this->forcelogout);
237
            $statement->bindValue(':forceidentified', $this->forceidentified);
238
            $statement->bindValue(':welcome_template', $this->welcome_template);
239
            $statement->bindValue(':abortpref', $this->abortpref);
240
            $statement->bindValue(':confirmationdiff', $this->confirmationdiff);
241
            $statement->bindValue(':emailsig', $this->emailsig);
242
            $statement->bindValue(':creationmode', $this->creationmode);
243
244
            if (!$statement->execute()) {
245
                throw new Exception($statement->errorInfo());
246
            }
247
248
            if ($statement->rowCount() !== 1) {
249
                throw new OptimisticLockFailedException();
250
            }
251
252
            $this->updateversion++;
253
        }
254
    }
255
256
    #region properties
257
258
    /**
259
     * Gets the tool username
260
     * @return string
261
     */
262
    public function getUsername()
263
    {
264
        return $this->username;
265
    }
266
267
    /**
268
     * Sets the tool username
269
     *
270
     * @param string $username
271
     */
272
    public function setUsername($username)
273
    {
274
        $this->username = $username;
275
276
        // If this isn't a brand new user, then it's a rename, force the logout
277
        if (!$this->isNew()) {
278
            $this->forcelogout = 1;
279
        }
280
    }
281
282
    /**
283
     * Gets the user's email address
284
     * @return string
285
     */
286
    public function getEmail()
287
    {
288
        return $this->email;
289
    }
290
291
    /**
292
     * Sets the user's email address
293
     *
294
     * @param string $email
295
     */
296
    public function setEmail($email)
297
    {
298
        $this->email = $email;
299
    }
300
301
    /**
302
     * Gets the status (User, Admin, Suspended, etc - excludes checkuser) of the user.
303
     * @return string
304
     */
305
    public function getStatus()
306
    {
307
        return $this->status;
308
    }
309
310
    /**
311
     * @param string $status
312
     */
313
    public function setStatus($status)
314
    {
315
        $this->status = $status;
316
    }
317
318
    /**
319
     * Gets the user's on-wiki name
320
     * @return string
321
     */
322
    public function getOnWikiName()
323
    {
324
        return $this->onwikiname;
325
    }
326
327
    /**
328
     * Sets the user's on-wiki name
329
     *
330
     * This can have interesting side-effects with OAuth.
331
     *
332
     * @param string $onWikiName
333
     */
334
    public function setOnWikiName($onWikiName)
335
    {
336
        $this->onwikiname = $onWikiName;
337
    }
338
339
    /**
340
     * Gets the welcome signature
341
     * @return string
342
     */
343
    public function getWelcomeSig()
344
    {
345
        return $this->welcome_sig;
346
    }
347
348
    /**
349
     * Sets the welcome signature
350
     *
351
     * @param string $welcomeSig
352
     */
353
    public function setWelcomeSig($welcomeSig)
354
    {
355
        $this->welcome_sig = $welcomeSig;
356
    }
357
358
    /**
359
     * Gets the last activity date for the user
360
     *
361
     * @return string
362
     * @todo This should probably return an instance of DateTime
363
     */
364
    public function getLastActive()
365
    {
366
        return $this->lastactive;
367
    }
368
369
    /**
370
     * Gets the user's forced logout status
371
     *
372
     * @return bool
373
     */
374
    public function getForceLogout()
375
    {
376
        return $this->forcelogout == 1;
377
    }
378
379
    /**
380
     * Sets the user's forced logout status
381
     *
382
     * @param bool $forceLogout
383
     */
384
    public function setForceLogout($forceLogout)
385
    {
386
        $this->forcelogout = $forceLogout ? 1 : 0;
387
    }
388
389
    /**
390
     * Returns the ID of the welcome template used.
391
     * @return int
392
     */
393
    public function getWelcomeTemplate()
394
    {
395
        return $this->welcome_template;
396
    }
397
398
    /**
399
     * Sets the ID of the welcome template used.
400
     *
401
     * @param int $welcomeTemplate
402
     */
403
    public function setWelcomeTemplate($welcomeTemplate)
404
    {
405
        $this->welcome_template = $welcomeTemplate;
406
    }
407
408
    /**
409
     * Gets the user's abort preference
410
     * @todo this is badly named too! Also a bool that's actually an int.
411
     * @return int
412
     */
413
    public function getAbortPref()
414
    {
415
        return $this->abortpref;
416
    }
417
418
    /**
419
     * Sets the user's abort preference
420
     * @todo rename, retype, and re-comment.
421
     *
422
     * @param int $abortPreference
423
     */
424
    public function setAbortPref($abortPreference)
425
    {
426
        $this->abortpref = $abortPreference;
427
    }
428
429
    /**
430
     * Gets the user's confirmation diff. Unused if OAuth is in use.
431
     * @return int the diff ID
432
     */
433
    public function getConfirmationDiff()
434
    {
435
        return $this->confirmationdiff;
436
    }
437
438
    /**
439
     * Sets the user's confirmation diff.
440
     *
441
     * @param int $confirmationDiff
442
     */
443
    public function setConfirmationDiff($confirmationDiff)
444
    {
445
        $this->confirmationdiff = $confirmationDiff;
446
    }
447
448
    /**
449
     * Gets the users' email signature used on outbound mail.
450
     * @todo rename me!
451
     * @return string
452
     */
453
    public function getEmailSig()
454
    {
455
        return $this->emailsig;
456
    }
457
458
    /**
459
     * Sets the user's email signature for outbound mail.
460
     *
461
     * @param string $emailSignature
462
     */
463
    public function setEmailSig($emailSignature)
464
    {
465
        $this->emailsig = $emailSignature;
466
    }
467
468
    /**
469
     * @return int
470
     */
471
    public function getCreationMode()
472
    {
473
        return $this->creationmode;
474
    }
475
476
    /**
477
     * @param $creationMode int
478
     */
479
    public function setCreationMode($creationMode)
480
    {
481
        $this->creationmode = $creationMode;
482
    }
483
484
    #endregion
485
486
    #region user access checks
487
488
    public function isActive()
489
    {
490
        return $this->status == self::STATUS_ACTIVE;
491
    }
492
493
    /**
494
     * Tests if the user is identified
495
     *
496
     * @param IdentificationVerifier $iv
497
     *
498
     * @return bool
499
     * @todo     Figure out what on earth is going on with PDO's typecasting here.  Apparently, it returns string("0") for
500
     *       the force-unidentified case, and int(1) for the identified case?!  This is quite ugly, but probably needed
501
     *       to play it safe for now.
502
     * @category Security-Critical
503
     */
504
    public function isIdentified(IdentificationVerifier $iv)
505
    {
506
        if ($this->forceidentified === 0 || $this->forceidentified === "0") {
507
            // User forced to unidentified in the database.
508
            return false;
509
        }
510
        elseif ($this->forceidentified === 1 || $this->forceidentified === "1") {
511
            // User forced to identified in the database.
512
            return true;
513
        }
514
        else {
515
            // User not forced to any particular identified status; consult IdentificationVerifier
516
            return $iv->isUserIdentified($this->getOnWikiName());
517
        }
518
    }
519
520
    /**
521
     * Tests if the user is suspended
522
     * @return bool
523
     * @category Security-Critical
524
     */
525
    public function isSuspended()
526
    {
527
        return $this->status == self::STATUS_SUSPENDED;
528
    }
529
530
    /**
531
     * Tests if the user is new
532
     * @return bool
533
     * @category Security-Critical
534
     */
535
    public function isNewUser()
536
    {
537
        return $this->status == self::STATUS_NEW;
538
    }
539
540
    /**
541
     * Tests if the user has been declined access to the tool
542
     * @return bool
543
     * @category Security-Critical
544
     */
545
    public function isDeclined()
546
    {
547
        return $this->status == self::STATUS_DECLINED;
548
    }
549
550
    /**
551
     * Tests if the user is the community user
552
     *
553
     * @todo     decide if this means logged out. I think it usually does.
554
     * @return bool
555
     * @category Security-Critical
556
     */
557
    public function isCommunityUser()
558
    {
559
        return false;
560
    }
561
562
    #endregion 
563
564
    /**
565
     * Gets a hash of data for the user to reset their password with.
566
     * @category Security-Critical
567
     * @return string
568
     */
569
    public function getForgottenPasswordHash()
570
    {
571
        // FIXME
572
        return md5($this->username . $this->email . $this->welcome_template . $this->id);
573
    }
574
575
    /**
576
     * Gets the approval date of the user
577
     * @return DateTime|false
578
     */
579
    public function getApprovalDate()
580
    {
581
        $query = $this->dbObject->prepare(<<<SQL
582
			SELECT timestamp 
583
			FROM log 
584
			WHERE objectid = :userid
585
				AND objecttype = 'User'
586
				AND action = 'Approved' 
587
			ORDER BY id DESC 
588
			LIMIT 1;
589
SQL
590
        );
591
        $query->execute(array(":userid" => $this->id));
592
593
        $data = DateTime::createFromFormat("Y-m-d H:i:s", $query->fetchColumn());
594
        $query->closeCursor();
595
596
        return $data;
597
    }
598
}
599