Failed Conditions
Push — multiproject/prefs ( 965e5d )
by Simon
08:54
created

User::isIdentified()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 13
rs 9.6111
cc 5
nc 3
nop 1
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
29
    private $username;
30
    private $email;
31
    private $status = self::STATUS_NEW;
32
    private $onwikiname;
33
    private $lastactive = "0000-00-00 00:00:00";
34
    private $forcelogout = 0;
35
    private $forceidentified = null;
36
    private $confirmationdiff = 0;
37
    /** @var User Cache variable of the current user - it's never going to change in the middle of a request. */
38
    private static $currentUser;
39
    #region Object load methods
40
41
    /**
42
     * Gets the currently logged in user
43
     *
44
     * @param PdoDatabase $database
45
     *
46
     * @return User|CommunityUser
47
     */
48
    public static function getCurrent(PdoDatabase $database)
49
    {
50
        if (self::$currentUser === null) {
51
            $sessionId = WebRequest::getSessionUserId();
52
53
            if ($sessionId !== null) {
54
                /** @var User $user */
55
                $user = self::getById($sessionId, $database);
56
57
                if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
58
                    self::$currentUser = new CommunityUser();
59
                }
60
                else {
61
                    self::$currentUser = $user;
62
                }
63
            }
64
            else {
65
                $anonymousCoward = new CommunityUser();
66
67
                self::$currentUser = $anonymousCoward;
68
            }
69
        }
70
71
        return self::$currentUser;
72
    }
73
74
    /**
75
     * Gets a user by their user ID
76
     *
77
     * Pass -1 to get the community user.
78
     *
79
     * @param int|null    $id
80
     * @param PdoDatabase $database
81
     *
82
     * @return User|false
83
     */
84
    public static function getById($id, PdoDatabase $database)
85
    {
86
        if ($id === null || $id == -1) {
87
            return new CommunityUser();
88
        }
89
90
        /** @var User|false $user */
91
        $user = parent::getById($id, $database);
92
93
        return $user;
94
    }
95
96
    /**
97
     * @return CommunityUser
98
     */
99
    public static function getCommunity()
100
    {
101
        return new CommunityUser();
102
    }
103
104
    /**
105
     * Gets a user by their username
106
     *
107
     * @param  string      $username
108
     * @param  PdoDatabase $database
109
     *
110
     * @return CommunityUser|User|false
111
     */
112
    public static function getByUsername($username, PdoDatabase $database)
113
    {
114
        global $communityUsername;
115
        if ($username == $communityUsername) {
116
            return new CommunityUser();
117
        }
118
119
        $statement = $database->prepare("SELECT * FROM user WHERE username = :id LIMIT 1;");
120
        $statement->bindValue(":id", $username);
121
122
        $statement->execute();
123
124
        $resultObject = $statement->fetchObject(get_called_class());
125
126
        if ($resultObject != false) {
127
            $resultObject->setDatabase($database);
128
        }
129
130
        return $resultObject;
131
    }
132
133
    /**
134
     * Gets a user by their on-wiki username.
135
     *
136
     * @param string      $username
137
     * @param PdoDatabase $database
138
     *
139
     * @return User|false
140
     */
141
    public static function getByOnWikiUsername($username, PdoDatabase $database)
142
    {
143
        $statement = $database->prepare("SELECT * FROM user WHERE onwikiname = :id LIMIT 1;");
144
        $statement->bindValue(":id", $username);
145
        $statement->execute();
146
147
        $resultObject = $statement->fetchObject(get_called_class());
148
149
        if ($resultObject != false) {
150
            $resultObject->setDatabase($database);
151
152
            return $resultObject;
153
        }
154
155
        return false;
156
    }
157
158
    #endregion
159
160
    /**
161
     * Saves the current object
162
     *
163
     * @throws Exception
164
     */
165
    public function save()
