Failed Conditions
Branch newinternal (104de7)
by Simon
09:33
created

LogHelper::getLogs()   C

Complexity

Conditions 8
Paths 64

Size

Total Lines 97
Code Lines 68

Duplication

Lines 36
Ratio 37.11 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 36
loc 97
rs 5.3652
cc 8
eloc 68
nc 64
nop 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: stwalkerster
5
 * Date: 26/03/2016
6
 * Time: 02:55
7
 */
8
9
namespace Waca\Helpers;
10
11
use Exception;
12
use PDO;
13
use Waca\DataObject;
14
use Waca\DataObjects\Ban;
15
use Waca\DataObjects\Comment;
16
use Waca\DataObjects\EmailTemplate;
17
use Waca\DataObjects\Log;
18
use Waca\DataObjects\Request;
19
use Waca\DataObjects\User;
20
use Waca\DataObjects\WelcomeTemplate;
21
use Waca\PdoDatabase;
22
use Waca\SiteConfiguration;
23
24
class LogHelper
25
{
26
	/**
27
	 * Summary of getRequestLogs
28
	 *
29
	 * @param int         $requestId ID of the request to get logs for
30
	 * @param PdoDatabase $db        Database to use
31
	 *
32
	 * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|false.

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...
33
	 */
34
	public static function getRequestLogs($requestId, PdoDatabase $db)
35
	{
36
		$logStatement = $db->prepare(
37
			<<<SQL
38
SELECT * FROM log
39
WHERE objecttype = 'Request' AND objectid = :requestId
40
ORDER BY timestamp DESC
41
SQL
42
		);
43
44
		$result = $logStatement->execute(array(":requestId" => $requestId));
45
		if ($result) {
46
			$data = $logStatement->fetchAll(PDO::FETCH_CLASS, Log::class);
47
48
			/** @var Log $entry */
49
			foreach ($data as $entry) {
50
				$entry->setDatabase($db);
51
			}
52
53
			return $data;
54
		}
55
56
		return false;
57
	}
58
59
	/**
60
	 * Summary of getRequestLogsWithComments
61
	 *
62
	 * @param int         $requestId
63
	 * @param PdoDatabase $db
64
	 *
65
	 * @return array
66
	 */
67
	public static function getRequestLogsWithComments($requestId, PdoDatabase $db)
68
	{
69
		$logs = self::getRequestLogs($requestId, $db);
70
		$comments = Comment::getForRequest($requestId, $db);
71
72
		$items = array_merge($logs, $comments);
73
74
		/**
75
		 * @param DataObject $item
76
		 *
77
		 * @return int
78
		 */
79
		$sortKey = function(DataObject $item) {
80
			if ($item instanceof Log) {
81
				return $item->getTimestamp()->getTimestamp();
82
			}
83
84
			if ($item instanceof Comment) {
85
				return $item->getTime()->getTimestamp();
86
			}
87
88
			return 0;
89
		};
90
91
		do {
92
			$flag = false;
93
94
			$loopLimit = (count($items) - 1);
95
			for ($i = 0; $i < $loopLimit; $i++) {
96
				// are these two items out of order?
97
				if ($sortKey($items[$i]) > $sortKey($items[$i + 1])) {
98
					// swap them
99
					$swap = $items[$i];
100
					$items[$i] = $items[$i + 1];
101
					$items[$i + 1] = $swap;
102
103
					// set a flag to say we've modified the array this time around
104
					$flag = true;
105
				}
106
			}
107
		}
108
		while ($flag);
109
110
		return $items;
111
	}
112
113
	/**
114
	 * Summary of getLogDescription
115
	 *
116
	 * @param Log $entry
117
	 *
118
	 * @return string
119
	 */
120
	public static function getLogDescription(Log $entry)
121
	{
122
		$text = "Deferred to ";
123
		if (substr($entry->getAction(), 0, strlen($text)) == $text) {
124
			// Deferred to a different queue
125
			// This is exactly what we want to display.
126
			return $entry->getAction();
127
		}
128
129
		$text = "Closed custom-n";
130
		if ($entry->getAction() == $text) {
131
			// Custom-closed
132
			return "closed (custom reason - account not created)";
133
		}
134
135
		$text = "Closed custom-y";
136
		if ($entry->getAction() == $text) {
137
			// Custom-closed
138
			return "closed (custom reason - account created)";
139
		}
140
141
		$text = "Closed 0";
142
		if ($entry->getAction() == $text) {
143
			// Dropped the request - short-circuit the lookup
144
			return "dropped request";
145
		}
146
147
		$text = "Closed ";
148
		if (substr($entry->getAction(), 0, strlen($text)) == $text) {
149
			// Closed with a reason - do a lookup here.
150
			$id = substr($entry->getAction(), strlen($text));
151
			/** @var EmailTemplate $template */
152
			$template = EmailTemplate::getById((int)$id, $entry->getDatabase());
153
154
			if ($template != false) {
155
				return "closed (" . $template->getName() . ")";
156
			}
157
		}
158
159
		// Fall back to the basic stuff
160
		$lookup = array(
161
			'Reserved'        => 'reserved',
162
			'Email Confirmed' => 'email-confirmed',
163
			'Unreserved'      => 'unreserved',
164
			'Approved'        => 'approved',
165
			'Suspended'       => 'suspended',
166
			'Banned'          => 'banned',
167
			'Edited'          => 'edited interface message',
168
			'Declined'        => 'declined',
169
			'EditComment-c'   => 'edited a comment',
170
			'EditComment-r'   => 'edited a comment',
171
			'Unbanned'        => 'unbanned',
172
			'Promoted'        => 'promoted to tool admin',
173
			'BreakReserve'    => 'forcibly broke the reservation',
174
			'Prefchange'      => 'changed user preferences',
175
			'Renamed'         => 'renamed',
176
			'Demoted'         => 'demoted from tool admin',
177
			'ReceiveReserved' => 'received the reservation',
178
			'SendReserved'    => 'sent the reservation',
179
			'EditedEmail'     => 'edited email',
180
			'DeletedTemplate' => 'deleted template',
181
			'EditedTemplate'  => 'edited template',
182
			'CreatedEmail'    => 'created email',
183
			'CreatedTemplate' => 'created template',
184
			'SentMail'        => 'sent an email to the requestor',
185
			'Registered'      => 'registered a tool account',
186
		);
187
188
		if (array_key_exists($entry->getAction(), $lookup)) {
189
			return $lookup[$entry->getAction()];
190
		}
191
192
		// OK, I don't know what this is. Fall back to something sane.
193
		return "performed an unknown action ({$entry->getAction()})";
194
	}
195
196
	/**
197
	 * @param PdoDatabase $database
198
	 *
199
	 * @return array
200
	 */
201
	public static function getLogActions(PdoDatabase $database)
202
	{
203
		$lookup = array(
204
			'Reserved'        => 'reserved',
205
			'Email Confirmed' => 'email-confirmed',
206
			'Unreserved'      => 'unreserved',
207
			'Approved'        => 'approved',
208
			'Suspended'       => 'suspended',
209
			'Banned'          => 'banned',
210
			'Edited'          => 'edited interface message',
211
			'Declined'        => 'declined',
212
			'EditComment-c'   => 'edited a comment',
213
			'EditComment-r'   => 'edited a comment',
214
			'Unbanned'        => 'unbanned',
215
			'Promoted'        => 'promoted to tool admin',
216
			'BreakReserve'    => 'forcibly broke the reservation',
217
			'Prefchange'      => 'changed user preferences',
218
			'Renamed'         => 'renamed',
219
			'Demoted'         => 'demoted from tool admin',
220
			'ReceiveReserved' => 'received the reservation',
221
			'SendReserved'    => 'sent the reservation',
222
			'EditedEmail'     => 'edited email',
223
			'DeletedTemplate' => 'deleted template',
224
			'EditedTemplate'  => 'edited template',
225
			'CreatedEmail'    => 'created email',
226
			'CreatedTemplate' => 'created template',
227
			'SentMail'        => 'sent an email to the requestor',
228
			'Registered'      => 'registered a tool account',
229
		);
230
231
		$statement = $database->query(<<<SQL
232
SELECT CONCAT('Closed ', id) AS k, CONCAT('closed (',name,')') AS v
233
FROM emailtemplate;
234
SQL
235
		);
236
		foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
237
			$lookup[$row['k']] = $row['v'];
238
		}
239
240
		return $lookup;
241
	}
