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

includes/DataObjects/Request.php (1 issue)

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
			$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()) {
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
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()
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?
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