166
    {
167
        if ($this->isNew()) {
168
            // insert
169
            $statement = $this->dbObject->prepare(<<<SQL
170
				INSERT INTO `user` ( 
171
					username, email, status, onwikiname, 
172
					lastactive, forcelogout, forceidentified,
173
					confirmationdiff
174
				) VALUES (
175
					:username, :email, :status, :onwikiname,
176
					:lastactive, :forcelogout, :forceidentified,
177
					:confirmationdiff
178
				);
179
SQL
180
            );
181
            $statement->bindValue(":username", $this->username);
182
            $statement->bindValue(":email", $this->email);
183
            $statement->bindValue(":status", $this->status);
184
            $statement->bindValue(":onwikiname", $this->onwikiname);
185
            $statement->bindValue(":lastactive", $this->lastactive);
186
            $statement->bindValue(":forcelogout", $this->forcelogout);
187
            $statement->bindValue(":forceidentified", $this->forceidentified);
188
            $statement->bindValue(":confirmationdiff", $this->confirmationdiff);
189
190
            if ($statement->execute()) {
191
                $this->id = (int)$this->dbObject->lastInsertId();
192
            }
193
            else {
194
                throw new Exception($statement->errorInfo());
0 ignored issues
show
Bug introduced by
$statement->errorInfo() of type array is incompatible with the type string expected by parameter $message of Exception::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

194
                throw new Exception(/** @scrutinizer ignore-type */ $statement->errorInfo());
Loading history...
195
            }
196
        }
197
        else {
198
            // update
199
            $statement = $this->dbObject->prepare(<<<SQL
200
				UPDATE `user` SET 
201
					username = :username, email = :email, 
202
					status = :status,
203
					onwikiname = :onwikiname, 
204
					lastactive = :lastactive,
205
					forcelogout = :forcelogout, 
206
					forceidentified = :forceidentified,
207
					confirmationdiff = :confirmationdiff,
208
                    updateversion = updateversion + 1
209
				WHERE id = :id AND updateversion = :updateversion;
210
SQL
211
            );
212
            $statement->bindValue(":forceidentified", $this->forceidentified);
213
214
            $statement->bindValue(':id', $this->id);
215
            $statement->bindValue(':updateversion', $this->updateversion);
216
217
            $statement->bindValue(':username', $this->username);
218
            $statement->bindValue(':email', $this->email);
219
            $statement->bindValue(':status', $this->status);
220
            $statement->bindValue(':onwikiname', $this->onwikiname);
221
            $statement->bindValue(':lastactive', $this->lastactive);
222
            $statement->bindValue(':forcelogout', $this->forcelogout);
223
            $statement->bindValue(':forceidentified', $this->forceidentified);
224
            $statement->bindValue(':confirmationdiff', $this->confirmationdiff);
225
226
            if (!$statement->execute()) {
227
                throw new Exception($statement->errorInfo());
228
            }
229
230
            if ($statement->rowCount() !== 1) {
231
                throw new OptimisticLockFailedException();
232
            }
233
234
            $this->updateversion++;
235
        }
236
    }
237
238
    #region properties
239
240
    /**
241
     * Gets the tool username
242
     * @return string
243
     */
244
    public function getUsername()
245
    {
246
        return $this->username;
247
    }
248
249
    /**
250
     * Sets the tool username
251
     *
252
     * @param string $username
253
     */
254
    public function setUsername($username)
255
    {
256
        $this->username = $username;
257
258
        // If this isn't a brand new user, then it's a rename, force the logout
259
        if (!$this->isNew()) {
260
            $this->forcelogout = 1;
261
        }
262
    }
263
264
    /**
265
     * Gets the user's email address
266
     * @return string
267
     */
268
    public function getEmail()
269
    {
270
        return $this->email;
271
    }
272
273
    /**
274
     * Sets the user's email address
275
     *
276
     * @param string $email
277
     */
278
    public function setEmail($email)
279
    {
280
        $this->email = $email;
281
    }
282
283
    /**
284
     * Gets the status (User, Admin, Suspended, etc - excludes checkuser) of the user.
285
     * @return string
286
     */
287
    public function getStatus()
288
    {
289
        return $this->status;
290
    }
291
292
    /**
293
     * @param string $status
294
     */
295
    public function setStatus($status)
296
    {
297
        $this->status = $status;
298
    }
299
300
    /**
301
     * Gets the user's on-wiki name
302
     * @return string
303
     */
304
    public function getOnWikiName()
305
    {
306
        return $this->onwikiname;
307
    }
308
309
    /**
310
     * Sets the user's on-wiki name
311
     *
312
     * This can have interesting side-effects with OAuth.
313
     *
314
     * @param string $onWikiName
315
     */
316
    public function setOnWikiName($onWikiName)
317
    {
318
        $this->onwikiname = $onWikiName;
319
    }