242
243
	/**
244
	 * Summary of getLogs
245
	 *
246
	 * @param PdoDatabase  $database
247
	 * @param string|null  $userFilter
248
	 * @param string|null  $actionFilter
249
	 * @param string|null  $objectTypeFilter
250
	 * @param integer|null $objectFilter
251
	 * @param integer      $limit
252
	 * @param integer      $offset
253
	 *
254
	 * @return array
255
	 */
256
	public static function getLogs(
257
		PdoDatabase $database,
258
		$userFilter,
259
		$actionFilter,
260
		$objectTypeFilter = null,
261
		$objectFilter = null,
262
		$limit = 100,
263
		$offset = 0
264
	) {
265
		$whereClause = <<<TXT
266
(:userFilter = 0 OR user = :userid)
267
AND (:actionFilter = 0 OR action = :action)
268
AND (:objectFilter = 0 OR objectid = :object)
269
AND (:objectTypeFilter = 0 OR objecttype = :objectType)
270
TXT;
271
		$searchSqlStatement = "SELECT * FROM log WHERE $whereClause ORDER BY timestamp DESC LIMIT :limit OFFSET :offset;";
272
		$countSqlStatement = "SELECT COUNT(1) FROM log WHERE $whereClause;";
273
274
		$searchStatement = $database->prepare($searchSqlStatement);
275
		$countStatement = $database->prepare($countSqlStatement);
276
277
		$searchStatement->bindValue(":limit", $limit, PDO::PARAM_INT);
278
		$searchStatement->bindValue(":offset", $offset, PDO::PARAM_INT);
279
280
		if ($userFilter === null) {
281
			$searchStatement->bindValue(":userFilter", 0, PDO::PARAM_INT);
282
			$countStatement->bindValue(":userFilter", 0, PDO::PARAM_INT);
283
			$searchStatement->bindValue(":userid", 0, PDO::PARAM_INT);
284
			$countStatement->bindValue(":userid", 0, PDO::PARAM_INT);
285
		}
286
		else {
287
			$searchStatement->bindValue(":userFilter", 1, PDO::PARAM_INT);
288
			$countStatement->bindValue(":userFilter", 1, PDO::PARAM_INT);
289
			$searchStatement->bindValue(":userid", User::getByUsername($userFilter, $database)->getId(),
290
				PDO::PARAM_INT);
291
			$countStatement->bindValue(":userid", User::getByUsername($userFilter, $database)->getId(), PDO::PARAM_INT);
292
		}
293
294 View Code Duplication
		if ($actionFilter === null) {
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...
295
			$searchStatement->bindValue(":actionFilter", 0, PDO::PARAM_INT);
296
			$countStatement->bindValue(":actionFilter", 0, PDO::PARAM_INT);
297
			$searchStatement->bindValue(":action", "", PDO::PARAM_STR);
298
			$countStatement->bindValue(":action", "", PDO::PARAM_STR);
299
		}
300
		else {
301
			$searchStatement->bindValue(":actionFilter", 1, PDO::PARAM_INT);
302
			$countStatement->bindValue(":actionFilter", 1, PDO::PARAM_INT);
303
			$searchStatement->bindValue(":action", $actionFilter, PDO::PARAM_STR);
304
			$countStatement->bindValue(":action", $actionFilter, PDO::PARAM_STR);
305
		}
306
307 View Code Duplication
		if ($objectTypeFilter === null) {
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...
308
			$searchStatement->bindValue(":objectTypeFilter", 0, PDO::PARAM_INT);
309
			$countStatement->bindValue(":objectTypeFilter", 0, PDO::PARAM_INT);
310
			$searchStatement->bindValue(":objectType", "", PDO::PARAM_STR);
311
			$countStatement->bindValue(":objectType", "", PDO::PARAM_STR);
312
		}
313
		else {
314
			$searchStatement->bindValue(":objectTypeFilter", 1, PDO::PARAM_INT);
315
			$countStatement->bindValue(":objectTypeFilter", 1, PDO::PARAM_INT);
316
			$searchStatement->bindValue(":objectType", $objectTypeFilter, PDO::PARAM_STR);
317
			$countStatement->bindValue(":objectType", $objectTypeFilter, PDO::PARAM_STR);
318
		}
319
320 View Code Duplication
		if ($objectFilter === null) {
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...
321
			$searchStatement->bindValue(":objectFilter", 0, PDO::PARAM_INT);
322
			$countStatement->bindValue(":objectFilter", 0, PDO::PARAM_INT);
323
			$searchStatement->bindValue(":object", "", PDO::PARAM_STR);
324
			$countStatement->bindValue(":object", "", PDO::PARAM_STR);
325
		}
326
		else {
327
			$searchStatement->bindValue(":objectFilter", 1, PDO::PARAM_INT);
328
			$countStatement->bindValue(":objectFilter", 1, PDO::PARAM_INT);
329
			$searchStatement->bindValue(":object", $objectFilter, PDO::PARAM_INT);
330
			$countStatement->bindValue(":object", $objectFilter, PDO::PARAM_INT);
331
		}
332
333
		if (!$countStatement->execute()) {
334
			return array(false, false);
335
		}
336
337
		$count = $countStatement->fetchColumn(0);
338
		$countStatement->closeCursor();
339
340
		if ($searchStatement->execute()) {
341
			$data = $searchStatement->fetchAll(PDO::FETCH_CLASS, Log::class);
342
343
			/** @var Log $entry */
344
			foreach ($data as $entry) {
345
				$entry->setDatabase($database);
346
			}
347
348
			return array($data, $count);
349
		}
350
351
		return array(false, false);
352
	}
