Completed
Pull Request — master (#526)
by Michael
02:11
created

Request   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 444
Duplicated Lines 0 %

Test Coverage

Coverage 6.5%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 185
dl 0
loc 444
ccs 13
cts 200
cp 0.065
rs 3.6
c 5
b 0
f 0
wmc 60

40 Methods

Rating   Name   Duplication   Size   Complexity  
A hasComments() 0 21 3
A getRelatedIpRequests() 0 26 3
A setIp() 0 3 1
A confirmEmail() 0 12 3
A getRelatedEmailRequests() 0 21 3
A getReservedObject() 0 3 1
A getTrustedIp() 0 3 1
A isBlacklisted() 0 17 4
A setDate() 0 3 1
A setEmailSent() 0 3 1
A setName() 0 3 1
A setReserved() 0 3 1
A setEmail() 0 3 1
A setStatus() 0 3 1
A setComment() 0 3 1
A getEmail() 0 3 1
A getComment() 0 3 1
A setChecksum() 0 3 1
A save() 0 44 4
A getStatus() 0 3 1
A setEmailConfirm() 0 3 1
A cleanExpiredUnconfirmedRequests() 0 15 1
A getComments() 0 3 1
A setUserAgent() 0 3 1
A setForwardedIp() 0 15 3
A getName() 0 3 1
A getChecksum() 0 3 1
A getReserved() 0 3 1
A getObjectDescription() 0 3 1
A generateEmailConfirmationHash() 0 3 1
A getIp() 0 3 1
A getEmailConfirm() 0 3 1
A updateChecksum() 0 3 1
A getForwardedIp() 0 3 1
A getDate() 0 3 1
A getUserAgent() 0 3 1
A isProtected() 0 12 3
A getEmailSent() 0 3 1
A getClosureReason() 0 23 2
A sendConfirmationEmail() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Request data object
5
 *
6
 * This data object is the main request object.
7
 */
8
class Request extends DataObject
9
{
10
	private $email;
11
	private $ip;
12
	private $name;
13
	private $comment;
14
	private $status = "Open";
15
	private $date;
16
	private $checksum = 0;
17
	private $emailsent = 0;
18
	private $emailconfirm;
19
	private $reserved = 0;
20
	private $useragent;
21
	private $forwardedip;
22
23
	private $hasComments = false;
24
	private $hasCommentsResolved = false;
25
26
	/**
27
	 * @var Request[]
28
	 */
29
	private $ipRequests;
30
	private $ipRequestsResolved = false;
31
32
	/**
33
	 * @var Request[]
34
	 */
35
	private $emailRequests;
36
	private $emailRequestsResolved = false;
37
38
	private $blacklistCache = null;
39
40
	/**
41
	 * This function removes all old requests which are not yet email-confirmed
42
	 * from the database.
43
	 */
44
	public static function cleanExpiredUnconfirmedRequests()
45
	{
46
		global $emailConfirmationExpiryDays;
47
48
		$database = gGetDb();
49
		$statement = $database->prepare(<<<SQL
50
            DELETE FROM request
51
            WHERE
52
                date < DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL $emailConfirmationExpiryDays DAY)
53
                AND emailconfirm != 'Confirmed'
54
                AND emailconfirm != '';
55
SQL
56
		);
57
58
		$statement->execute();
59
	}
60
61
	public function save()
62
	{
63
		if ($this->isNew) {
64
// insert
65
			$statement = $this->dbObject->prepare(
66
				"INSERT INTO `request` (" .
67
				"email, ip, name, comment, status, date, checksum, emailsent, emailconfirm, reserved, useragent, forwardedip" .
68
				") VALUES (" .
69
				":email, :ip, :name, :comment, :status, CURRENT_TIMESTAMP(), :checksum, :emailsent," .
70
				":emailconfirm, :reserved, :useragent, :forwardedip" .
71
				");");
72
			$statement->bindValue(":email", $this->email);
73
			$statement->bindValue(":ip", $this->ip);
74
			$statement->bindValue(":name", $this->name);
75
			$statement->bindValue(":comment", $this->comment);
76
			$statement->bindValue(":status", $this->status);
77
			$statement->bindValue(":checksum", $this->checksum);
78
			$statement->bindValue(":emailsent", $this->emailsent);
79
			$statement->bindValue(":emailconfirm", $this->emailconfirm);
80
			$statement->bindValue(":reserved", $this->reserved);
81
			$statement->bindValue(":useragent", $this->useragent);
82
			$statement->bindValue(":forwardedip", $this->forwardedip);
83
			if ($statement->execute()) {
84
				$this->isNew = false;
85
				$this->id = (int)$this->dbObject->lastInsertId();
86
			}
87
			else {
88
				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

88
				throw new Exception(/** @scrutinizer ignore-type */ $statement->errorInfo());
Loading history...
89
			}
90
		}
91
		else {
92
// update
93
			$statement = $this->dbObject->prepare("UPDATE `request` SET " .
94
				"status = :status, checksum = :checksum, emailsent = :emailsent, emailconfirm = :emailconfirm, " .
95
				"reserved = :reserved " .
96
				"WHERE id = :id;");
97
			$statement->bindValue(":id", $this->id);
98
			$statement->bindValue(":status", $this->status);
99
			$statement->bindValue(":checksum", $this->checksum);
100
			$statement->bindValue(":emailsent", $this->emailsent);
101
			$statement->bindValue(":emailconfirm", $this->emailconfirm);
102
			$statement->bindValue(":reserved", $this->reserved);
103
			if (!$statement->execute()) {
104
				throw new Exception($statement->errorInfo());
105
			}
106
		}
107
108
	}
109
110 1
	public function getEmail()
111
	{
112 1
		return $this->email;
113
	}
114
115
	/**
116
	 * @param string $email
117
	 */
118 1
	public function setEmail($email)
119
	{
120 1
		$this->email = $email;
121 1
	}
122
123
	public function getIp()
124
	{
125
		return $this->ip;
126
	}
127
128
	public function getTrustedIp()
129
	{
130
		return trim(getTrustedClientIP($this->ip, $this->forwardedip));
131
	}
132
133
	/**
134
	 * @param string $ip
135
	 */
136 1
	public function setIp($ip)
137
	{
138 1
		$this->ip = $ip;
139 1
	}
140
141 1
	public function getName()
142
	{
143 1
		return $this->name;
144
	}
145
146
	/**
147
	 * @param string $name
148
	 */
149 1
	public function setName($name)
150
	{
151 1
		$this->name = $name;
152 1
	}
153
154
	public function getComment()
155
	{
156
		return $this->comment;
157
	}
158
159
	public function setComment($comment)
160
	{
161
		$this->comment = $comment;
162
	}
163
164
	public function getStatus()
165
	{
166
		return $this->status;
167
	}
168
169
	/**
170
	 * @param string $status
171
	 */
172
	public function setStatus($status)
173
	{
174
		$this->status = $status;
175
	}
176
177
	public function getDate()
178
	{
179
		return $this->date;
180
	}
181
182
	public function setDate($date)
183
	{
184
		$this->date = $date;
185
	}
186
187
	public function getChecksum()
188
	{
189
		return $this->checksum;
190
	}
191
192
	public function setChecksum($checksum)
193
	{
194
		$this->checksum = $checksum;
195
	}
196
197
	public function updateChecksum()
198
	{
199
		$this->checksum = md5($this->id . $this->name . $this->email . microtime());
200
	}
201
202
	public function getEmailSent()
203
	{
204
		return $this->emailsent;
205
	}
206
207
	public function setEmailSent($emailsent)
208
	{
209
		$this->emailsent = $emailsent;
210
	}
211
212
	public function getEmailConfirm()
213
	{
214
		return $this->emailconfirm;
215
	}
216
217
	/**
218
	 * @param string $emailconfirm
219
	 */
220
	public function setEmailConfirm($emailconfirm)
221
	{
222
		$this->emailconfirm = $emailconfirm;
223
	}
224
225
	public function getReserved()
226
	{
227
		return $this->reserved;
228
	}
229
230
	public function getReservedObject()
231
	{
232
		return User::getById($this->reserved, $this->dbObject);
233
	}
234
235
	public function setReserved($reserved)
236
	{
237
		$this->reserved = $reserved;
238
	}
239
240
	public function getUserAgent()
241
	{
242
		return $this->useragent;
243
	}
244
245
	public function setUserAgent($useragent)
246
	{
247
		$this->useragent = $useragent;
248
	}
249
250
	public function getForwardedIp()
251
	{
252
		return $this->forwardedip;
253
	}
254
255
	public function setForwardedIp($forwardedip)
256
	{
257
		// Verify that the XFF chain only contains valid IP addresses, and silently discard anything that isn't.
258
259
		$xff = explode(',', $forwardedip);
260
		$valid = array();
261
262
		foreach ($xff as $ip) {
263
			$ip = trim($ip);
264
			if (filter_var($ip, FILTER_VALIDATE_IP)) {
265
				$valid[] = $ip;
266
			}
267
		}
268
269
		$this->forwardedip = implode(", ", $valid);
270
	}
271
272
	public function hasComments()
273
	{
274
		if ($this->hasCommentsResolved) {
275
			return $this->hasComments;
276
		}
277
278
		if ($this->comment != "") {
279
			$this->hasComments = true;
280
			$this->hasCommentsResolved = true;
281
			return true;
282
		}
283
284
		$commentsQuery = $this->dbObject->prepare("SELECT COUNT(*) as num FROM comment where request = :id;");
285
		$commentsQuery->bindValue(":id", $this->id);
286
287
		$commentsQuery->execute();
288
289
		$this->hasComments = ($commentsQuery->fetchColumn() != 0);
290
		$this->hasCommentsResolved = true;
291
292
		return $this->hasComments;
293
	}
294
295
	public function getRelatedEmailRequests()
296
	{
297
		if ($this->emailRequestsResolved == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
298
			global $cDataClearEmail;
299
300
			$query = $this->dbObject->prepare("SELECT * FROM request WHERE email = :email AND email != :clearedemail AND id != :id AND emailconfirm = 'Confirmed';");
301
			$query->bindValue(":id", $this->id);
302
			$query->bindValue(":email", $this->email);
303
			$query->bindValue(":clearedemail", $cDataClearEmail);
304
305
			$query->execute();
306
307
			$this->emailRequests = $query->fetchAll(PDO::FETCH_CLASS, "Request");
308
			$this->emailRequestsResolved = true;
309
310
			foreach ($this->emailRequests as $r) {
311
				$r->setDatabase($this->dbObject);
312
			}
313
		}
314
315
		return $this->emailRequests;
316
	}
317
318
	public function getRelatedIpRequests()
319
	{
320
		if ($this->ipRequestsResolved == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
321
			global $cDataClearIp;
322
323
			$query = $this->dbObject->prepare("SELECT * FROM request WHERE (ip = :ip OR forwardedip LIKE :forwarded) AND ip != :clearedip AND id != :id AND emailconfirm = 'Confirmed';");
324
325
			$trustedIp = $this->getTrustedIp();
326
			$trustedFilter = '%' . $trustedIp . '%';
327
328
			$query->bindValue(":id", $this->id);
329
			$query->bindValue(":ip", $trustedIp);
330
			$query->bindValue(":forwarded", $trustedFilter);
331
			$query->bindValue(":clearedip", $cDataClearIp);
332
333
			$query->execute();
334
335
			$this->ipRequests = $query->fetchAll(PDO::FETCH_CLASS, "Request");
336
			$this->ipRequestsResolved = true;
337
338
			foreach ($this->ipRequests as $r) {
339
				$r->setDatabase($this->dbObject);
340
			}
341
		}
342
343
		return $this->ipRequests;
344
	}
345
346
	public function isBlacklisted()
347
	{
348
		global $enableTitleBlacklist;
349
350
		if (!$enableTitleBlacklist || $this->blacklistCache === false) {
351
			return false;
352
		}
353
354
		$apiResult = file_get_contents("https://en.wikipedia.org/w/api.php?action=titleblacklist&tbtitle=" . urlencode($this->name) . "&tbaction=new-account&tbnooverride&format=php");
355
356
		$data = unserialize($apiResult);
357
358
		$result = $data['titleblacklist']['result'] == "ok";
359
360
		$this->blacklistCache = $result ? false : $data['titleblacklist']['line'];
361
362
		return $this->blacklistCache;
363
	}
364
365
	public function getComments()
366
	{
367
		return Comment::getForRequest($this->id, $this->dbObject);
368
	}
369
370
	public function isProtected()
371
	{
372
		if ($this->reserved != 0) {
373
			if ($this->reserved == User::getCurrent()->getId()) {
374
				return false;
375
			}
376
			else {
377
				return true;
378
			}
379
		}
380
		else {
381
			return false;
382
		}
383
384
	}
385
386
	public function confirmEmail($si)
387
	{
388
		if ($this->getEmailConfirm() == "Confirmed") {
389
			// already confirmed. Act as though we've completed successfully.
390
			return;
391
		}
392
393
		if ($this->getEmailConfirm() == $si) {
394
			$this->setEmailConfirm("Confirmed");
395
		}
396
		else {
397
			throw new TransactionException("Confirmation hash does not match the expected value", "Email confirmation failed");
398
		}
399
	}
400
401
	public function generateEmailConfirmationHash()
402
	{
403
		$this->emailconfirm = bin2hex(openssl_random_pseudo_bytes(16));
404
	}
405
406
	public function sendConfirmationEmail()
407
	{
408
		global $smarty;
409
410
		$smarty->assign("ip", $this->getTrustedIp());
411
		$smarty->assign("id", $this->getId());
412
		$smarty->assign("hash", $this->getEmailConfirm());
413
414
		$headers = 'From: [email protected]';
415
416
		// Sends the confirmation email to the user.
417
		$mailsuccess = mail($this->getEmail(), "[ACC #{$this->getId()}] English Wikipedia Account Request", $smarty->fetch('request/confirmation-mail.tpl'), $headers);
418
419
		if (!$mailsuccess) {
420
			throw new Exception("Error sending email.");
421
		}
422
	}
423
	
424
	public function getObjectDescription()
425
	{
426
		return '<a href="acc.php?action=zoom&amp;id=' . $this->getId() . '">Request #' . $this->getId() . " (" . htmlentities($this->name) . ")</a>";
427
	}
428
429
	public function getClosureReason()
430
	{
431
		if ($this->status != 'Closed') {
432
			throw new Exception("Can't get closure reason for open request.");
433
		}
434
435
		$statement = $this->dbObject->prepare(<<<SQL
436
SELECT closes.mail_desc
437
FROM log
438
INNER JOIN closes ON log.action = closes.closes
439
WHERE log.objecttype = 'Request'
440
AND log.objectid = :requestId
441
AND log.action LIKE 'Closed%'
442
ORDER BY log.timestamp DESC
443
LIMIT 1;
444
SQL
445
		);
446
447
		$statement->bindValue(":requestId", $this->id);
448
		$statement->execute();
449
		$reason = $statement->fetchColumn();
450
451
		return $reason;
452
	}
453
}
454