320
321
    /**
322
     * Gets the last activity date for the user
323
     *
324
     * @return string
325
     * @todo This should probably return an instance of DateTime
326
     */
327
    public function getLastActive()
328
    {
329
        return $this->lastactive;
330
    }
331
332
    /**
333
     * Gets the user's forced logout status
334
     *
335
     * @return bool
336
     */
337
    public function getForceLogout()
338
    {
339
        return $this->forcelogout == 1;
340
    }
341
342
    /**
343
     * Sets the user's forced logout status
344
     *
345
     * @param bool $forceLogout
346
     */
347
    public function setForceLogout($forceLogout)
348
    {
349
        $this->forcelogout = $forceLogout ? 1 : 0;
350
    }
351
352
    /**
353
     * Gets the user's confirmation diff. Unused if OAuth is in use.
354
     * @return int the diff ID
355
     */
356
    public function getConfirmationDiff()
357
    {
358
        return $this->confirmationdiff;
359
    }
360
361
    /**
362
     * Sets the user's confirmation diff.
363
     *
364
     * @param int $confirmationDiff
365
     */
366
    public function setConfirmationDiff($confirmationDiff)
367
    {
368
        $this->confirmationdiff = $confirmationDiff;
369
    }
370
371
    #endregion
372
373
    #region user access checks
374
375
    public function isActive()
376
    {
377
        return $this->status == self::STATUS_ACTIVE;
378
    }
379
380
    /**
381
     * Tests if the user is identified
382
     *
383
     * @param IdentificationVerifier $iv
384
     *
385
     * @return bool
386
     * @todo     Figure out what on earth is going on with PDO's typecasting here.  Apparently, it returns string("0") for
387
     *       the force-unidentified case, and int(1) for the identified case?!  This is quite ugly, but probably needed
388
     *       to play it safe for now.
389
     * @category Security-Critical
390
     */
391
    public function isIdentified(IdentificationVerifier $iv)
392
    {
393
        if ($this->forceidentified === 0 || $this->forceidentified === "0") {
394
            // User forced to unidentified in the database.
395
            return false;
396
        }
397
        elseif ($this->forceidentified === 1 || $this->forceidentified === "1") {
398
            // User forced to identified in the database.
399
            return true;
400
        }
401
        else {
402
            // User not forced to any particular identified status; consult IdentificationVerifier
403
            return $iv->isUserIdentified($this->getOnWikiName());
404
        }
405
    }
406
407
    /**
408
     * DO NOT USE FOR TESTING IDENTIFICATION STATUS.
409
     *
410
     * @return bool|null
411
     */
412
    public function getForceIdentified()
413
    {
414
        return $this->forceidentified;
415
    }
416
417
    /**
418
     * Tests if the user is suspended
419
     * @return bool
420
     * @category Security-Critical
421
     */
422
    public function isSuspended()
423
    {
424
        return $this->status == self::STATUS_SUSPENDED;
425
    }
426
427
    /**
428
     * Tests if the user is new
429
     * @return bool
430
     * @category Security-Critical
431
     */
432
    public function isNewUser()
433
    {
434
        return $this->status == self::STATUS_NEW;
435
    }
436
437
    /**
438
     * Tests if the user has been declined access to the tool
439
     * @return bool
440
     * @category Security-Critical
441
     */
442
    public function isDeclined()
443
    {
444
        return $this->status == self::STATUS_DECLINED;
445
    }
446
447
    /**
448
     * Tests if the user is the community user
449
     *
450
     * @todo     decide if this means logged out. I think it usually does.
451
     * @return bool
452
     * @category Security-Critical
453
     */
454
    public function isCommunityUser()
455
    {
456
        return false;
457
    }
458
459
    #endregion 
460
461
    /**
462
     * Gets the approval date of the user
463
     * @return DateTime|false
464
     */
465
    public function getApprovalDate()
466
    {
467
        $query = $this->dbObject->prepare(<<<SQL
468
			SELECT timestamp 
469
			FROM log 
470
			WHERE objectid = :userid
471
				AND objecttype = 'User'
472
				AND action = 'Approved' 
473
			ORDER BY id DESC 
474
			LIMIT 1;
475
SQL
476
        );
477
        $query->execute(array(":userid" => $this->id));
478
479
        $data = DateTime::createFromFormat("Y-m-d H:i:s", $query->fetchColumn());
480
        $query->closeCursor();
481
482
        return $data;
483
    }
484
}
485