353
354
	/**
355
	 * This returns a HTML
356
	 *
357
	 * @param string            $objectId
358
	 * @param string            $objectType
359
	 * @param PdoDatabase       $database
360
	 * @param SiteConfiguration $configuration
361
	 *
362
	 * @return null|string
363
	 * @category Security-Critical
364
	 */
365
	private static function getObjectDescription($objectId, $objectType, PdoDatabase $database, SiteConfiguration $configuration)
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
366
	{
367
		if ($objectType == '') {
368
			return null;
369
		}
370
371
		$baseurl = $configuration->getBaseUrl();
372
373
		switch ($objectType) {
374
			case 'Ban':
375
				/** @var Ban $ban */
376
				$ban = Ban::getById($objectId, $database);
377
378
				return 'Ban #' . $objectId . " (" . htmlentities($ban->getTarget()) . ")</a>";
379 View Code Duplication
			case 'EmailTemplate':
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...
380
				/** @var EmailTemplate $emailTemplate */
381
				$emailTemplate = EmailTemplate::getById($objectId, $database);
382
				$name = htmlentities($emailTemplate->getName(), ENT_COMPAT, 'UTF-8');
383
384
				return <<<HTML
385
<a href="{$baseurl}/internal.php/emailManagement/view?id={$objectId}">Email Template #{$objectId} ({$name})</a>
386
HTML;
387
			case 'SiteNotice':
388
				return "<a href=\"{$baseurl}/internal.php/siteNotice\">the site notice</a>";
389 View Code Duplication
			case 'Request':
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...
390
				/** @var Request $request */
391
				$request = Request::getById($objectId, $database);
392
				$name = htmlentities($request->getName(), ENT_COMPAT, 'UTF-8');
393
394
				return <<<HTML
395
<a href="{$baseurl}/internal.php/viewRequest?id={$objectId}">Request #{$objectId} ({$name})</a>
396
HTML;
397 View Code Duplication
			case 'User':
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...
398
				/** @var User $user */
399
				$user = User::getById($objectId, $database);
400
				$username = htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8');
401
402
				return "<a href=\"{$baseurl}/internal.php/statistics/users/detail?user={$objectId}\">{$username}</a>";
403 View Code Duplication
			case 'WelcomeTemplate':
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...
404
				/** @var WelcomeTemplate $welcomeTemplate */
405
				$welcomeTemplate = WelcomeTemplate::getById($objectId, $database);
406
				$userCode = htmlentities($welcomeTemplate->getUserCode(), ENT_COMPAT, 'UTF-8');
407
408
				return "<a href=\"{$baseurl}/internal.php/welcomeTemplates/view?id={$objectId}\">{$userCode}</a>";
409
			default:
410
				return '[' . $objectType . " " . $objectId . ']';
411
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
412
		}
413
	}
