Passed
Push — multiproject/requestforms ( 675fe5 )
by Simon
08:58 queued 04:51
created

LogHelper::getRequestLogsWithComments()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 49
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
dl 0
loc 49
ccs 0
cts 31
cp 0
rs 8.9297
c 1
b 0
f 0
cc 6
nc 3
nop 3
crap 42
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\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\Domain;
17
use Waca\DataObjects\EmailTemplate;
18
use Waca\DataObjects\JobQueue;
19
use Waca\DataObjects\Log;
20
use Waca\DataObjects\Request;
21
use Waca\DataObjects\RequestForm;
22
use Waca\DataObjects\RequestQueue;
23
use Waca\DataObjects\User;
24
use Waca\DataObjects\WelcomeTemplate;
25
use Waca\Helpers\SearchHelpers\LogSearchHelper;
26
use Waca\Helpers\SearchHelpers\UserSearchHelper;
27
use Waca\PdoDatabase;
28
use Waca\Security\SecurityManager;
29
use Waca\SiteConfiguration;
30
31
class LogHelper
32
{
33
    /**
34
     * Summary of getRequestLogsWithComments
35
     *
36
     * @param int             $requestId
37
     * @param PdoDatabase     $db
38
     * @param SecurityManager $securityManager
39
     *
40
     * @return DataObject[]
41
     */
42
    public static function getRequestLogsWithComments($requestId, PdoDatabase $db, SecurityManager $securityManager)
43
    {
44
        $logs = LogSearchHelper::get($db)->byObjectType('Request')->byObjectId($requestId)->fetch();
45
46
        $currentUser = User::getCurrent($db);
47
        $showRestrictedComments = $securityManager->allows('RequestData', 'seeRestrictedComments', $currentUser) === SecurityManager::ALLOWED;
48
        $showCheckuserComments = $securityManager->allows('RequestData', 'seeCheckuserComments', $currentUser) === SecurityManager::ALLOWED;
49
50
        $comments = Comment::getForRequest($requestId, $db, $showRestrictedComments, $showCheckuserComments, $currentUser->getId());
51
52
        $items = array_merge($logs, $comments);
53
54
        /**
55
         * @param DataObject $item
56
         *
57
         * @return int
58
         */
59
        $sortKey = function(DataObject $item) {
60
            if ($item instanceof Log) {
61
                return $item->getTimestamp()->getTimestamp();
62
            }
63
64
            if ($item instanceof Comment) {
65
                return $item->getTime()->getTimestamp();
66
            }
67
68
            return 0;
69
        };
70
71
        do {
72
            $flag = false;
73
74
            $loopLimit = (count($items) - 1);
75
            for ($i = 0; $i < $loopLimit; $i++) {
76
                // are these two items out of order?
77
                if ($sortKey($items[$i]) > $sortKey($items[$i + 1])) {
78
                    // swap them
79
                    $swap = $items[$i];
80
                    $items[$i] = $items[$i + 1];
81
                    $items[$i + 1] = $swap;
82
83
                    // set a flag to say we've modified the array this time around
84
                    $flag = true;
85
                }
86
            }
87
        }
88
        while ($flag);
89
90
        return $items;
91
    }
92
93
    /**
94
     * Summary of getLogDescription
95
     *
96
     * @param Log $entry
97
     *
98
     * @return string
99
     */
100
    public static function getLogDescription(Log $entry)
101
    {
102
        $text = "Deferred to ";
103
        if (substr($entry->getAction(), 0, strlen($text)) == $text) {
104
            // Deferred to a different queue
105
            // This is exactly what we want to display.
106
            return $entry->getAction();
107
        }
108
109
        $text = "Closed custom-n";
110
        if ($entry->getAction() == $text) {
111
            // Custom-closed
112
            return "closed (custom reason - account not created)";
113
        }
114
115
        $text = "Closed custom-y";
116
        if ($entry->getAction() == $text) {
117
            // Custom-closed
118
            return "closed (custom reason - account created)";
119
        }
120
121
        $text = "Closed 0";
122
        if ($entry->getAction() == $text) {
123
            // Dropped the request - short-circuit the lookup
124
            return "dropped request";
125
        }
126
127
        $text = "Closed ";
128
        if (substr($entry->getAction(), 0, strlen($text)) == $text) {
129
            // Closed with a reason - do a lookup here.
130
            $id = substr($entry->getAction(), strlen($text));
131
            /** @var EmailTemplate $template */
132
            $template = EmailTemplate::getById((int)$id, $entry->getDatabase());
133
134
            if ($template != false) {
0 ignored issues
show
introduced by
The condition $template != false is always true.
Loading history...
135
                return "closed (" . $template->getName() . ")";
136
            }
137
        }
138
139
        // Fall back to the basic stuff
140
        $lookup = array(
141
            'Reserved'            => 'reserved',
142
            'Email Confirmed'     => 'email-confirmed',
143
            'Unreserved'          => 'unreserved',
144
            'Approved'            => 'approved',
145
            'Suspended'           => 'suspended',
146
            'RoleChange'          => 'changed roles',
147
            'Banned'              => 'banned',
148
            'Edited'              => 'edited interface message',
149
            'Declined'            => 'declined',
150
            'EditComment-c'       => 'edited a comment',
151
            'EditComment-r'       => 'edited a comment',
152
            'FlaggedComment'      => 'flagged a comment',
153
            'UnflaggedComment'    => 'unflagged a comment',
154
            'Unbanned'            => 'unbanned',
155
            'Promoted'            => 'promoted to tool admin',
156
            'BreakReserve'        => 'forcibly broke the reservation',
157
            'Prefchange'          => 'changed user preferences',
158
            'Renamed'             => 'renamed',
159
            'Demoted'             => 'demoted from tool admin',
160
            'ReceiveReserved'     => 'received the reservation',
161
            'SendReserved'        => 'sent the reservation',
162
            'EditedEmail'         => 'edited email',
163
            'DeletedTemplate'     => 'deleted template',
164
            'EditedTemplate'      => 'edited template',
165
            'CreatedEmail'        => 'created email',
166
            'CreatedTemplate'     => 'created template',
167
            'SentMail'            => 'sent an email to the requester',
168
            'Registered'          => 'registered a tool account',
169
            'JobIssue'            => 'ran a background job unsuccessfully',
170
            'JobCompleted'        => 'completed a background job',
171
            'JobAcknowledged'     => 'acknowledged a job failure',
172
            'JobRequeued'         => 'requeued a job for re-execution',
173
            'JobCancelled'        => 'cancelled execution of a job',
174
            'EnqueuedJobQueue'    => 'scheduled for creation',
175
            'Hospitalised'        => 'sent to the hospital',
176
            'QueueCreated'        => 'created a request queue',
177
            'QueueEdited'         => 'edited a request queue',
178
            'DomainCreated'       => 'created a domain',
179
            'DomainEdited'        => 'edited a domain',
180
            'RequestFormCreated'  => 'created a request form',
181
            'RequestFormEdited'   => 'edited a request form',
182
        );
183
184
        if (array_key_exists($entry->getAction(), $lookup)) {
185
            return $lookup[$entry->getAction()];
186
        }
187
188
        // OK, I don't know what this is. Fall back to something sane.
189
        return "performed an unknown action ({$entry->getAction()})";
190
    }
191
192
    /**
193
     * @param PdoDatabase $database
194
     *
195
     * @return array
196
     */
197
    public static function getLogActions(PdoDatabase $database)
198
    {
199
        $lookup = array(
200
            "Requests" => [
201
                'Reserved'            => 'reserved',
202
                'Email Confirmed'     => 'email-confirmed',
203
                'Unreserved'          => 'unreserved',
204
                'EditComment-c'       => 'edited a comment (by comment ID)',
205
                'EditComment-r'       => 'edited a comment (by request)',
206
                'FlaggedComment'      => 'flagged a comment',
207
                'UnflaggedComment'    => 'unflagged a comment',
208
                'BreakReserve'        => 'forcibly broke the reservation',
209
                'ReceiveReserved'     => 'received the reservation',
210
                'SendReserved'        => 'sent the reservation',
211
                'SentMail'            => 'sent an email to the requester',
212
                'Closed 0'            => 'dropped request',
213
                'Closed custom-y'     => 'closed (custom reason - account created)',
214
                'Closed custom-n'     => 'closed (custom reason - account not created)',
215
            ],
216
            'Users' => [
217
                'Approved'            => 'approved',
218
                'Suspended'           => 'suspended',
219
                'RoleChange'          => 'changed roles',
220
                'Declined'            => 'declined',
221
                'Prefchange'          => 'changed user preferences',
222
                'Renamed'             => 'renamed',
223
                'Promoted'            => 'promoted to tool admin',
224
                'Demoted'             => 'demoted from tool admin',
225
                'Registered'          => 'registered a tool account',
226
            ],
227
            "Bans" => [
228
                'Banned'              => 'banned',
229
                'Unbanned'            => 'unbanned',
230
            ],
231
            "Site notice" => [
232
                'Edited'              => 'edited interface message',
233
            ],
234
            "Email close templates" => [
235
                'EditedEmail'         => 'edited email',
236
                'CreatedEmail'        => 'created email',
237
            ],
238
            "Welcome templates" => [
239
                'DeletedTemplate'     => 'deleted template',
240
                'EditedTemplate'      => 'edited template',
241
                'CreatedTemplate'     => 'created template',
242
            ],
243
            "Job queue" => [
244
                'JobIssue'            => 'ran a background job unsuccessfully',
245
                'JobCompleted'        => 'completed a background job',
246
                'JobAcknowledged'     => 'acknowledged a job failure',
247
                'JobRequeued'         => 'requeued a job for re-execution',
248
                'JobCancelled'        => 'cancelled execution of a job',
249
                'EnqueuedJobQueue'    => 'scheduled for creation',
250
                'Hospitalised'        => 'sent to the hospital',
251
            ],
252
            "Request queues" => [
253
                'QueueCreated'        => 'created a request queue',
254
                'QueueEdited'         => 'edited a request queue',
255
            ],
256
            "Domains" => [
257
                'DomainCreated'       => 'created a domain',
258
                'DomainEdited'        => 'edited a domain',
259
            ],
260
            "Request forms" => [
261
                'RequestFormCreated'        => 'created a request form',
262
                'RequestFormEdited'         => 'edited a request form',
263
            ],
264
        );
265
266
        $databaseDrivenLogKeys = $database->query(<<<SQL
267
SELECT CONCAT('Closed ', id) AS k, CONCAT('closed (',name,')') AS v FROM emailtemplate
268
UNION ALL
269
SELECT CONCAT('Deferred to ', logname) AS k, CONCAT('deferred to ', displayname) AS v FROM requestqueue;
270
SQL
271
        );
272
        foreach ($databaseDrivenLogKeys->fetchAll(PDO::FETCH_ASSOC) as $row) {
273
            $lookup["Requests"][$row['k']] = $row['v'];
274
        }
275
276
        return $lookup;
277
    }
278
279
    public static function getObjectTypes()
280
    {
281
        return array(
282
            'Ban'             => 'Ban',
283
            'Comment'         => 'Comment',
284
            'EmailTemplate'   => 'Email template',
285
            'JobQueue'        => 'Job queue item',
286
            'Request'         => 'Request',
287
            'SiteNotice'      => 'Site notice',
288
            'User'            => 'User',
289
            'WelcomeTemplate' => 'Welcome template',
290
            'RequestQueue'    => 'Request queue',
291
            'Domain'          => 'Domain',
292
            'RequestForm'     => 'Request form'
293
        );
294
    }
295
296
    /**
297
     * This returns a HTML
298
     *
299
     * @param string            $objectId
300
     * @param string            $objectType
301
     * @param PdoDatabase       $database
302
     * @param SiteConfiguration $configuration
303
     *
304
     * @return null|string
305
     * @category Security-Critical
306
     */
307
    private static function getObjectDescription(
308
        $objectId,
309
        $objectType,
310
        PdoDatabase $database,
311
        SiteConfiguration $configuration
312
    ) {
313
        if ($objectType == '') {
314
            return null;
315
        }
316
317
        $baseurl = $configuration->getBaseUrl();
318
319
        switch ($objectType) {
320
            case 'Ban':
321
                /** @var Ban $ban */
322
                $ban = Ban::getById($objectId, $database);
0 ignored issues
show
Bug introduced by
$objectId of type string is incompatible with the type integer expected by parameter $id of Waca\DataObject::getById(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
                $ban = Ban::getById(/** @scrutinizer ignore-type */ $objectId, $database);
Loading history...
323
324
                if ($ban === false) {
0 ignored issues
show
introduced by
The condition $ban === false is always false.
Loading history...
325
                    return 'Ban #' . $objectId;
326
                }
327
328
                return <<<HTML
329
<a href="{$baseurl}/internal.php/bans/show?id={$objectId}">Ban #{$objectId}</a>
330
HTML;
331
            case 'EmailTemplate':
332
                /** @var EmailTemplate $emailTemplate */
333
                $emailTemplate = EmailTemplate::getById($objectId, $database);
334
335
                if ($emailTemplate === false) {
0 ignored issues
show
introduced by
The condition $emailTemplate === false is always false.
Loading history...
336
                    return 'Email Template #' . $objectId;
337
                }
338
339
                $name = htmlentities($emailTemplate->getName(), ENT_COMPAT, 'UTF-8');
340
341
                return <<<HTML
342
<a href="{$baseurl}/internal.php/emailManagement/view?id={$objectId}">Email Template #{$objectId} ({$name})</a>
343
HTML;
344
            case 'SiteNotice':
345
                return "<a href=\"{$baseurl}/internal.php/siteNotice\">the site notice</a>";
346
            case 'Request':
347
                /** @var Request $request */
348
                $request = Request::getById($objectId, $database);
349
350
                if ($request === false) {
0 ignored issues
show
introduced by
The condition $request === false is always false.
Loading history...
351
                    return 'Request #' . $objectId;
352
                }
353
354
                $name = htmlentities($request->getName(), ENT_COMPAT, 'UTF-8');
355
356
                return <<<HTML
357
<a href="{$baseurl}/internal.php/viewRequest?id={$objectId}">Request #{$objectId} ({$name})</a>
358
HTML;
359
            case 'User':
360
                /** @var User $user */
361
                $user = User::getById($objectId, $database);
0 ignored issues
show
Bug introduced by
$objectId of type string is incompatible with the type integer|null expected by parameter $id of Waca\DataObjects\User::getById(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

361
                $user = User::getById(/** @scrutinizer ignore-type */ $objectId, $database);
Loading history...
362
363
                // Some users were merged out of existence
364
                if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
365
                    return 'User #' . $objectId;
366
                }
367
368
                $username = htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8');
369
370
                return "<a href=\"{$baseurl}/internal.php/statistics/users/detail?user={$objectId}\">{$username}</a>";
371
            case 'WelcomeTemplate':
372
                /** @var WelcomeTemplate $welcomeTemplate */
373
                $welcomeTemplate = WelcomeTemplate::getById($objectId, $database);
374
375
                // some old templates have been completely deleted and lost to the depths of time.
376
                if ($welcomeTemplate === false) {
0 ignored issues
show
introduced by
The condition $welcomeTemplate === false is always false.
Loading history...
377
                    return "Welcome template #{$objectId}";
378
                }
379
                else {
380
                    $userCode = htmlentities($welcomeTemplate->getUserCode(), ENT_COMPAT, 'UTF-8');
381
382
                    return "<a href=\"{$baseurl}/internal.php/welcomeTemplates/view?template={$objectId}\">{$userCode}</a>";
383
                }
384
            case 'JobQueue':
385
                /** @var JobQueue $job */
386
                $job = JobQueue::getById($objectId, $database);
387
388
                $taskDescriptions = JobQueue::getTaskDescriptions();
389
390
                if ($job === false) {
0 ignored issues
show
introduced by
The condition $job === false is always false.
Loading history...
391
                    return 'Job Queue Task #' . $objectId;
392
                }
393
394
                $task = $job->getTask();
395
                if (isset($taskDescriptions[$task])) {
396
                    $description = $taskDescriptions[$task];
397
                }
398
                else {
399
                    $description = 'Unknown task';
400
                }
401
402
                return "<a href=\"{$baseurl}/internal.php/jobQueue/view?id={$objectId}\">Job #{$job->getId()} ({$description})</a>";
403
            case 'RequestQueue':
404
                /** @var RequestQueue $queue */
405
                $queue = RequestQueue::getById($objectId, $database);
406
407
                if ($queue === false) {
0 ignored issues
show
introduced by
The condition $queue === false is always false.
Loading history...
408
                    return "Request Queue #{$objectId}";
409
                }
410
411
                $queueHeader = htmlentities($queue->getHeader(), ENT_COMPAT, 'UTF-8');
412
413
                return "<a href=\"{$baseurl}/internal.php/queueManagement/edit?queue={$objectId}\">{$queueHeader}</a>";
414
            case 'Domain':
415
                /** @var Domain $domain */
416
                $domain = Domain::getById($objectId, $database);
417
418
                if ($domain === false) {
0 ignored issues
show
introduced by
The condition $domain === false is always false.
Loading history...
419
                    return "Domain #{$objectId}";
420
                }
421
422
                $domainName = htmlentities($domain->getShortName(), ENT_COMPAT, 'UTF-8');
423
                return "<a href=\"{$baseurl}/internal.php/domainManagement/edit?domain={$objectId}\">{$domainName}</a>";
424
            case 'RequestForm':
425
                /** @var RequestForm $queue */
426
                $queue = RequestForm::getById($objectId, $database);
427
428
                if ($queue === false) {
0 ignored issues
show
introduced by
The condition $queue === false is always false.
Loading history...
429
                    return "Request Form #{$objectId}";
430
                }
431
432
                $formName = htmlentities($queue->getName(), ENT_COMPAT, 'UTF-8');
433
434
                return "<a href=\"{$baseurl}/internal.php/requestFormManagement/edit?form={$objectId}\">{$formName}</a>";
435
436
            default:
437
                return '[' . $objectType . " " . $objectId . ']';
438
        }
439
    }
440
441
    /**
442
     * @param Log[]             $logs
443
     * @param PdoDatabase       $database
444
     * @param SiteConfiguration $configuration
445
     *
446
     * @return array
447
     * @throws Exception
448
     */
449
    public static function prepareLogsForTemplate($logs, PdoDatabase $database, SiteConfiguration $configuration)
450
    {
451
        $userIds = array();
452
453
        foreach ($logs as $logEntry) {
454
            if (!$logEntry instanceof Log) {
455
                // if this happens, we've done something wrong with passing back the log data.
456
                throw new Exception('Log entry is not an instance of a Log, this should never happen.');
457
            }
458
459
            $user = $logEntry->getUser();
460
            if ($user === -1) {
461
                continue;
462
            }
463
464
            if (!array_search($user, $userIds)) {
465
                $userIds[] = $user;
466
            }
467
        }
468
469
        $users = UserSearchHelper::get($database)->inIds($userIds)->fetchMap('username');
470
        $users[-1] = User::getCommunity()->getUsername();
471
472
        $logData = array();
473
474
        foreach ($logs as $logEntry) {
475
            $objectDescription = self::getObjectDescription($logEntry->getObjectId(), $logEntry->getObjectType(),
476
                $database, $configuration);
477
478
            // initialise to sane default
479
            $comment = null;
480
481
            switch ($logEntry->getAction()) {
482
                case 'Renamed':
483
                    $renameData = unserialize($logEntry->getComment());
0 ignored issues
show
Bug introduced by
It seems like $logEntry->getComment() can also be of type null; however, parameter $data of unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

483
                    $renameData = unserialize(/** @scrutinizer ignore-type */ $logEntry->getComment());
Loading history...
484
                    $oldName = htmlentities($renameData['old'], ENT_COMPAT, 'UTF-8');
485
                    $newName = htmlentities($renameData['new'], ENT_COMPAT, 'UTF-8');
486
                    $comment = 'Renamed \'' . $oldName . '\' to \'' . $newName . '\'.';
487
                    break;
488
                case 'RoleChange':
489
                    $roleChangeData = unserialize($logEntry->getComment());
490
491
                    $removed = array();
492
                    foreach ($roleChangeData['removed'] as $r) {
493
                        $removed[] = htmlentities($r, ENT_COMPAT, 'UTF-8');
494
                    }
495
496
                    $added = array();
497
                    foreach ($roleChangeData['added'] as $r) {
498
                        $added[] = htmlentities($r, ENT_COMPAT, 'UTF-8');
499
                    }
500
501
                    $reason = htmlentities($roleChangeData['reason'], ENT_COMPAT, 'UTF-8');
502
503
                    $roleDelta = 'Removed [' . implode(', ', $removed) . '], Added [' . implode(', ', $added) . ']';
504
                    $comment = $roleDelta . ' with comment: ' . $reason;
505
                    break;
506
                case 'JobIssue':
507
                    $jobIssueData = unserialize($logEntry->getComment());
508
                    $errorMessage = $jobIssueData['error'];
509
                    $status = $jobIssueData['status'];
510
511
                    $comment = 'Job ' . htmlentities($status, ENT_COMPAT, 'UTF-8') . ': ';
512
                    $comment .= htmlentities($errorMessage, ENT_COMPAT, 'UTF-8');
513
                    break;
514
                case 'JobIssueRequest':
515
                case 'JobCompletedRequest':
516
                    $jobData = unserialize($logEntry->getComment());
517
518
                    /** @var JobQueue $job */
519
                    $job = JobQueue::getById($jobData['job'], $database);
520
                    $descs = JobQueue::getTaskDescriptions();
521
                    $comment = htmlentities($descs[$job->getTask()], ENT_COMPAT, 'UTF-8');
522
                    break;
523
524
                case 'JobCompleted':
525
                    break;
526
                default:
527
                    $comment = $logEntry->getComment();
528
                    break;
529
            }
530
531
            $logData[] = array(
532
                'timestamp'         => $logEntry->getTimestamp(),
533
                'userid'            => $logEntry->getUser(),
534
                'username'          => $users[$logEntry->getUser()],
535
                'description'       => self::getLogDescription($logEntry),
536
                'objectdescription' => $objectDescription,
537
                'comment'           => $comment,
538
            );
539
        }
540
541
        return array($users, $logData);
542
    }
543
}
544