Failed Conditions
Pull Request — bugsquish (#573)
by Simon
03:02 queued 49s
created

Request::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
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 $status = "Open";
29
    private $date;
30
    private $emailsent = 0;
31
    private $emailconfirm;
32
    /** @var int|null */
33
    private $reserved = null;
34
    private $useragent;
35
    private $forwardedip;
36
    private $hasComments = false;
37
    private $hasCommentsResolved = false;
38
39
    /**
40
     * @throws Exception
41
     * @throws OptimisticLockFailedException
42
     */
43
    public function save()
44
    {
45
        if ($this->isNew()) {
46
            // insert
47
            $statement = $this->dbObject->prepare(<<<SQL
48
INSERT INTO `request` (
49
	email, ip, name, status, date, emailsent,
50
	emailconfirm, reserved, useragent, forwardedip
51
) VALUES (
52
	:email, :ip, :name, :status, CURRENT_TIMESTAMP(), :emailsent,
53
	:emailconfirm, :reserved, :useragent, :forwardedip
54
);
55
SQL
56
            );
57
            $statement->bindValue(':email', $this->email);
58
            $statement->bindValue(':ip', $this->ip);
59
            $statement->bindValue(':name', $this->name);
60
            $statement->bindValue(':status', $this->status);
61
            $statement->bindValue(':emailsent', $this->emailsent);
62
            $statement->bindValue(':emailconfirm', $this->emailconfirm);
63
            $statement->bindValue(':reserved', $this->reserved);
64
            $statement->bindValue(':useragent', $this->useragent);
65
            $statement->bindValue(':forwardedip', $this->forwardedip);
66
67
            if ($statement->execute()) {
68
                $this->id = (int)$this->dbObject->lastInsertId();
69
            }
70
            else {
71
                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

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