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\EmailTemplate; |
17
|
|
|
use Waca\DataObjects\JobQueue; |
18
|
|
|
use Waca\DataObjects\Log; |
19
|
|
|
use Waca\DataObjects\Request; |
20
|
|
|
use Waca\DataObjects\User; |
21
|
|
|
use Waca\DataObjects\WelcomeTemplate; |
22
|
|
|
use Waca\Helpers\SearchHelpers\LogSearchHelper; |
23
|
|
|
use Waca\Helpers\SearchHelpers\UserSearchHelper; |
24
|
|
|
use Waca\PdoDatabase; |
25
|
|
|
use Waca\Security\SecurityManager; |
26
|
|
|
use Waca\SiteConfiguration; |
27
|
|
|
|
28
|
|
|
class LogHelper |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* Summary of getRequestLogsWithComments |
32
|
|
|
* |
33
|
|
|
* @param int $requestId |
34
|
|
|
* @param PdoDatabase $db |
35
|
|
|
* @param SecurityManager $securityManager |
36
|
|
|
* |
37
|
|
|
* @return \Waca\DataObject[] |
38
|
|
|
*/ |
39
|
|
|
public static function getRequestLogsWithComments($requestId, PdoDatabase $db, SecurityManager $securityManager) |
40
|
|
|
{ |
41
|
|
|
$logs = LogSearchHelper::get($db)->byObjectType('Request')->byObjectId($requestId)->fetch(); |
42
|
|
|
|
43
|
|
|
$currentUser = User::getCurrent($db); |
44
|
|
|
$securityResult = $securityManager->allows('RequestData', 'seeRestrictedComments', $currentUser); |
45
|
|
|
$showAllComments = $securityResult === SecurityManager::ALLOWED; |
46
|
|
|
|
47
|
|
|
$comments = Comment::getForRequest($requestId, $db, $showAllComments, $currentUser->getId()); |
48
|
|
|
|
49
|
|
|
$items = array_merge($logs, $comments); |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @param DataObject $item |
53
|
|
|
* |
54
|
|
|
* @return int |
55
|
|
|
*/ |
56
|
|
|
$sortKey = function(DataObject $item) { |
57
|
|
|
if ($item instanceof Log) { |
58
|
|
|
return $item->getTimestamp()->getTimestamp(); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
if ($item instanceof Comment) { |
62
|
|
|
return $item->getTime()->getTimestamp(); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
return 0; |
66
|
|
|
}; |
67
|
|
|
|
68
|
|
|
do { |
69
|
|
|
$flag = false; |
70
|
|
|
|
71
|
|
|
$loopLimit = (count($items) - 1); |
72
|
|
|
for ($i = 0; $i < $loopLimit; $i++) { |
73
|
|
|
// are these two items out of order? |
74
|
|
|
if ($sortKey($items[$i]) > $sortKey($items[$i + 1])) { |
75
|
|
|
// swap them |
76
|
|
|
$swap = $items[$i]; |
77
|
|
|
$items[$i] = $items[$i + 1]; |
78
|
|
|
$items[$i + 1] = $swap; |
79
|
|
|
|
80
|
|
|
// set a flag to say we've modified the array this time around |
81
|
|
|
$flag = true; |
82
|
|
|
} |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
while ($flag); |
86
|
|
|
|
87
|
|
|
return $items; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Summary of getLogDescription |
92
|
|
|
* |
93
|
|
|
* @param Log $entry |
94
|
|
|
* |
95
|
|
|
* @return string |
96
|
|
|
*/ |
97
|
|
|
public static function getLogDescription(Log $entry) |
98
|
|
|
{ |
99
|
|
|
$text = "Deferred to "; |
100
|
|
|
if (substr($entry->getAction(), 0, strlen($text)) == $text) { |
101
|
|
|
// Deferred to a different queue |
102
|
|
|
// This is exactly what we want to display. |
103
|
|
|
return $entry->getAction(); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
$text = "Closed custom-n"; |
107
|
|
|
if ($entry->getAction() == $text) { |
108
|
|
|
// Custom-closed |
109
|
|
|
return "closed (custom reason - account not created)"; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
$text = "Closed custom-y"; |
113
|
|
|
if ($entry->getAction() == $text) { |
114
|
|
|
// Custom-closed |
115
|
|
|
return "closed (custom reason - account created)"; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$text = "Closed 0"; |
119
|
|
|
if ($entry->getAction() == $text) { |
120
|
|
|
// Dropped the request - short-circuit the lookup |
121
|
|
|
return "dropped request"; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$text = "Closed "; |
125
|
|
|
if (substr($entry->getAction(), 0, strlen($text)) == $text) { |
126
|
|
|
// Closed with a reason - do a lookup here. |
127
|
|
|
$id = substr($entry->getAction(), strlen($text)); |
128
|
|
|
/** @var EmailTemplate $template */ |
129
|
|
|
$template = EmailTemplate::getById((int)$id, $entry->getDatabase()); |
130
|
|
|
|
131
|
|
|
if ($template != false) { |
132
|
|
|
return "closed (" . $template->getName() . ")"; |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
// Fall back to the basic stuff |
137
|
|
|
$lookup = array( |
138
|
|
|
'Reserved' => 'reserved', |
139
|
|
|
'Email Confirmed' => 'email-confirmed', |
140
|
|
|
'Unreserved' => 'unreserved', |
141
|
|
|
'Approved' => 'approved', |
142
|
|
|
'Suspended' => 'suspended', |
143
|
|
|
'RoleChange' => 'changed roles', |
144
|
|
|
'Banned' => 'banned', |
145
|
|
|
'Edited' => 'edited interface message', |
146
|
|
|
'Declined' => 'declined', |
147
|
|
|
'EditComment-c' => 'edited a comment', |
148
|
|
|
'EditComment-r' => 'edited a comment', |
149
|
|
|
'Unbanned' => 'unbanned', |
150
|
|
|
'Promoted' => 'promoted to tool admin', |
151
|
|
|
'BreakReserve' => 'forcibly broke the reservation', |
152
|
|
|
'Prefchange' => 'changed user preferences', |
153
|
|
|
'Renamed' => 'renamed', |
154
|
|
|
'Demoted' => 'demoted from tool admin', |
155
|
|
|
'ReceiveReserved' => 'received the reservation', |
156
|
|
|
'SendReserved' => 'sent the reservation', |
157
|
|
|
'EditedEmail' => 'edited email', |
158
|
|
|
'DeletedTemplate' => 'deleted template', |
159
|
|
|
'EditedTemplate' => 'edited template', |
160
|
|
|
'CreatedEmail' => 'created email', |
161
|
|
|
'CreatedTemplate' => 'created template', |
162
|
|
|
'SentMail' => 'sent an email to the requestor', |
163
|
|
|
'Registered' => 'registered a tool account', |
164
|
|
|
'JobIssue' => 'ran a background job unsuccessfully', |
165
|
|
|
'JobCompleted' => 'completed a background job', |
166
|
|
|
'JobAcknowledged' => 'acknowledged a job failure', |
167
|
|
|
'JobRequeued' => 'requeued a job for re-execution', |
168
|
|
|
'EnqueuedJobQueue' => 'scheduled for creation', |
169
|
|
|
'Hospitalised' => 'sent to the hospital', |
170
|
|
|
); |
171
|
|
|
|
172
|
|
|
if (array_key_exists($entry->getAction(), $lookup)) { |
173
|
|
|
return $lookup[$entry->getAction()]; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
// OK, I don't know what this is. Fall back to something sane. |
177
|
|
|
return "performed an unknown action ({$entry->getAction()})"; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @param PdoDatabase $database |
182
|
|
|
* |
183
|
|
|
* @return array |
184
|
|
|
*/ |
185
|
|
|
public static function getLogActions(PdoDatabase $database) |
186
|
|
|
{ |
187
|
|
|
$lookup = array( |
188
|
|
|
'Reserved' => 'reserved', |
189
|
|
|
'Email Confirmed' => 'email-confirmed', |
190
|
|
|
'Unreserved' => 'unreserved', |
191
|
|
|
'Approved' => 'approved', |
192
|
|
|
'Suspended' => 'suspended', |
193
|
|
|
'RoleChange' => 'changed roles', |
194
|
|
|
'Banned' => 'banned', |
195
|
|
|
'Edited' => 'edited interface message', |
196
|
|
|
'Declined' => 'declined', |
197
|
|
|
'EditComment-c' => 'edited a comment (by comment ID)', |
198
|
|
|
'EditComment-r' => 'edited a comment (by request)', |
199
|
|
|
'Unbanned' => 'unbanned', |
200
|
|
|
'Promoted' => 'promoted to tool admin', |
201
|
|
|
'BreakReserve' => 'forcibly broke the reservation', |
202
|
|
|
'Prefchange' => 'changed user preferences', |
203
|
|
|
'Renamed' => 'renamed', |
204
|
|
|
'Demoted' => 'demoted from tool admin', |
205
|
|
|
'ReceiveReserved' => 'received the reservation', |
206
|
|
|
'SendReserved' => 'sent the reservation', |
207
|
|
|
'EditedEmail' => 'edited email', |
208
|
|
|
'DeletedTemplate' => 'deleted template', |
209
|
|
|
'EditedTemplate' => 'edited template', |
210
|
|
|
'CreatedEmail' => 'created email', |
211
|
|
|
'CreatedTemplate' => 'created template', |
212
|
|
|
'SentMail' => 'sent an email to the requestor', |
213
|
|
|
'Registered' => 'registered a tool account', |
214
|
|
|
'Closed 0' => 'dropped request', |
215
|
|
|
'JobIssue' => 'ran a background job unsuccessfully', |
216
|
|
|
'JobCompleted' => 'completed a background job', |
217
|
|
|
'JobAcknowledged' => 'acknowledged a job failure', |
218
|
|
|
'JobRequeued' => 'requeued a job for re-execution', |
219
|
|
|
'EnqueuedJobQueue' => 'scheduled for creation', |
220
|
|
|
'Hospitalised' => 'sent to the hospital', |
221
|
|
|
); |
222
|
|
|
|
223
|
|
|
$statement = $database->query(<<<SQL |
224
|
|
|
SELECT CONCAT('Closed ', id) AS k, CONCAT('closed (',name,')') AS v |
225
|
|
|
FROM emailtemplate; |
226
|
|
|
SQL |
227
|
|
|
); |
228
|
|
|
foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) { |
229
|
|
|
$lookup[$row['k']] = $row['v']; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $lookup; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
public static function getObjectTypes() |
236
|
|
|
{ |
237
|
|
|
return array( |
238
|
|
|
'Ban' => 'Ban', |
239
|
|
|
'Comment' => 'Comment', |
240
|
|
|
'EmailTemplate' => 'Email template', |
241
|
|
|
'JobQueue' => 'Job queue item', |
242
|
|
|
'Request' => 'Request', |
243
|
|
|
'SiteNotice' => 'Site notice', |
244
|
|
|
'User' => 'User', |
245
|
|
|
'WelcomeTemplate' => 'Welcome template', |
246
|
|
|
); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* This returns a HTML |
251
|
|
|
* |
252
|
|
|
* @param string $objectId |
253
|
|
|
* @param string $objectType |
254
|
|
|
* @param PdoDatabase $database |
255
|
|
|
* @param SiteConfiguration $configuration |
256
|
|
|
* |
257
|
|
|
* @return null|string |
258
|
|
|
* @category Security-Critical |
259
|
|
|
*/ |
260
|
|
|
private static function getObjectDescription( |
261
|
|
|
$objectId, |
262
|
|
|
$objectType, |
263
|
|
|
PdoDatabase $database, |
264
|
|
|
SiteConfiguration $configuration |
265
|
|
|
) { |
266
|
|
|
if ($objectType == '') { |
267
|
|
|
return null; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
$baseurl = $configuration->getBaseUrl(); |
271
|
|
|
|
272
|
|
|
switch ($objectType) { |
273
|
|
|
case 'Ban': |
274
|
|
|
/** @var Ban $ban */ |
275
|
|
|
$ban = Ban::getById($objectId, $database); |
276
|
|
|
|
277
|
|
|
if ($ban === false) { |
278
|
|
|
return 'Ban #' . $objectId . "</a>"; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
return 'Ban #' . $objectId . " (" . htmlentities($ban->getTarget()) . ")</a>"; |
282
|
|
View Code Duplication |
case 'EmailTemplate': |
|
|
|
|
283
|
|
|
/** @var EmailTemplate $emailTemplate */ |
284
|
|
|
$emailTemplate = EmailTemplate::getById($objectId, $database); |
285
|
|
|
$name = htmlentities($emailTemplate->getName(), ENT_COMPAT, 'UTF-8'); |
286
|
|
|
|
287
|
|
|
return <<<HTML |
288
|
|
|
<a href="{$baseurl}/internal.php/emailManagement/view?id={$objectId}">Email Template #{$objectId} ({$name})</a> |
289
|
|
|
HTML; |
290
|
|
|
case 'SiteNotice': |
291
|
|
|
return "<a href=\"{$baseurl}/internal.php/siteNotice\">the site notice</a>"; |
292
|
|
View Code Duplication |
case 'Request': |
|
|
|
|
293
|
|
|
/** @var Request $request */ |
294
|
|
|
$request = Request::getById($objectId, $database); |
295
|
|
|
$name = htmlentities($request->getName(), ENT_COMPAT, 'UTF-8'); |
296
|
|
|
|
297
|
|
|
return <<<HTML |
298
|
|
|
<a href="{$baseurl}/internal.php/viewRequest?id={$objectId}">Request #{$objectId} ({$name})</a> |
299
|
|
|
HTML; |
300
|
|
|
case 'User': |
301
|
|
|
/** @var User $user */ |
302
|
|
|
$user = User::getById($objectId, $database); |
303
|
|
|
$username = htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'); |
304
|
|
|
|
305
|
|
|
return "<a href=\"{$baseurl}/internal.php/statistics/users/detail?user={$objectId}\">{$username}</a>"; |
306
|
|
|
case 'WelcomeTemplate': |
307
|
|
|
/** @var WelcomeTemplate $welcomeTemplate */ |
308
|
|
|
$welcomeTemplate = WelcomeTemplate::getById($objectId, $database); |
309
|
|
|
|
310
|
|
|
// some old templates have been completely deleted and lost to the depths of time. |
311
|
|
|
if ($welcomeTemplate === false) { |
312
|
|
|
return "Welcome template #{$objectId}"; |
313
|
|
|
} |
314
|
|
|
else { |
315
|
|
|
$userCode = htmlentities($welcomeTemplate->getUserCode(), ENT_COMPAT, 'UTF-8'); |
316
|
|
|
|
317
|
|
|
return "<a href=\"{$baseurl}/internal.php/welcomeTemplates/view?template={$objectId}\">{$userCode}</a>"; |
318
|
|
|
} |
319
|
|
|
case 'JobQueue': |
320
|
|
|
/** @var JobQueue $job */ |
321
|
|
|
$job = JobQueue::getById($objectId, $database); |
322
|
|
|
|
323
|
|
|
$taskDescriptions = JobQueue::getTaskDescriptions(); |
324
|
|
|
|
325
|
|
|
$task = $job->getTask(); |
326
|
|
|
if(isset($taskDescriptions[$task])){ |
327
|
|
|
$description = $taskDescriptions[$task]; |
328
|
|
|
} else { |
329
|
|
|
$description = 'Unknown task'; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return "<a href=\"{$baseurl}/internal.php/jobQueue/view?id={$objectId}\">Job #{$job->getId()} ({$description})</a>"; |
333
|
|
|
default: |
334
|
|
|
return '[' . $objectType . " " . $objectId . ']'; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* @param Log[] $logs |
340
|
|
|
* @param PdoDatabase $database |
341
|
|
|
* @param SiteConfiguration $configuration |
342
|
|
|
* |
343
|
|
|
* @return array |
|
|
|
|
344
|
|
|
* @throws Exception |
345
|
|
|
*/ |
346
|
|
|
public static function prepareLogsForTemplate($logs, PdoDatabase $database, SiteConfiguration $configuration) |
347
|
|
|
{ |
348
|
|
|
$userIds = array(); |
349
|
|
|
|
350
|
|
|
/** @var Log $logEntry */ |
351
|
|
|
foreach ($logs as $logEntry) { |
352
|
|
|
if (!$logEntry instanceof Log) { |
353
|
|
|
// if this happens, we've done something wrong with passing back the log data. |
354
|
|
|
throw new Exception('Log entry is not an instance of a Log, this should never happen.'); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
$user = $logEntry->getUser(); |
358
|
|
|
if ($user === -1) { |
359
|
|
|
continue; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
if (!array_search($user, $userIds)) { |
363
|
|
|
$userIds[] = $user; |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
$users = UserSearchHelper::get($database)->inIds($userIds)->fetchMap('username'); |
368
|
|
|
$users[-1] = User::getCommunity()->getUsername(); |
369
|
|
|
|
370
|
|
|
$logData = array(); |
371
|
|
|
|
372
|
|
|
/** @var Log $logEntry */ |
373
|
|
|
foreach ($logs as $logEntry) { |
374
|
|
|
$objectDescription = self::getObjectDescription($logEntry->getObjectId(), $logEntry->getObjectType(), |
375
|
|
|
$database, $configuration); |
376
|
|
|
|
377
|
|
|
switch ($logEntry->getAction()) { |
378
|
|
View Code Duplication |
case 'Renamed': |
|
|
|
|
379
|
|
|
$renameData = unserialize($logEntry->getComment()); |
380
|
|
|
$oldName = htmlentities($renameData['old'], ENT_COMPAT, 'UTF-8'); |
381
|
|
|
$newName = htmlentities($renameData['new'], ENT_COMPAT, 'UTF-8'); |
382
|
|
|
$comment = 'Renamed \'' . $oldName . '\' to \'' . $newName . '\'.'; |
383
|
|
|
break; |
384
|
|
|
case 'RoleChange': |
385
|
|
|
$roleChangeData = unserialize($logEntry->getComment()); |
386
|
|
|
|
387
|
|
|
$removed = array(); |
388
|
|
|
foreach ($roleChangeData['removed'] as $r) { |
389
|
|
|
$removed[] = htmlentities($r, ENT_COMPAT, 'UTF-8'); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
$added = array(); |
393
|
|
|
foreach ($roleChangeData['added'] as $r) { |
394
|
|
|
$added[] = htmlentities($r, ENT_COMPAT, 'UTF-8'); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
$reason = htmlentities($roleChangeData['reason'], ENT_COMPAT, 'UTF-8'); |
398
|
|
|
|
399
|
|
|
$roleDelta = 'Removed [' . implode(', ', $removed) . '], Added [' . implode(', ', $added) . ']'; |
400
|
|
|
$comment = $roleDelta . ' with comment: ' . $reason; |
401
|
|
|
break; |
402
|
|
View Code Duplication |
case 'JobIssue': |
|
|
|
|
403
|
|
|
$jobIssueData = unserialize($logEntry->getComment()); |
404
|
|
|
$errorMessage = $jobIssueData['error']; |
405
|
|
|
$status = $jobIssueData['status']; |
406
|
|
|
|
407
|
|
|
$comment = 'Job ' . htmlentities($status, ENT_COMPAT, 'UTF-8') . ': '; |
408
|
|
|
$comment .= htmlentities($errorMessage, ENT_COMPAT, 'UTF-8'); |
409
|
|
|
break; |
410
|
|
|
case 'JobIssueRequest': |
411
|
|
|
case 'JobCompletedRequest': |
412
|
|
|
$jobData = unserialize($logEntry->getComment()); |
413
|
|
|
|
414
|
|
|
/** @var JobQueue $job */ |
415
|
|
|
$job = JobQueue::getById($jobData['job'], $database); |
416
|
|
|
$descs = JobQueue::getTaskDescriptions(); |
417
|
|
|
$comment = htmlentities($descs[$job->getTask()], ENT_COMPAT, 'UTF-8'); |
418
|
|
|
break; |
419
|
|
|
|
420
|
|
|
case 'JobCompleted': |
421
|
|
|
break; |
422
|
|
|
default: |
423
|
|
|
$comment = $logEntry->getComment(); |
424
|
|
|
break; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
$logData[] = array( |
428
|
|
|
'timestamp' => $logEntry->getTimestamp(), |
429
|
|
|
'userid' => $logEntry->getUser(), |
430
|
|
|
'username' => $users[$logEntry->getUser()], |
431
|
|
|
'description' => self::getLogDescription($logEntry), |
432
|
|
|
'objectdescription' => $objectDescription, |
433
|
|
|
'comment' => $comment, |
|
|
|
|
434
|
|
|
); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
return array($users, $logData); |
438
|
|
|
} |
439
|
|
|
} |
440
|
|
|
|
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.