Failed Conditions
Branch newinternal (3df7fe)
by Simon
04:13
created

Request::getRevealHash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 11
ccs 0
cts 8
cp 0
rs 9.4285
cc 1
eloc 8
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 $comment;
29
	private $status = "Open";
30
	private $date;
31
	private $emailsent = 0;
32
	private $emailconfirm;
33
	private $reserved = 0;
34
	private $useragent;
35
	private $forwardedip;
36
	private $hasComments = false;
37
	private $hasCommentsResolved = false;
38
39
	/**
40
	 * @throws Exception
41
	 */
42
	public function save()
43
	{
44
		if ($this->isNew()) {
45
			// insert
46
			// @todo drop the checksum column
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
47
			$statement = $this->dbObject->prepare(<<<SQL
48
INSERT INTO `request` (
49
	email, ip, name, comment, status, date, emailsent,
50
	emailconfirm, reserved, useragent, forwardedip, checksum
51
) VALUES (
52
	:email, :ip, :name, :comment, :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(":comment", $this->comment);
61
			$statement->bindValue(":status", $this->status);
62
			$statement->bindValue(":emailsent", $this->emailsent);
63
			$statement->bindValue(":emailconfirm", $this->emailconfirm);
64
			$statement->bindValue(":reserved", $this->reserved);
65
			$statement->bindValue(":useragent", $this->useragent);
66
			$statement->bindValue(":forwardedip", $this->forwardedip);
67
68 View Code Duplication
			if ($statement->execute()) {
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...
69
				$this->id = (int)$this->dbObject->lastInsertId();
70
			}
71
			else {
72
				throw new Exception($statement->errorInfo());
73
			}
74
		}
75
		else {
76
			// update
77
			$statement = $this->dbObject->prepare(<<<SQL
78
UPDATE `request` SET
79
	status = :status,
80
	emailsent = :emailsent,
81
	emailconfirm = :emailconfirm,
82
	reserved = :reserved,
83
	updateversion = updateversion + 1
84
WHERE id = :id AND updateversion = :updateversion
85
LIMIT 1;
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
	 * @todo allow this to return null instead
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
201
	 * @return int
202
	 */
203
	public function getReserved()
204
	{
205
		return $this->reserved;
206
	}
207
208
	/**
209
	 * @param int|null $reserved
210
	 */
211
	public function setReserved($reserved)
212
	{
213
		if ($reserved === null) {
214
			// @todo this shouldn't be needed!
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
215
			$reserved = 0;
216
		}
217
218
		$this->reserved = $reserved;
219
	}
220
221
	/**
222
	 * @return string
223
	 */
224
	public function getUserAgent()
225
	{
226
		return $this->useragent;
227
	}
228
229
	/**
230
	 * @param string $useragent
231
	 */
232
	public function setUserAgent($useragent)
233
	{
234
		$this->useragent = $useragent;
235
	}
236
237
	/**
238
	 * @return string|null
239
	 */
240
	public function getForwardedIp()
241
	{
242
		return $this->forwardedip;
243
	}
244
245
	/**
246
	 * @param string|null $forwardedip
247
	 */
248
	public function setForwardedIp($forwardedip)
249
	{
250
		$this->forwardedip = $forwardedip;
251
	}
252
253
	/**
254
	 * @return bool
255
	 */
256
	public function hasComments()
257
	{
258
		if ($this->hasCommentsResolved) {
259
			return $this->hasComments;
260
		}
261
262
		if ($this->comment != "") {
263
			$this->hasComments = true;
264
			$this->hasCommentsResolved = true;
265
266
			return true;
267
		}
268
269
		$commentsQuery = $this->dbObject->prepare("SELECT COUNT(*) AS num FROM comment WHERE request = :id;");
270
		$commentsQuery->bindValue(":id", $this->id);
271
272
		$commentsQuery->execute();
273
274
		$this->hasComments = ($commentsQuery->fetchColumn() != 0);
275
		$this->hasCommentsResolved = true;
276
277
		return $this->hasComments;
278
	}
279
280
	/**
281
	 * @return string
282
	 */
283
	public function getEmailConfirm()
284
	{
285
		return $this->emailconfirm;
286
	}
287
288
	/**
289
	 * @param string $emailconfirm
290
	 */
291
	public function setEmailConfirm($emailconfirm)
292
	{
293
		$this->emailconfirm = $emailconfirm;
294
	}
295
296
	public function generateEmailConfirmationHash()
297
	{
298
		$this->emailconfirm = bin2hex(openssl_random_pseudo_bytes(16));
299
	}
300
301
	/**
302
	 * @return string|null
303
	 */
304 1
	public function getEmail()
305
	{
306 1
		return $this->email;
307
	}
308
309
	/**
310
	 * @param string|null $email
311
	 */
312 1
	public function setEmail($email)
313
	{
314 1
		$this->email = $email;
315 1
	}
316
317
	/**
318
	 * @return string
319
	 * @throws Exception
320
	 */
321
	public function getClosureReason()
322
	{
323
		if ($this->status != 'Closed') {
324
			throw new Exception("Can't get closure reason for open request.");
325
		}
326
327
		$statement = $this->dbObject->prepare(<<<SQL
328
SELECT closes.mail_desc
329
FROM log
330
INNER JOIN closes ON log.action = closes.closes
331
WHERE log.objecttype = 'Request'
332
AND log.objectid = :requestId
333
AND log.action LIKE 'Closed%'
334
ORDER BY log.timestamp DESC
335
LIMIT 1;
336
SQL
337
		);
338
339
		$statement->bindValue(":requestId", $this->id);
340
		$statement->execute();
341
		$reason = $statement->fetchColumn();
342
343
		return $reason;
344
	}
345
346
	/**
347
	 * Gets a value indicating whether the request was closed as created or not.
348
	 */
349
	public function getWasCreated()
350
	{
351
		if ($this->status != 'Closed') {
352
			throw new Exception("Can't get closure reason for open request.");
353
		}
354
355
		$statement = $this->dbObject->prepare(<<<SQL
356
SELECT emailtemplate.oncreated, log.action
357
FROM log
358
LEFT JOIN emailtemplate ON CONCAT('Closed ', emailtemplate.id) = log.action
359
WHERE log.objecttype = 'Request'
360
AND log.objectid = :requestId
361
AND log.action LIKE 'Closed%'
362
ORDER BY log.timestamp DESC
363
LIMIT 1;
364
SQL
365
		);
366
367
		$statement->bindValue(":requestId", $this->id);
368
		$statement->execute();
369
		$onCreated = $statement->fetchColumn(0);
370
		$logAction = $statement->fetchColumn(1);
371
		$statement->closeCursor();
372
373
		if ($onCreated === null) {
374
			return $logAction === "Closed custom-y";
375
		}
376
377
		return (bool)$onCreated;
378
	}
379
380
	/**
381
	 * @return DateTime
382
	 */
383 View Code Duplication
	public function getClosureDate()
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...
384
	{
385
		$logQuery = $this->dbObject->prepare(<<<SQL
386
SELECT timestamp FROM log
387
WHERE objectid = :request AND objecttype = 'Request' AND action LIKE 'Closed%'
388
ORDER BY timestamp DESC LIMIT 1;
389
SQL
390
		);
391
		$logQuery->bindValue(":request", $this->getId());
392
		$logQuery->execute();
393
		$logTime = $logQuery->fetchColumn();
394
		$logQuery->closeCursor();
395
396
		return new DateTime($logTime);
397
	}
398
399
	/**
400
	 * Returns a hash based on data within this request which can be generated easily from the data to be used to reveal
401
	 * data to unauthorised* users.
402
	 *
403
	 * *:Not tool admins, check users, or the reserving user.
404
	 *
405
	 * @return string
406
	 *
407
	 * @todo future work to make invalidation better. Possibly move to the database and invalidate on relevant events?
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
408
	 *       Maybe depend on the last logged action timestamp?
409
	 */
410
	public function getRevealHash()
411
	{
412
		$data = $this->id         // unique per request
413
			. '|' . $this->ip           // }
414
			. '|' . $this->forwardedip  // } private data not known to those without access
415
			. '|' . $this->useragent    // }
416
			. '|' . $this->email        // }
417
			. '|' . $this->status;      // to rudimentarily invalidate the token on status change
418
419
		return hash('sha256', $data);
420
	}
421
}
422