1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file contains only the EditCounter class. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace Xtools; |
7
|
|
|
|
8
|
|
|
use \DateTime; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* An EditCounter provides statistics about a user's edits on a project. |
12
|
|
|
*/ |
13
|
|
|
class EditCounter extends Model |
14
|
|
|
{ |
15
|
|
|
|
16
|
|
|
/** @var Project The project. */ |
17
|
|
|
protected $project; |
18
|
|
|
|
19
|
|
|
/** @var User The user. */ |
20
|
|
|
protected $user; |
21
|
|
|
|
22
|
|
|
/** @var int[] Revision and page counts etc. */ |
23
|
|
|
protected $pairData; |
24
|
|
|
|
25
|
|
|
/** @var string[] The start and end dates of revisions. */ |
26
|
|
|
protected $revisionDates; |
27
|
|
|
|
28
|
|
|
/** @var int[] The total page counts. */ |
29
|
|
|
protected $pageCounts; |
30
|
|
|
|
31
|
|
|
/** @var int[] The lot totals. */ |
32
|
|
|
protected $logCounts; |
33
|
|
|
|
34
|
|
|
/** @var int[] Keys are project DB names. */ |
35
|
|
|
protected $globalEditCounts; |
36
|
|
|
|
37
|
|
|
/** @var array Block data, with keys 'set' and 'received'. */ |
38
|
|
|
protected $blocks; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Revision size data, with keys 'average_size', 'large_edits' and 'small_edits'. |
42
|
|
|
* @var string[] As returned by the DB, unconverted to int or float |
43
|
|
|
*/ |
44
|
|
|
protected $editSizeData; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Duration of the longest block in days; -1 if indefinite, |
48
|
|
|
* or false if could not be parsed from log params |
49
|
|
|
* @var int|bool |
50
|
|
|
*/ |
51
|
|
|
protected $longestBlockDays; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* EditCounter constructor. |
55
|
|
|
* @param Project $project The base project to count edits |
56
|
|
|
* @param User $user |
57
|
|
|
*/ |
58
|
|
|
public function __construct(Project $project, User $user) |
59
|
|
|
{ |
60
|
|
|
$this->project = $project; |
61
|
|
|
$this->user = $user; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Get revision and page counts etc. |
66
|
|
|
* @return int[] |
67
|
|
|
*/ |
68
|
|
|
protected function getPairData() |
69
|
|
|
{ |
70
|
|
|
if (! is_array($this->pairData)) { |
71
|
|
|
$this->pairData = $this->getRepository() |
|
|
|
|
72
|
|
|
->getPairData($this->project, $this->user); |
73
|
|
|
} |
74
|
|
|
return $this->pairData; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Get revision dates. |
79
|
|
|
* @return int[] |
80
|
|
|
*/ |
81
|
|
|
protected function getLogCounts() |
82
|
|
|
{ |
83
|
|
|
if (! is_array($this->logCounts)) { |
84
|
|
|
$this->logCounts = $this->getRepository() |
|
|
|
|
85
|
|
|
->getLogCounts($this->project, $this->user); |
86
|
|
|
} |
87
|
|
|
return $this->logCounts; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Get block data. |
92
|
|
|
* @param string $type Either 'set' or 'received'. |
93
|
|
|
* @return array |
94
|
|
|
*/ |
95
|
|
|
protected function getBlocks($type) |
96
|
|
|
{ |
97
|
|
|
if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) { |
98
|
|
|
return $this->blocks[$type]; |
99
|
|
|
} |
100
|
|
|
$method = "getBlocks".ucfirst($type); |
101
|
|
|
$blocks = $this->getRepository()->$method($this->project, $this->user); |
102
|
|
|
$this->blocks[$type] = $blocks; |
103
|
|
|
return $this->blocks[$type]; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Get the total number of currently-live revisions. |
108
|
|
|
* @return int |
109
|
|
|
*/ |
110
|
|
|
public function countLiveRevisions() |
111
|
|
|
{ |
112
|
|
|
$revCounts = $this->getPairData(); |
113
|
|
|
return isset($revCounts['live']) ? $revCounts['live'] : 0; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Get the total number of the user's revisions that have been deleted. |
118
|
|
|
* @return int |
119
|
|
|
*/ |
120
|
|
|
public function countDeletedRevisions() |
121
|
|
|
{ |
122
|
|
|
$revCounts = $this->getPairData(); |
123
|
|
|
return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Get the total edit count (live + deleted). |
128
|
|
|
* @return int |
129
|
|
|
*/ |
130
|
|
|
public function countAllRevisions() |
131
|
|
|
{ |
132
|
|
|
return $this->countLiveRevisions() + $this->countDeletedRevisions(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Get the total number of revisions with comments. |
137
|
|
|
* @return int |
138
|
|
|
*/ |
139
|
|
|
public function countRevisionsWithComments() |
140
|
|
|
{ |
141
|
|
|
$revCounts = $this->getPairData(); |
142
|
|
|
return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Get the total number of revisions without comments. |
147
|
|
|
* @return int |
148
|
|
|
*/ |
149
|
|
|
public function countRevisionsWithoutComments() |
150
|
|
|
{ |
151
|
|
|
return $this->countAllRevisions() - $this->countRevisionsWithComments(); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Get the total number of revisions marked as 'minor' by the user. |
156
|
|
|
* @return int |
157
|
|
|
*/ |
158
|
|
|
public function countMinorRevisions() |
159
|
|
|
{ |
160
|
|
|
$revCounts = $this->getPairData(); |
161
|
|
|
return isset($revCounts['minor']) ? $revCounts['minor'] : 0; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Get the total number of non-deleted pages edited by the user. |
166
|
|
|
* @return int |
167
|
|
|
*/ |
168
|
|
|
public function countLivePagesEdited() |
169
|
|
|
{ |
170
|
|
|
$pageCounts = $this->getPairData(); |
171
|
|
|
return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Get the total number of deleted pages ever edited by the user. |
176
|
|
|
* @return int |
177
|
|
|
*/ |
178
|
|
|
public function countDeletedPagesEdited() |
179
|
|
|
{ |
180
|
|
|
$pageCounts = $this->getPairData(); |
181
|
|
|
return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Get the total number of pages ever edited by this user (both live and deleted). |
186
|
|
|
* @return int |
187
|
|
|
*/ |
188
|
|
|
public function countAllPagesEdited() |
189
|
|
|
{ |
190
|
|
|
return $this->countLivePagesEdited() + $this->countDeletedPagesEdited(); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Get the total number of pages (both still live and those that have been deleted) created |
195
|
|
|
* by the user. |
196
|
|
|
* @return int |
197
|
|
|
*/ |
198
|
|
|
public function countPagesCreated() |
199
|
|
|
{ |
200
|
|
|
return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted(); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Get the total number of pages created by the user, that have not been deleted. |
205
|
|
|
* @return int |
206
|
|
|
*/ |
207
|
|
|
public function countCreatedPagesLive() |
208
|
|
|
{ |
209
|
|
|
$pageCounts = $this->getPairData(); |
210
|
|
|
return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* Get the total number of pages created by the user, that have since been deleted. |
215
|
|
|
* @return int |
216
|
|
|
*/ |
217
|
|
|
public function countPagesCreatedDeleted() |
218
|
|
|
{ |
219
|
|
|
$pageCounts = $this->getPairData(); |
220
|
|
|
return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Get the total number of pages that have been deleted by the user. |
225
|
|
|
* @return int |
226
|
|
|
*/ |
227
|
|
|
public function countPagesDeleted() |
228
|
|
|
{ |
229
|
|
|
$logCounts = $this->getLogCounts(); |
230
|
|
|
return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Get the total number of pages moved by the user. |
235
|
|
|
* @return int |
236
|
|
|
*/ |
237
|
|
|
public function countPagesMoved() |
238
|
|
|
{ |
239
|
|
|
$logCounts = $this->getLogCounts(); |
240
|
|
|
return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Get the total number of times the user has blocked a user. |
245
|
|
|
* @return int |
246
|
|
|
*/ |
247
|
|
|
public function countBlocksSet() |
248
|
|
|
{ |
249
|
|
|
$logCounts = $this->getLogCounts(); |
250
|
|
|
$reBlock = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0; |
251
|
|
|
return $reBlock; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Get the total number of times the user has re-blocked a user. |
256
|
|
|
* @return int |
257
|
|
|
*/ |
258
|
|
|
public function countReblocksSet() |
259
|
|
|
{ |
260
|
|
|
$logCounts = $this->getLogCounts(); |
261
|
|
|
$reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0; |
262
|
|
|
return $reBlock; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Get the total number of times the user has unblocked a user. |
267
|
|
|
* @return int |
268
|
|
|
*/ |
269
|
|
|
public function countUnblocksSet() |
270
|
|
|
{ |
271
|
|
|
$logCounts = $this->getLogCounts(); |
272
|
|
|
return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Get the total number of blocks that have been lifted (i.e. unblocks) by this user. |
277
|
|
|
* @return int |
278
|
|
|
*/ |
279
|
|
|
public function countBlocksLifted() |
280
|
|
|
{ |
281
|
|
|
$logCounts = $this->getLogCounts(); |
282
|
|
|
return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Get the total number of times the user has been blocked. |
287
|
|
|
* @return int |
288
|
|
|
*/ |
289
|
|
|
public function countBlocksReceived() |
290
|
|
|
{ |
291
|
|
|
$blocks = $this->getBlocks('received'); |
292
|
|
|
return count($blocks); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Get the length of the longest block the user received. |
297
|
|
|
* @return int|bool Number of days or false if it could not be determined. |
298
|
|
|
* If the longest duration is indefinite, -1 is returned. |
299
|
|
|
*/ |
300
|
|
|
public function getLongestBlockDays() |
301
|
|
|
{ |
302
|
|
|
if (isset($this->longestBlockDays)) { |
303
|
|
|
return $this->longestBlockDays; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
$blocks = $this->getBlocks('received'); // FIXME: make sure this is only called once |
307
|
|
|
$this->longestBlockDays = false; |
308
|
|
|
|
309
|
|
|
foreach ($blocks as $block) { |
310
|
|
|
$timestamp = strtotime($block['log_timestamp']); |
311
|
|
|
|
312
|
|
|
// First check if the string is serialized, and if so parse it to get the block duration |
313
|
|
|
if (@unserialize($block['log_params']) !== false) { |
314
|
|
|
$parsedParams = unserialize($block['log_params']); |
315
|
|
|
$durationStr = $parsedParams['5::duration']; |
316
|
|
|
} else { |
317
|
|
|
// Old format, the duration in English + block options separated by new lines |
318
|
|
|
$durationStr = explode("\n", $block['log_params'])[0]; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
if (in_array($durationStr, ['indefinite', 'infinity', 'infinite'])) { |
322
|
|
|
return -1; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
// Try block just in case there are older, unpredictable formats |
326
|
|
|
try { |
327
|
|
|
$expiry = strtotime($durationStr, $timestamp); |
328
|
|
|
$duration = ($expiry - $timestamp) / (60 * 60 * 24); |
329
|
|
|
|
330
|
|
|
if (!$duration || $duration > $this->longestBlockDays) { |
331
|
|
|
$this->longestBlockDays = $duration; |
332
|
|
|
} |
333
|
|
|
} catch (Exception $error) { |
|
|
|
|
334
|
|
|
// do nothing, leaving the longest block at false |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return $this->longestBlockDays; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Get the total number of pages protected by the user. |
343
|
|
|
* @return int |
344
|
|
|
*/ |
345
|
|
|
public function countPagesProtected() |
346
|
|
|
{ |
347
|
|
|
$logCounts = $this->getLogCounts(); |
348
|
|
|
return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Get the total number of pages reprotected by the user. |
353
|
|
|
* @return int |
354
|
|
|
*/ |
355
|
|
|
public function countPagesReprotected() |
356
|
|
|
{ |
357
|
|
|
$logCounts = $this->getLogCounts(); |
358
|
|
|
return isset($logCounts['protect-modify']) ? (int)$logCounts['protect-modify'] : 0; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Get the total number of pages unprotected by the user. |
363
|
|
|
* @return int |
364
|
|
|
*/ |
365
|
|
|
public function countPagesUnprotected() |
366
|
|
|
{ |
367
|
|
|
$logCounts = $this->getLogCounts(); |
368
|
|
|
return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Get the total number of edits deleted by the user. |
373
|
|
|
* @return int |
374
|
|
|
*/ |
375
|
|
|
public function countEditsDeleted() |
376
|
|
|
{ |
377
|
|
|
$logCounts = $this->getLogCounts(); |
378
|
|
|
return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* Get the total number of pages restored by the user. |
383
|
|
|
* @return int |
384
|
|
|
*/ |
385
|
|
|
public function countPagesRestored() |
386
|
|
|
{ |
387
|
|
|
$logCounts = $this->getLogCounts(); |
388
|
|
|
return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Get the total number of times the user has modified the rights of a user. |
393
|
|
|
* @return int |
394
|
|
|
*/ |
395
|
|
|
public function countRightsModified() |
396
|
|
|
{ |
397
|
|
|
$logCounts = $this->getLogCounts(); |
398
|
|
|
return isset($logCounts['rights-rights']) ? (int)$logCounts['rights-rights'] : 0; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Get the total number of pages imported by the user (through any import mechanism: |
403
|
|
|
* interwiki, or XML upload). |
404
|
|
|
* @return int |
405
|
|
|
*/ |
406
|
|
|
public function countPagesImported() |
407
|
|
|
{ |
408
|
|
|
$logCounts = $this->getLogCounts(); |
409
|
|
|
$import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0; |
410
|
|
|
$interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0; |
411
|
|
|
$upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0; |
412
|
|
|
return $import + $interwiki + $upload; |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Get the average number of edits per page (including deleted revisions and pages). |
417
|
|
|
* @return float |
418
|
|
|
*/ |
419
|
|
|
public function averageRevisionsPerPage() |
420
|
|
|
{ |
421
|
|
|
if ($this->countAllPagesEdited() == 0) { |
422
|
|
|
return 0; |
423
|
|
|
} |
424
|
|
|
return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3); |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
/** |
428
|
|
|
* Average number of edits made per day. |
429
|
|
|
* @return float |
430
|
|
|
*/ |
431
|
|
|
public function averageRevisionsPerDay() |
432
|
|
|
{ |
433
|
|
|
if ($this->getDays() == 0) { |
434
|
|
|
return 0; |
435
|
|
|
} |
436
|
|
|
return round($this->countAllRevisions() / $this->getDays(), 3); |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
/** |
440
|
|
|
* Get the total number of edits made by the user with semi-automating tools. |
441
|
|
|
*/ |
442
|
|
|
public function countAutomatedRevisions() |
443
|
|
|
{ |
444
|
|
|
$autoSummary = $this->automatedRevisionsSummary(); |
445
|
|
|
$count = 0; |
446
|
|
|
foreach ($autoSummary as $summary) { |
447
|
|
|
$count += $summary; |
448
|
|
|
} |
449
|
|
|
return $count; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Get a summary of the numbers of edits made by the user with semi-automating tools. |
454
|
|
|
*/ |
455
|
|
|
public function automatedRevisionsSummary() |
456
|
|
|
{ |
457
|
|
|
return $this->getRepository()->countAutomatedRevisions($this->project, $this->user); |
|
|
|
|
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Get the count of (non-deleted) edits made in the given timeframe to now. |
462
|
|
|
* @param string $time One of 'day', 'week', 'month', or 'year'. |
463
|
|
|
* @return int The total number of live edits. |
464
|
|
|
*/ |
465
|
|
|
public function countRevisionsInLast($time) |
466
|
|
|
{ |
467
|
|
|
$revCounts = $this->getPairData(); |
468
|
|
|
return isset($revCounts[$time]) ? $revCounts[$time] : 0; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Get the date and time of the user's first edit. |
473
|
|
|
* @return DateTime|bool The time of the first revision, or false. |
474
|
|
|
*/ |
475
|
|
|
public function datetimeFirstRevision() |
476
|
|
|
{ |
477
|
|
|
$revDates = $this->getPairData(); |
478
|
|
|
return isset($revDates['first']) ? new DateTime($revDates['first']) : false; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Get the date and time of the user's first edit. |
483
|
|
|
* @return DateTime|bool The time of the last revision, or false. |
484
|
|
|
*/ |
485
|
|
|
public function datetimeLastRevision() |
486
|
|
|
{ |
487
|
|
|
$revDates = $this->getPairData(); |
488
|
|
|
return isset($revDates['last']) ? new DateTime($revDates['last']) : false; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Get the number of days between the first and last edits. |
493
|
|
|
* If there's only one edit, this is counted as one day. |
494
|
|
|
* @return int |
495
|
|
|
*/ |
496
|
|
|
public function getDays() |
497
|
|
|
{ |
498
|
|
|
$first = $this->datetimeFirstRevision(); |
499
|
|
|
$last = $this->datetimeLastRevision(); |
500
|
|
|
if ($first === false || $last === false) { |
501
|
|
|
return 0; |
502
|
|
|
} |
503
|
|
|
$days = $last->diff($first)->days; |
504
|
|
|
return $days > 0 ? $days : 1; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Get the total number of files uploaded (including those now deleted). |
509
|
|
|
* @return int |
510
|
|
|
*/ |
511
|
|
|
public function countFilesUploaded() |
512
|
|
|
{ |
513
|
|
|
$logCounts = $this->getLogCounts(); |
514
|
|
|
return $logCounts['upload-upload'] ?: 0; |
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* Get the total number of files uploaded to Commons (including those now deleted). |
519
|
|
|
* This is only applicable for WMF labs installations. |
520
|
|
|
* @return int |
521
|
|
|
*/ |
522
|
|
|
public function countFilesUploadedCommons() |
523
|
|
|
{ |
524
|
|
|
$logCounts = $this->getLogCounts(); |
525
|
|
|
return $logCounts['files_uploaded_commons'] ?: 0; |
526
|
|
|
} |
527
|
|
|
|
528
|
|
|
/** |
529
|
|
|
* Get the total number of revisions the user has sent thanks for. |
530
|
|
|
* @return int |
531
|
|
|
*/ |
532
|
|
|
public function thanks() |
533
|
|
|
{ |
534
|
|
|
$logCounts = $this->getLogCounts(); |
535
|
|
|
return $logCounts['thanks-thank'] ?: 0; |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* Get the total number of approvals |
540
|
|
|
* @return int |
541
|
|
|
*/ |
542
|
|
|
public function approvals() |
543
|
|
|
{ |
544
|
|
|
$logCounts = $this->getLogCounts(); |
545
|
|
|
$total = $logCounts['review-approve'] + |
546
|
|
|
(!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) + |
547
|
|
|
(!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) + |
548
|
|
|
(!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0); |
549
|
|
|
return $total; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
/** |
553
|
|
|
* Get the total number of patrols performed by the user. |
554
|
|
|
* @return int |
555
|
|
|
*/ |
556
|
|
|
public function patrols() |
557
|
|
|
{ |
558
|
|
|
$logCounts = $this->getLogCounts(); |
559
|
|
|
return $logCounts['patrol-patrol'] ?: 0; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Get the total number of accounts created by the user. |
564
|
|
|
* @return int |
565
|
|
|
*/ |
566
|
|
|
public function accountsCreated() |
567
|
|
|
{ |
568
|
|
|
$logCounts = $this->getLogCounts(); |
569
|
|
|
$create2 = $logCounts['newusers-create2'] ?: 0; |
570
|
|
|
$byemail = $logCounts['newusers-byemail'] ?: 0; |
571
|
|
|
return $create2 + $byemail; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* Get the given user's total edit counts per namespace. |
576
|
|
|
* @return integer[] Array keys are namespace IDs, values are the edit counts. |
577
|
|
|
*/ |
578
|
|
|
public function namespaceTotals() |
579
|
|
|
{ |
580
|
|
|
$counts = $this->getRepository()->getNamespaceTotals($this->project, $this->user); |
|
|
|
|
581
|
|
|
arsort($counts); |
582
|
|
|
return $counts; |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
/** |
586
|
|
|
* Get a summary of the times of day and the days of the week that the user has edited. |
587
|
|
|
* @return string[] |
588
|
|
|
*/ |
589
|
|
|
public function timeCard() |
590
|
|
|
{ |
591
|
|
|
return $this->getRepository()->getTimeCard($this->project, $this->user); |
|
|
|
|
592
|
|
|
} |
593
|
|
|
|
594
|
|
|
/** |
595
|
|
|
* Get the total numbers of edits per year. |
596
|
|
|
* @return int[] |
597
|
|
|
*/ |
598
|
|
|
public function yearCounts() |
599
|
|
|
{ |
600
|
|
|
$totals = $this->getRepository()->getYearCounts($this->project, $this->user); |
|
|
|
|
601
|
|
|
$out = [ |
602
|
|
|
'years' => [], |
603
|
|
|
'namespaces' => [], |
604
|
|
|
'totals' => [], |
605
|
|
|
]; |
606
|
|
|
foreach ($totals as $total) { |
607
|
|
|
$out['years'][$total['year']] = $total['year']; |
608
|
|
|
$out['namespaces'][$total['page_namespace']] = $total['page_namespace']; |
609
|
|
|
if (!isset($out['totals'][$total['page_namespace']])) { |
610
|
|
|
$out['totals'][$total['page_namespace']] = []; |
611
|
|
|
} |
612
|
|
|
$out['totals'][$total['page_namespace']][$total['year']] = $total['count']; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
// Make sure data is sorted by namespace |
616
|
|
|
ksort($out['namespaces']); |
617
|
|
|
ksort($out['totals']); |
618
|
|
|
|
619
|
|
|
return $out; |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* Get the total numbers of edits per month. |
624
|
|
|
* @return mixed[] With keys 'years', 'namespaces' and 'totals'. |
625
|
|
|
*/ |
626
|
|
|
public function monthCounts() |
627
|
|
|
{ |
628
|
|
|
$totals = $this->getRepository()->getMonthCounts($this->project, $this->user); |
|
|
|
|
629
|
|
|
$out = [ |
630
|
|
|
'years' => [], |
631
|
|
|
'totals' => [], |
632
|
|
|
]; |
633
|
|
|
$out['max_year'] = 0; |
634
|
|
|
$out['min_year'] = date('Y'); |
635
|
|
|
foreach ($totals as $total) { |
636
|
|
|
// Collect all applicable years and namespaces. |
637
|
|
|
$out['max_year'] = max($out['max_year'], $total['year']); |
638
|
|
|
$out['min_year'] = min($out['min_year'], $total['year']); |
639
|
|
|
// Collate the counts by namespace, and then year and month. |
640
|
|
|
$ns = $total['page_namespace']; |
641
|
|
|
if (!isset($out['totals'][$ns])) { |
642
|
|
|
$out['totals'][$ns] = []; |
643
|
|
|
} |
644
|
|
|
$out['totals'][$ns][$total['year'] . $total['month']] = $total['count']; |
645
|
|
|
} |
646
|
|
|
// Fill in the blanks (where no edits were made in a given month for a namespace). |
647
|
|
|
for ($y = $out['min_year']; $y <= $out['max_year']; $y++) { |
648
|
|
|
for ($m = 1; $m <= 12; $m++) { |
649
|
|
|
foreach ($out['totals'] as $nsId => &$total) { |
650
|
|
|
if (!isset($total[$y . $m])) { |
651
|
|
|
$total[$y . $m] = 0; |
652
|
|
|
} |
653
|
|
|
} |
654
|
|
|
} |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
// Sort by namespace |
658
|
|
|
ksort($out['totals']); |
659
|
|
|
|
660
|
|
|
return $out; |
661
|
|
|
} |
662
|
|
|
|
663
|
|
|
/** |
664
|
|
|
* Get the total edit counts for the top n projects of this user. |
665
|
|
|
* @param int $numProjects |
666
|
|
|
* @return mixed[] Each element has 'total' and 'project' keys. |
667
|
|
|
*/ |
668
|
|
|
public function globalEditCountsTopN($numProjects = 10) |
669
|
|
|
{ |
670
|
|
|
// Get counts. |
671
|
|
|
$editCounts = $this->globalEditCounts(true); |
672
|
|
|
// Truncate, and return. |
|
|
|
|
673
|
|
|
return array_slice($editCounts, 0, $numProjects); |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
/** |
677
|
|
|
* Get the total number of edits excluding the top n. |
678
|
|
|
* @param int $numProjects |
679
|
|
|
* @return int |
680
|
|
|
*/ |
681
|
|
|
public function globalEditCountWithoutTopN($numProjects = 10) |
682
|
|
|
{ |
683
|
|
|
$editCounts = $this->globalEditCounts(true); |
684
|
|
|
$bottomM = array_slice($editCounts, $numProjects); |
685
|
|
|
$total = 0; |
686
|
|
|
foreach ($bottomM as $editCount) { |
687
|
|
|
$total += $editCount['total']; |
688
|
|
|
} |
689
|
|
|
return $total; |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
/** |
693
|
|
|
* Get the grand total of all edits on all projects. |
694
|
|
|
* @return int |
695
|
|
|
*/ |
696
|
|
|
public function globalEditCount() |
697
|
|
|
{ |
698
|
|
|
$total = 0; |
699
|
|
|
foreach ($this->globalEditCounts() as $editCount) { |
700
|
|
|
$total += $editCount['total']; |
701
|
|
|
} |
702
|
|
|
return $total; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
/** |
706
|
|
|
* Get the total revision counts for all projects for this user. |
707
|
|
|
* @param bool $sorted Whether to sort the list by total, or not. |
708
|
|
|
* @return mixed[] Each element has 'total' and 'project' keys. |
709
|
|
|
*/ |
710
|
|
|
public function globalEditCounts($sorted = false) |
711
|
|
|
{ |
712
|
|
|
if (!$this->globalEditCounts) { |
|
|
|
|
713
|
|
|
$this->globalEditCounts = $this->getRepository() |
|
|
|
|
714
|
|
|
->globalEditCounts($this->user, $this->project); |
715
|
|
|
if ($sorted) { |
716
|
|
|
// Sort. |
717
|
|
|
uasort($this->globalEditCounts, function ($a, $b) { |
718
|
|
|
return $b['total'] - $a['total']; |
719
|
|
|
}); |
720
|
|
|
} |
721
|
|
|
} |
722
|
|
|
return $this->globalEditCounts; |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
/** |
726
|
|
|
* Get the most recent n revisions across all projects. |
727
|
|
|
* @param int $max The maximum number of revisions to return. |
728
|
|
|
* @return Edit[] |
729
|
|
|
*/ |
730
|
|
|
public function globalEdits($max) |
731
|
|
|
{ |
732
|
|
|
// Only look for revisions newer than this. |
733
|
|
|
$oldest = null; |
|
|
|
|
734
|
|
|
// Collect all projects with any edits. |
735
|
|
|
$projects = []; |
736
|
|
|
foreach ($this->globalEditCounts() as $editCount) { |
737
|
|
|
// Don't query revisions if there aren't any. |
738
|
|
|
if ($editCount['total'] == 0) { |
739
|
|
|
continue; |
740
|
|
|
} |
741
|
|
|
$projects[$editCount['project']->getDatabaseName()] = $editCount['project']; |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
// Get all revisions for those projects. |
745
|
|
|
$globalRevisionsData = $this->getRepository() |
|
|
|
|
746
|
|
|
->getRevisions($projects, $this->user, $max); |
747
|
|
|
$globalEdits = []; |
748
|
|
|
foreach ($globalRevisionsData as $revision) { |
749
|
|
|
/** @var Project $project */ |
750
|
|
|
$project = $projects[$revision['project_name']]; |
751
|
|
|
$nsName = ''; |
752
|
|
|
if ($revision['page_namespace']) { |
753
|
|
|
$nsName = $project->getNamespaces()[$revision['page_namespace']]; |
754
|
|
|
} |
755
|
|
|
$page = $project->getRepository() |
|
|
|
|
756
|
|
|
->getPage($project, $nsName . ':' . $revision['page_title']); |
757
|
|
|
$edit = new Edit($page, $revision); |
758
|
|
|
$globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit; |
759
|
|
|
} |
760
|
|
|
|
761
|
|
|
// Sort and prune, before adding more. |
762
|
|
|
krsort($globalEdits); |
763
|
|
|
$globalEdits = array_slice($globalEdits, 0, $max); |
764
|
|
|
return $globalEdits; |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/** |
768
|
|
|
* Get average edit size, and number of large and small edits. |
769
|
|
|
* @return int[] |
770
|
|
|
*/ |
771
|
|
|
protected function getEditSizeData() |
772
|
|
|
{ |
773
|
|
|
if (! is_array($this->editSizeData)) { |
774
|
|
|
$this->editSizeData = $this->getRepository() |
|
|
|
|
775
|
|
|
->getEditSizeData($this->project, $this->user); |
776
|
|
|
} |
777
|
|
|
return $this->editSizeData; |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
/** |
781
|
|
|
* Get the total edit count of this user or 5,000 if they've made more than 5,000 edits. |
782
|
|
|
* This is used to ensure percentages of small and large edits are computed properly. |
783
|
|
|
* @return int |
784
|
|
|
*/ |
785
|
|
|
public function countLast5000() |
786
|
|
|
{ |
787
|
|
|
return $this->countLiveRevisions() > 5000 ? 5000 : $this->countLiveRevisions(); |
788
|
|
|
} |
789
|
|
|
|
790
|
|
|
/** |
791
|
|
|
* Get the number of edits under 20 bytes of the user's past 5000 edits. |
792
|
|
|
* @return int |
793
|
|
|
*/ |
794
|
|
|
public function countSmallEdits() |
795
|
|
|
{ |
796
|
|
|
$editSizeData = $this->getEditSizeData(); |
797
|
|
|
return isset($editSizeData['small_edits']) ? (int) $editSizeData['small_edits'] : 0; |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
/** |
801
|
|
|
* Get the total number of edits over 1000 bytes of the user's past 5000 edits. |
802
|
|
|
* @return int |
803
|
|
|
*/ |
804
|
|
|
public function countLargeEdits() |
805
|
|
|
{ |
806
|
|
|
$editSizeData = $this->getEditSizeData(); |
807
|
|
|
return isset($editSizeData['large_edits']) ? (int) $editSizeData['large_edits'] : 0; |
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
/** |
811
|
|
|
* Get the average size of the user's past 5000 edits. |
812
|
|
|
* @return float Size in bytes. |
813
|
|
|
*/ |
814
|
|
|
public function averageEditSize() |
815
|
|
|
{ |
816
|
|
|
$editSizeData = $this->getEditSizeData(); |
817
|
|
|
if (isset($editSizeData['average_size'])) { |
818
|
|
|
return round($editSizeData['average_size'], 3); |
819
|
|
|
} else { |
820
|
|
|
return 0; |
821
|
|
|
} |
822
|
|
|
} |
823
|
|
|
} |
824
|
|
|
|
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: