Passed
Pull Request — newinternal (#556)
by Matthew
03:29
created

Request::getEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 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 DateTimeImmutable;
13
use Exception;
14
use Waca\DataObject;
15
use Waca\Exceptions\OptimisticLockFailedException;
16
17
/**
18
 * Request data object
19
 *
20
 * This data object is the main request object.
21
 */
22
class Request extends DataObject
23
{
24
    private $email;
25
    private $ip;
26
    private $name;
27
    /** @var string|null */
28
    private $comment;
29
    private $status = "Open";
30
    private $date;
31
    private $emailsent = 0;
32
    private $emailconfirm;
33
    /** @var int|null */
34
    private $reserved = null;
35
    private $useragent;
36
    private $forwardedip;
37
    private $hasComments = false;
38
    private $hasCommentsResolved = false;
39
40
    /**
41
     * @throws Exception
42
     * @throws OptimisticLockFailedException
43
     */
44
    public function save()
45
    {
46
        if ($this->isNew()) {
47
            // insert
48
            $statement = $this->dbObject->prepare(<<<SQL
49
INSERT INTO `request` (
50
	email, ip, name, comment, status, date, emailsent,
51
	emailconfirm, reserved, useragent, forwardedip
52
) VALUES (
53
	:email, :ip, :name, :comment, :status, CURRENT_TIMESTAMP(), :emailsent,
54
	:emailconfirm, :reserved, :useragent, :forwardedip
55
);
56
SQL
57
            );
58
            $statement->bindValue(':email', $this->email);
59
            $statement->bindValue(':ip', $this->ip);
60
            $statement->bindValue(':name', $this->name);
61
            $statement->bindValue(':comment', $this->comment);
62
            $statement->bindValue(':status', $this->status);
63
            $statement->bindValue(':emailsent', $this->emailsent);
64
            $statement->bindValue(':emailconfirm', $this->emailconfirm);
65
            $statement->bindValue(':reserved', $this->reserved);
66
            $statement->bindValue(':useragent', $this->useragent);
67
            $statement->bindValue(':forwardedip', $this->forwardedip);
68
69
            if ($statement->execute()) {
70
                $this->id = (int)$this->dbObject->lastInsertId();
71
            }
72
            else {
73
                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

73
                throw new Exception(/** @scrutinizer ignore-type */ $statement->errorInfo());
Loading history...
74
            }
75
        }
76
        else {
77
            // update
78
            $statement = $this->dbObject->prepare(<<<SQL
79
UPDATE `request` SET
80
	status = :status,
81
	emailsent = :emailsent,
82
	emailconfirm = :emailconfirm,
83
	reserved = :reserved,
84
	updateversion = updateversion + 1
85
WHERE id = :id AND updateversion = :updateversion;
86
SQL
87
            );
88
89
            $statement->bindValue(':id', $this->id);
90
            $statement->bindValue(':updateversion', $this->updateversion);
91
92
            $statement->bindValue(':status', $this->status);
93
            $statement->bindValue(':emailsent', $this->emailsent);
94
            $statement->bindValue(':emailconfirm', $this->emailconfirm);
95
            $statement->bindValue(':reserved', $this->reserved);
96
97
            if (!$statement->execute()) {
98
                throw new Exception($statement->errorInfo());
99
            }
100
101
            if ($statement->rowCount() !== 1) {
102
                throw new OptimisticLockFailedException();
103
            }
104
105
            $this->updateversion++;
106
        }
107
    }
108
109
    /**
110
     * @return string
111
     */
112
    public function getIp()
113
    {
114
        return $this->ip;
115
    }
116
117
    /**
118
     * @param string $ip
119
     */
120 1
    public function setIp($ip)
121
    {
122 1
        $this->ip = $ip;
123 1
    }
124
125
    /**
126
     * @return string
127
     */
128 1
    public function getName()
129
    {
130 1
        return $this->name;
131
    }
132
133
    /**
134
     * @param string $name
135
     */
136 1
    public function setName($name)
137
    {
138 1
        $this->name = $name;
139 1
    }
140
141
    /**
142
     * @return string|null
143
     */
144
    public function getComment()
145
    {
146
        return $this->comment;
147
    }
148
149
    /**
150
     * @param string $comment
151
     */
152
    public function setComment($comment)
153
    {
154
        $this->comment = $comment;
155
    }
156
157
    /**
158
     * @return string
159
     */
160
    public function getStatus()
161
    {
162
        return $this->status;
163
    }
164
165
    /**
166
     * @param string $status
167
     */
168
    public function setStatus($status)
169
    {
170
        $this->status = $status;
171
    }
172
173
    /**
174
     * Returns the time the request was first submitted
175
     *
176
     * @return DateTimeImmutable
177
     */
178
    public function getDate()
179
    {
180
        return new DateTimeImmutable($this->date);
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    public function getEmailSent()
187
    {
188
        return $this->emailsent == "1";
189
    }
190
191
    /**
192
     * @param bool $emailSent
193
     */
194
    public function setEmailSent($emailSent)
195
    {
196
        $this->emailsent = $emailSent ? 1 : 0;
197
    }
198
199
    /**
200
     * @return int|null
201
     */
202
    public function getReserved()
203
    {
204
        return $this->reserved;
205
    }
206
207
    /**
208
     * @param int|null $reserved
209
     */
210
    public function setReserved($reserved)
211
    {
212
        $this->reserved = $reserved;
213
    }
214
215
    /**
216
     * @return string
217
     */
218
    public function getUserAgent()
219
    {
220
        return $this->useragent;
221
    }
222
223
    /**
224
     * @param string $useragent
225
     */
226
    public function setUserAgent($useragent)
227
    {
228
        $this->useragent = $useragent;
229
    }
230
231
    /**
232
     * @return string|null
233
     */
234
    public function getForwardedIp()
235
    {
236
        return $this->forwardedip;
237
    }
238
239
    /**
240
     * @param string|null $forwardedip
241
     */
242
    public function setForwardedIp($forwardedip)
243
    {
244
		// Verify that the XFF chain only contains valid IP addresses, and silently discard anything that isn't.
245
246
		$xff = explode(',', $forwardedip);
247
		$valid = array();
248
249
		foreach ($xff as $ip) {
250
			$ip = trim($ip);
251
			if (filter_var($ip, FILTER_VALIDATE_IP)) {
252
				$valid[] = $ip;
253
			}
254
		}
255
256
		$this->forwardedip = implode(", ", $valid);
257
    }
258
259
    /**
260
     * @return bool
261
     */
262
    public function hasComments()
263
    {
264
        if ($this->hasCommentsResolved) {
265
            return $this->hasComments;
266
        }
267
268
        if ($this->comment != "") {
269
            $this->hasComments = true;
270
            $this->hasCommentsResolved = true;
271
272
            return true;
273
        }
274
275
        $commentsQuery = $this->dbObject->prepare("SELECT COUNT(*) AS num FROM comment WHERE request = :id;");
276
        $commentsQuery->bindValue(":id", $this->id);
277
278
        $commentsQuery->execute();
279
280
        $this->hasComments = ($commentsQuery->fetchColumn() != 0);
281
        $this->hasCommentsResolved = true;
282
283
        return $this->hasComments;
284
    }
285
286
    /**
287
     * @return string
288
     */
289
    public function getEmailConfirm()
290
    {
291
        return $this->emailconfirm;
292
    }
293
294
    /**
295
     * @param string $emailconfirm
296
     */
297
    public function setEmailConfirm($emailconfirm)
298
    {
299
        $this->emailconfirm = $emailconfirm;
300
    }
301
302
    public function generateEmailConfirmationHash()
303
    {
304
        $this->emailconfirm = bin2hex(openssl_random_pseudo_bytes(16));
305
    }
306
307
    /**
308
     * @return string|null
309
     */
310 1
    public function getEmail()
311
    {
312 1
        return $this->email;
313
    }
314
315
    /**
316
     * @param string|null $email
317
     */
318 1
    public function setEmail($email)
319
    {
320 1
        $this->email = $email;
321 1
    }
322
323
    /**
324
     * @return string
325
     * @throws Exception
326
     */
327
    public function getClosureReason()
328
    {
329
        if ($this->status != 'Closed') {
330
            throw new Exception("Can't get closure reason for open request.");
331
        }
332
333
        $statement = $this->dbObject->prepare(<<<SQL
334
SELECT closes.mail_desc
335
FROM log
336
INNER JOIN closes ON log.action = closes.closes
337
WHERE log.objecttype = 'Request'
338
AND log.objectid = :requestId
339
AND log.action LIKE 'Closed%'
340
ORDER BY log.timestamp DESC
341
LIMIT 1;
342
SQL
343
        );
344
345
        $statement->bindValue(":requestId", $this->id);
346
        $statement->execute();
347
        $reason = $statement->fetchColumn();
348
349
        return $reason;
350
    }
351
352
    /**
353
     * Gets a value indicating whether the request was closed as created or not.
354
     */
355
    public function getWasCreated()
356
    {
357
        if ($this->status != 'Closed') {
358
            throw new Exception("Can't get closure reason for open request.");
359
        }
360
361
        $statement = $this->dbObject->prepare(<<<SQL
362
SELECT emailtemplate.oncreated, log.action
363
FROM log
364
LEFT JOIN emailtemplate ON CONCAT('Closed ', emailtemplate.id) = log.action
365
WHERE log.objecttype = 'Request'
366
AND log.objectid = :requestId
367
AND log.action LIKE 'Closed%'
368
ORDER BY log.timestamp DESC
369
LIMIT 1;
370
SQL
371
        );
372
373
        $statement->bindValue(":requestId", $this->id);
374
        $statement->execute();
375
        $onCreated = $statement->fetchColumn(0);
376
        $logAction = $statement->fetchColumn(1);
377
        $statement->closeCursor();
378
379
        if ($onCreated === null) {
380
            return $logAction === "Closed custom-y";
381
        }
382
383
        return (bool)$onCreated;
384
    }
385
386
    /**
387
     * @return DateTime
388
     */
389
    public function getClosureDate()
390
    {
391
        $logQuery = $this->dbObject->prepare(<<<SQL
392
SELECT timestamp FROM log
393
WHERE objectid = :request AND objecttype = 'Request' AND action LIKE 'Closed%'
394
ORDER BY timestamp DESC LIMIT 1;
395
SQL
396
        );
397
        $logQuery->bindValue(":request", $this->getId());
398
        $logQuery->execute();
399
        $logTime = $logQuery->fetchColumn();
400
        $logQuery->closeCursor();
401
402
        return new DateTime($logTime);
403
    }
404
405
    /**
406
     * Returns a hash based on data within this request which can be generated easily from the data to be used to reveal
407
     * data to unauthorised* users.
408
     *
409
     * *:Not tool admins, check users, or the reserving user.
410
     *
411
     * @return string
412
     *
413
     * @todo future work to make invalidation better. Possibly move to the database and invalidate on relevant events?
414
     *       Maybe depend on the last logged action timestamp?
415
     */
416
    public function getRevealHash()
417
    {
418
        $data = $this->id         // unique per request
419
            . '|' . $this->ip           // }
420
            . '|' . $this->forwardedip  // } private data not known to those without access
421
            . '|' . $this->useragent    // }
422
            . '|' . $this->email        // }
423
            . '|' . $this->status;      // to rudimentarily invalidate the token on status change
424
425
        return hash('sha256', $data);
426
    }
427
}
428