Completed
Branch newinternal (e32466)
by Simon
03:39
created

Request::getClosureReason()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 24
ccs 0
cts 9
cp 0
rs 8.9713
c 3
b 0
f 0
cc 2
eloc 10
nc 2
nop 0
crap 6
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
			$statement = $this->dbObject->prepare(<<<SQL
47
INSERT INTO `request` (
48
	email, ip, name, comment, status, date, emailsent,
49
	emailconfirm, reserved, useragent, forwardedip
50
) VALUES (
51
	:email, :ip, :name, :comment, :status, CURRENT_TIMESTAMP(), :emailsent,
52
	:emailconfirm, :reserved, :useragent, :forwardedip
53
);
54
SQL
55
			);
56
			$statement->bindValue(":email", $this->email);
57
			$statement->bindValue(":ip", $this->ip);
58
			$statement->bindValue(":name", $this->name);
59
			$statement->bindValue(":comment", $this->comment);
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 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...
68
				$this->id = (int)$this->dbObject->lastInsertId();
69
			}
70
			else {
71
				throw new Exception($statement->errorInfo());
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
LIMIT 1;
85
SQL
86
			);
87
88
			$statement->bindValue(':id', $this->id);
89
			$statement->bindValue(':updateversion', $this->updateversion);
90
91
			$statement->bindValue(':status', $this->status);
92
			$statement->bindValue(':emailsent', $this->emailsent);
93
			$statement->bindValue(':emailconfirm', $this->emailconfirm);
94
			$statement->bindValue(':reserved', $this->reserved);
95
96
			if (!$statement->execute()) {
97
				throw new Exception($statement->errorInfo());
98
			}
99
100
			if ($statement->rowCount() !== 1) {
101
				throw new OptimisticLockFailedException();
102
			}
103
104
			$this->updateversion++;
105
		}
106
	}
107
108
	/**
109
	 * @return string
110
	 */
111
	public function getIp()
112
	{
113
		return $this->ip;
114
	}
115
116
	/**
117
	 * @param string $ip
118
	 */
119 1
	public function setIp($ip)
120
	{
121 1
		$this->ip = $ip;
122 1
	}
123
124
	/**
125
	 * @return string
126
	 */
127 1
	public function getName()
128
	{
129 1
		return $this->name;
130
	}
131
132
	/**
133
	 * @param string $name
134
	 */
135 1
	public function setName($name)
136
	{
137 1
		$this->name = $name;
138 1
	}
139
140
	/**
141
	 * @return string|null
142
	 */
143
	public function getComment()
144
	{
145
		return $this->comment;
146
	}
147
148
	/**
149
	 * @param string $comment
150
	 */
151
	public function setComment($comment)
152
	{
153
		$this->comment = $comment;
154
	}
155
156
	/**
157
	 * @return string
158
	 */
159
	public function getStatus()
160
	{
161
		return $this->status;
162
	}
163
164
	/**
165
	 * @param string $status
166
	 */
167
	public function setStatus($status)
168
	{
169
		$this->status = $status;
170
	}
171
172
	/**
173
	 * Returns the time the request was first submitted
174
	 *
175
	 * @return DateTimeImmutable
176
	 */
177
	public function getDate()
178
	{
179
		return new DateTimeImmutable($this->date);
180
	}
181
182
	/**
183
	 * @return bool
184
	 */
185
	public function getEmailSent()
186
	{
187
		return $this->emailsent == "1";
188
	}
189
190
	/**
191
	 * @param bool $emailSent
192
	 */
193
	public function setEmailSent($emailSent)
194
	{
195
		$this->emailsent = $emailSent ? 1 : 0;
196
	}
197
198
	/**
199
	 * @return int|null
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use integer.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

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