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

includes/DataObjects/Request.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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()) {
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
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!
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()
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
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