414
415
	/**
416
	 * @param    Log[]          $logs
417
	 * @param     PdoDatabase   $database
418
	 * @param SiteConfiguration $configuration
419
	 *
420
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

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...
421
	 * @throws Exception
422
	 */
423
	public static function prepareLogsForTemplate($logs, PdoDatabase $database, SiteConfiguration $configuration)
424
	{
425
		$userIds = array();
426
427
		/** @var Log $logEntry */
428
		foreach ($logs as $logEntry) {
429
			if (!$logEntry instanceof Log) {
430
				// if this happens, we've done something wrong with passing back the log data.
431
				throw new Exception('Log entry is not an instance of a Log, this should never happen.');
432
			}
433
434
			$user = $logEntry->getUser();
435
			if ($user === -1) {
436
				continue;
437
			}
438
439
			if (!array_search($user, $userIds)) {
440
				$userIds[] = $user;
441
			}
442
		}
443
444
		$users = User::getUsernames($userIds, $database);
445
		$users[-1] = User::getCommunity()->getUsername();
446
447
		$logData = array();
448
449
		/** @var Log $logEntry */
450
		foreach ($logs as $logEntry) {
451
			$objectDescription = self::getObjectDescription($logEntry->getObjectId(), $logEntry->getObjectType(),
452
				$database, $configuration);
453
454
			if($logEntry->getAction() === 'Renamed'){
455
				$renameData = unserialize($logEntry->getComment());
456
				$oldName = htmlentities($renameData['old'], ENT_COMPAT, 'UTF-8');
457
				$newName = htmlentities($renameData['new'], ENT_COMPAT, 'UTF-8');
458
				$comment = 'Renamed \'' . $oldName . '\' to \'' . $newName . '\'.';
459
			}
460
			else{
461
				$comment = $logEntry->getComment();
462
			}
463
464
			$logData[] = array(
465
				'timestamp'         => $logEntry->getTimestamp(),
466
				'userid'            => $logEntry->getUser(),
467
				'username'          => $users[$logEntry->getUser()],
468
				'description'       => self::getLogDescription($logEntry),
469
				'objectdescription' => $objectDescription,
470
				'comment'           => $comment,
471
			);
472
		}
473
474
		return array($users, $logData);
475
	}
476
}