|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file contains only the EditCounter class. |
|
4
|
|
|
*/ |
|
5
|
|
|
|
|
6
|
|
|
namespace Xtools; |
|
7
|
|
|
|
|
8
|
|
|
use DateTime; |
|
9
|
|
|
use Exception; |
|
10
|
|
|
use DatePeriod; |
|
11
|
|
|
use DateInterval; |
|
12
|
|
|
use GuzzleHttp; |
|
13
|
|
|
use GuzzleHttp\Promise\Promise; |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* An EditCounter provides statistics about a user's edits on a project. |
|
17
|
|
|
*/ |
|
18
|
|
|
class EditCounter extends Model |
|
19
|
|
|
{ |
|
20
|
|
|
|
|
21
|
|
|
/** @var Project The project. */ |
|
22
|
|
|
protected $project; |
|
23
|
|
|
|
|
24
|
|
|
/** @var User The user. */ |
|
25
|
|
|
protected $user; |
|
26
|
|
|
|
|
27
|
|
|
/** @var int[] Revision and page counts etc. */ |
|
28
|
|
|
protected $pairData; |
|
29
|
|
|
|
|
30
|
|
|
/** @var string[] The start and end dates of revisions. */ |
|
31
|
|
|
protected $revisionDates; |
|
32
|
|
|
|
|
33
|
|
|
/** @var int[] The total page counts. */ |
|
34
|
|
|
protected $pageCounts; |
|
35
|
|
|
|
|
36
|
|
|
/** @var int[] The lot totals. */ |
|
37
|
|
|
protected $logCounts; |
|
38
|
|
|
|
|
39
|
|
|
/** @var mixed[] Total numbers of edits per month */ |
|
40
|
|
|
protected $monthCounts; |
|
41
|
|
|
|
|
42
|
|
|
/** @var mixed[] Total numbers of edits per year */ |
|
43
|
|
|
protected $yearCounts; |
|
44
|
|
|
|
|
45
|
|
|
/** @var int[] Keys are project DB names. */ |
|
46
|
|
|
protected $globalEditCounts; |
|
47
|
|
|
|
|
48
|
|
|
/** @var array Block data, with keys 'set' and 'received'. */ |
|
49
|
|
|
protected $blocks; |
|
50
|
|
|
|
|
51
|
|
|
/** @var integer[] Array keys are namespace IDs, values are the edit counts */ |
|
52
|
|
|
protected $namespaceTotals; |
|
53
|
|
|
|
|
54
|
|
|
/** @var int Number of semi-automated edits */ |
|
55
|
|
|
protected $autoEditCount; |
|
56
|
|
|
|
|
57
|
|
|
/** @var string[] Data needed for time card chart */ |
|
58
|
|
|
protected $timeCardData; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* Revision size data, with keys 'average_size', 'large_edits' and 'small_edits'. |
|
62
|
|
|
* @var string[] As returned by the DB, unconverted to int or float |
|
63
|
|
|
*/ |
|
64
|
|
|
protected $editSizeData; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Duration of the longest block in seconds; -1 if indefinite, |
|
68
|
|
|
* or false if could not be parsed from log params |
|
69
|
|
|
* @var int|bool |
|
70
|
|
|
*/ |
|
71
|
|
|
protected $longestBlockSeconds; |
|
72
|
|
|
|
|
73
|
|
|
/** |
|
74
|
|
|
* EditCounter constructor. |
|
75
|
|
|
* @param Project $project The base project to count edits |
|
76
|
|
|
* @param User $user |
|
77
|
|
|
*/ |
|
78
|
|
|
public function __construct(Project $project, User $user) |
|
79
|
|
|
{ |
|
80
|
|
|
$this->project = $project; |
|
81
|
|
|
$this->user = $user; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* This method asynchronously fetches all the expensive data, waits |
|
86
|
|
|
* for each request to finish, and copies the values to the class instance. |
|
87
|
|
|
* @return null |
|
88
|
|
|
*/ |
|
89
|
|
|
public function prepareData() |
|
90
|
|
|
{ |
|
91
|
|
|
$project = $this->project->getDomain(); |
|
92
|
|
|
$username = $this->user->getUsername(); |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* The URL of each endpoint, keyed by the name of the corresponding class-level |
|
96
|
|
|
* instance variable. |
|
97
|
|
|
* @var array[] |
|
98
|
|
|
*/ |
|
99
|
|
|
$endpoints = [ |
|
100
|
|
|
"pairData" => "ec/pairdata/$project/$username", |
|
101
|
|
|
"logCounts" => "ec/logcounts/$project/$username", |
|
102
|
|
|
"namespaceTotals" => "ec/namespacetotals/$project/$username", |
|
103
|
|
|
"editSizeData" => "ec/editsizes/$project/$username", |
|
104
|
|
|
"monthCounts" => "ec/monthcounts/$project/$username", |
|
105
|
|
|
// "globalEditCounts" => "ec-globaleditcounts/$project/$username", |
|
|
|
|
|
|
106
|
|
|
"autoEditCount" => "user/automated_editcount/$project/$username", |
|
107
|
|
|
]; |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* Keep track of all promises so we can wait for all of them to complete. |
|
111
|
|
|
* @var GuzzleHttp\Promise\Promise[] |
|
112
|
|
|
*/ |
|
113
|
|
|
$promises = []; |
|
114
|
|
|
|
|
115
|
|
|
foreach ($endpoints as $key => $endpoint) { |
|
116
|
|
|
$promise = $this->getRepository()->queryXToolsApi($endpoint, true); |
|
117
|
|
|
$promises[] = $promise; |
|
118
|
|
|
|
|
119
|
|
|
// Handle response of $promise asynchronously. |
|
120
|
|
|
$promise->then(function ($response) use ($key, $endpoint) { |
|
121
|
|
|
$result = (array) json_decode($response->getBody()->getContents()); |
|
122
|
|
|
|
|
123
|
|
|
if (isset($result)) { |
|
124
|
|
|
// Copy result to the class class instance. From here any subsequent |
|
125
|
|
|
// calls to the getters (e.g. getPairData()) will return these cached values. |
|
126
|
|
|
$this->{$key} = $result; |
|
127
|
|
|
} else { |
|
128
|
|
|
// The API should *always* return something, so if $result is not set, |
|
129
|
|
|
// something went wrong, so we simply won't set it and the getters will in |
|
130
|
|
|
// turn re-attempt to get the data synchronously. |
|
131
|
|
|
// We'll log this to see how often it happens. |
|
132
|
|
|
$this->getRepository() |
|
133
|
|
|
->getLog() |
|
134
|
|
|
->error("Failed to fetch data for $endpoint via async, " . |
|
135
|
|
|
"re-attempting synchoronously."); |
|
136
|
|
|
} |
|
137
|
|
|
}); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
// Wait for all promises to complete, even if some of them fail. |
|
141
|
|
|
GuzzleHttp\Promise\settle($promises)->wait(); |
|
142
|
|
|
|
|
143
|
|
|
// Everything we need now lives on the class instance, so we're done. |
|
144
|
|
|
return; |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Get revision and page counts etc. |
|
149
|
|
|
* @return int[] |
|
150
|
|
|
*/ |
|
151
|
|
|
public function getPairData() |
|
152
|
|
|
{ |
|
153
|
|
|
if (!is_array($this->pairData)) { |
|
154
|
|
|
$this->pairData = $this->getRepository() |
|
|
|
|
|
|
155
|
|
|
->getPairData($this->project, $this->user); |
|
156
|
|
|
} |
|
157
|
|
|
return $this->pairData; |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* Get revision dates. |
|
162
|
|
|
* @return int[] |
|
163
|
|
|
*/ |
|
164
|
|
|
public function getLogCounts() |
|
165
|
|
|
{ |
|
166
|
|
|
if (!is_array($this->logCounts)) { |
|
167
|
|
|
$this->logCounts = $this->getRepository() |
|
|
|
|
|
|
168
|
|
|
->getLogCounts($this->project, $this->user); |
|
169
|
|
|
} |
|
170
|
|
|
return $this->logCounts; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* Get block data. |
|
175
|
|
|
* @param string $type Either 'set', 'received' |
|
176
|
|
|
* @param bool $blocksOnly Whether to include only blocks, and not reblocks and unblocks. |
|
177
|
|
|
* @return array |
|
178
|
|
|
*/ |
|
179
|
|
|
protected function getBlocks($type, $blocksOnly = true) |
|
180
|
|
|
{ |
|
181
|
|
|
if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) { |
|
182
|
|
|
return $this->blocks[$type]; |
|
183
|
|
|
} |
|
184
|
|
|
$method = "getBlocks".ucfirst($type); |
|
185
|
|
|
$blocks = $this->getRepository()->$method($this->project, $this->user); |
|
186
|
|
|
$this->blocks[$type] = $blocks; |
|
187
|
|
|
|
|
188
|
|
|
// Filter out unblocks unless requested. |
|
189
|
|
|
if ($blocksOnly) { |
|
190
|
|
|
$blocks = array_filter($blocks, function ($block) { |
|
191
|
|
|
return $block['log_action'] === 'block'; |
|
192
|
|
|
}); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
return $blocks; |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* Get the total number of currently-live revisions. |
|
200
|
|
|
* @return int |
|
201
|
|
|
*/ |
|
202
|
|
|
public function countLiveRevisions() |
|
203
|
|
|
{ |
|
204
|
|
|
$revCounts = $this->getPairData(); |
|
205
|
|
|
return isset($revCounts['live']) ? (int)$revCounts['live'] : 0; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
/** |
|
209
|
|
|
* Get the total number of the user's revisions that have been deleted. |
|
210
|
|
|
* @return int |
|
211
|
|
|
*/ |
|
212
|
|
|
public function countDeletedRevisions() |
|
213
|
|
|
{ |
|
214
|
|
|
$revCounts = $this->getPairData(); |
|
215
|
|
|
return isset($revCounts['deleted']) ? (int)$revCounts['deleted'] : 0; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
/** |
|
219
|
|
|
* Get the total edit count (live + deleted). |
|
220
|
|
|
* @return int |
|
221
|
|
|
*/ |
|
222
|
|
|
public function countAllRevisions() |
|
223
|
|
|
{ |
|
224
|
|
|
return $this->countLiveRevisions() + $this->countDeletedRevisions(); |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Get the total number of live revisions with comments. |
|
229
|
|
|
* @return int |
|
230
|
|
|
*/ |
|
231
|
|
|
public function countRevisionsWithComments() |
|
232
|
|
|
{ |
|
233
|
|
|
$revCounts = $this->getPairData(); |
|
234
|
|
|
return isset($revCounts['with_comments']) ? (int)$revCounts['with_comments'] : 0; |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
/** |
|
238
|
|
|
* Get the total number of live revisions without comments. |
|
239
|
|
|
* @return int |
|
240
|
|
|
*/ |
|
241
|
|
|
public function countRevisionsWithoutComments() |
|
242
|
|
|
{ |
|
243
|
|
|
return $this->countLiveRevisions() - $this->countRevisionsWithComments(); |
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
/** |
|
247
|
|
|
* Get the total number of revisions marked as 'minor' by the user. |
|
248
|
|
|
* @return int |
|
249
|
|
|
*/ |
|
250
|
|
|
public function countMinorRevisions() |
|
251
|
|
|
{ |
|
252
|
|
|
$revCounts = $this->getPairData(); |
|
253
|
|
|
return isset($revCounts['minor']) ? (int)$revCounts['minor'] : 0; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* Get the total number of non-deleted pages edited by the user. |
|
258
|
|
|
* @return int |
|
259
|
|
|
*/ |
|
260
|
|
|
public function countLivePagesEdited() |
|
261
|
|
|
{ |
|
262
|
|
|
$pageCounts = $this->getPairData(); |
|
263
|
|
|
return isset($pageCounts['edited-live']) ? (int)$pageCounts['edited-live'] : 0; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
/** |
|
267
|
|
|
* Get the total number of deleted pages ever edited by the user. |
|
268
|
|
|
* @return int |
|
269
|
|
|
*/ |
|
270
|
|
|
public function countDeletedPagesEdited() |
|
271
|
|
|
{ |
|
272
|
|
|
$pageCounts = $this->getPairData(); |
|
273
|
|
|
return isset($pageCounts['edited-deleted']) ? (int)$pageCounts['edited-deleted'] : 0; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** |
|
277
|
|
|
* Get the total number of pages ever edited by this user (both live and deleted). |
|
278
|
|
|
* @return int |
|
279
|
|
|
*/ |
|
280
|
|
|
public function countAllPagesEdited() |
|
281
|
|
|
{ |
|
282
|
|
|
return $this->countLivePagesEdited() + $this->countDeletedPagesEdited(); |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* Get the total number of pages (both still live and those that have been deleted) created |
|
287
|
|
|
* by the user. |
|
288
|
|
|
* @return int |
|
289
|
|
|
*/ |
|
290
|
|
|
public function countPagesCreated() |
|
291
|
|
|
{ |
|
292
|
|
|
return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted(); |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Get the total number of pages created by the user, that have not been deleted. |
|
297
|
|
|
* @return int |
|
298
|
|
|
*/ |
|
299
|
|
|
public function countCreatedPagesLive() |
|
300
|
|
|
{ |
|
301
|
|
|
$pageCounts = $this->getPairData(); |
|
302
|
|
|
return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0; |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Get the total number of pages created by the user, that have since been deleted. |
|
307
|
|
|
* @return int |
|
308
|
|
|
*/ |
|
309
|
|
|
public function countPagesCreatedDeleted() |
|
310
|
|
|
{ |
|
311
|
|
|
$pageCounts = $this->getPairData(); |
|
312
|
|
|
return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
/** |
|
316
|
|
|
* Get the total number of pages that have been deleted by the user. |
|
317
|
|
|
* @return int |
|
318
|
|
|
*/ |
|
319
|
|
|
public function countPagesDeleted() |
|
320
|
|
|
{ |
|
321
|
|
|
$logCounts = $this->getLogCounts(); |
|
322
|
|
|
return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0; |
|
323
|
|
|
} |
|
324
|
|
|
|
|
325
|
|
|
/** |
|
326
|
|
|
* Get the total number of pages moved by the user. |
|
327
|
|
|
* @return int |
|
328
|
|
|
*/ |
|
329
|
|
|
public function countPagesMoved() |
|
330
|
|
|
{ |
|
331
|
|
|
$logCounts = $this->getLogCounts(); |
|
332
|
|
|
return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0; |
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
/** |
|
336
|
|
|
* Get the total number of times the user has blocked a user. |
|
337
|
|
|
* @return int |
|
338
|
|
|
*/ |
|
339
|
|
|
public function countBlocksSet() |
|
340
|
|
|
{ |
|
341
|
|
|
$logCounts = $this->getLogCounts(); |
|
342
|
|
|
$reBlock = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0; |
|
343
|
|
|
return $reBlock; |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
/** |
|
347
|
|
|
* Get the total number of times the user has re-blocked a user. |
|
348
|
|
|
* @return int |
|
349
|
|
|
*/ |
|
350
|
|
|
public function countReblocksSet() |
|
351
|
|
|
{ |
|
352
|
|
|
$logCounts = $this->getLogCounts(); |
|
353
|
|
|
$reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0; |
|
354
|
|
|
return $reBlock; |
|
355
|
|
|
} |
|
356
|
|
|
|
|
357
|
|
|
/** |
|
358
|
|
|
* Get the total number of times the user has unblocked a user. |
|
359
|
|
|
* @return int |
|
360
|
|
|
*/ |
|
361
|
|
|
public function countUnblocksSet() |
|
362
|
|
|
{ |
|
363
|
|
|
$logCounts = $this->getLogCounts(); |
|
364
|
|
|
return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** |
|
368
|
|
|
* Get the total number of blocks that have been lifted (i.e. unblocks) by this user. |
|
369
|
|
|
* @return int |
|
370
|
|
|
*/ |
|
371
|
|
|
public function countBlocksLifted() |
|
372
|
|
|
{ |
|
373
|
|
|
$logCounts = $this->getLogCounts(); |
|
374
|
|
|
return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* Get the total number of times the user has been blocked. |
|
379
|
|
|
* @return int |
|
380
|
|
|
*/ |
|
381
|
|
|
public function countBlocksReceived() |
|
382
|
|
|
{ |
|
383
|
|
|
$blocks = $this->getBlocks('received'); |
|
384
|
|
|
return count($blocks); |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
/** |
|
388
|
|
|
* Get the length of the longest block the user received, in seconds. |
|
389
|
|
|
* @return int Number of seconds or false if it could not be determined. |
|
390
|
|
|
* If the user is blocked, the time since the block is returned. If the block is |
|
391
|
|
|
* indefinite, -1 is returned. 0 if there was never a block. |
|
392
|
|
|
*/ |
|
393
|
|
|
public function getLongestBlockSeconds() |
|
394
|
|
|
{ |
|
395
|
|
|
if (isset($this->longestBlockSeconds)) { |
|
396
|
|
|
return $this->longestBlockSeconds; |
|
|
|
|
|
|
397
|
|
|
} |
|
398
|
|
|
|
|
399
|
|
|
$blocks = $this->getBlocks('received', false); |
|
400
|
|
|
$this->longestBlockSeconds = false; |
|
401
|
|
|
|
|
402
|
|
|
// If there was never a block, the longest was zero seconds. |
|
403
|
|
|
if (empty($blocks)) { |
|
404
|
|
|
return 0; |
|
405
|
|
|
} |
|
406
|
|
|
|
|
407
|
|
|
/** |
|
408
|
|
|
* Keep track of the last block so we can determine the duration |
|
409
|
|
|
* if the current block in the loop is an unblock. |
|
410
|
|
|
* @var int[] [ |
|
411
|
|
|
* Unix timestamp, |
|
412
|
|
|
* Duration in seconds (-1 if indefinite) |
|
413
|
|
|
* ] |
|
414
|
|
|
*/ |
|
415
|
|
|
$lastBlock = [null, null]; |
|
416
|
|
|
|
|
417
|
|
|
foreach ($blocks as $index => $block) { |
|
418
|
|
|
list($timestamp, $duration) = $this->parseBlockLogEntry($block); |
|
419
|
|
|
|
|
420
|
|
|
if ($block['log_action'] === 'block') { |
|
421
|
|
|
// This is a new block, so first see if the duration of the last |
|
422
|
|
|
// block exceeded our longest duration. -1 duration means indefinite. |
|
423
|
|
|
if ($lastBlock[1] > $this->longestBlockSeconds || $lastBlock[1] === -1) { |
|
424
|
|
|
$this->longestBlockSeconds = $lastBlock[1]; |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
// Now set this as the last block. |
|
428
|
|
|
$lastBlock = [$timestamp, $duration]; |
|
429
|
|
|
} elseif ($block['log_action'] === 'unblock') { |
|
430
|
|
|
// The last block was lifted. So the duration will be the time from when the |
|
431
|
|
|
// last block was set to the time of the unblock. |
|
432
|
|
|
$timeSinceLastBlock = $timestamp - $lastBlock[0]; |
|
433
|
|
|
if ($timeSinceLastBlock > $this->longestBlockSeconds) { |
|
434
|
|
|
$this->longestBlockSeconds = $timeSinceLastBlock; |
|
|
|
|
|
|
435
|
|
|
|
|
436
|
|
|
// Reset the last block, as it has now been accounted for. |
|
437
|
|
|
$lastBlock = null; |
|
438
|
|
|
} |
|
439
|
|
|
} elseif ($block['log_action'] === 'reblock' && $lastBlock[1] !== -1) { |
|
440
|
|
|
// The last block was modified. So we will adjust $lastBlock to include |
|
441
|
|
|
// the difference of the duration of the new reblock, and time since the last block. |
|
442
|
|
|
// $lastBlock is left unchanged if its duration was indefinite. |
|
443
|
|
|
$timeSinceLastBlock = $timestamp - $lastBlock[0]; |
|
444
|
|
|
$lastBlock[1] = $timeSinceLastBlock + $duration; |
|
445
|
|
|
} |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
// If the last block was indefinite, we'll return that as the longest duration. |
|
449
|
|
|
if ($lastBlock[1] === -1) { |
|
450
|
|
|
return -1; |
|
451
|
|
|
} |
|
452
|
|
|
|
|
453
|
|
|
// Test if the last block is still active, and if so use the expiry as the duration. |
|
454
|
|
|
$lastBlockExpiry = $lastBlock[0] + $lastBlock[1]; |
|
455
|
|
|
if ($lastBlockExpiry > time() && $lastBlockExpiry > $this->longestBlockSeconds) { |
|
456
|
|
|
$this->longestBlockSeconds = $lastBlock[1]; |
|
457
|
|
|
// Otherwise, test if the duration of the last block is now the longest overall. |
|
458
|
|
|
} elseif ($lastBlock[1] > $this->longestBlockSeconds) { |
|
459
|
|
|
$this->longestBlockSeconds = $lastBlock[1]; |
|
460
|
|
|
} |
|
461
|
|
|
|
|
462
|
|
|
return $this->longestBlockSeconds; |
|
463
|
|
|
} |
|
464
|
|
|
|
|
465
|
|
|
/** |
|
466
|
|
|
* Given a block log entry from the database, get the timestamp and duration in seconds. |
|
467
|
|
|
* @param mixed[] $block Block log entry as fetched via self::getBlocks() |
|
468
|
|
|
* @return int[] [ |
|
469
|
|
|
* Unix timestamp, |
|
470
|
|
|
* Duration in seconds (-1 if indefinite, null if unparsable or unblock) |
|
471
|
|
|
* ] |
|
472
|
|
|
*/ |
|
473
|
|
|
public function parseBlockLogEntry($block) |
|
474
|
|
|
{ |
|
475
|
|
|
$timestamp = strtotime($block['log_timestamp']); |
|
476
|
|
|
$duration = null; |
|
477
|
|
|
|
|
478
|
|
|
// First check if the string is serialized, and if so parse it to get the block duration. |
|
479
|
|
|
if (@unserialize($block['log_params']) !== false) { |
|
480
|
|
|
$parsedParams = unserialize($block['log_params']); |
|
481
|
|
|
$durationStr = isset($parsedParams['5::duration']) ? $parsedParams['5::duration'] : null; |
|
482
|
|
|
} else { |
|
483
|
|
|
// Old format, the duration in English + block options separated by new lines. |
|
484
|
|
|
$durationStr = explode("\n", $block['log_params'])[0]; |
|
485
|
|
|
} |
|
486
|
|
|
|
|
487
|
|
|
if (in_array($durationStr, ['indefinite', 'infinity', 'infinite'])) { |
|
488
|
|
|
$duration = -1; |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
// Make sure $durationStr is valid just in case it is in an older, unpredictable format. |
|
492
|
|
|
// If invalid, $duration is left as null. |
|
493
|
|
|
if (strtotime($durationStr)) { |
|
494
|
|
|
$expiry = strtotime($durationStr, $timestamp); |
|
495
|
|
|
$duration = $expiry - $timestamp; |
|
496
|
|
|
} |
|
497
|
|
|
|
|
498
|
|
|
return [$timestamp, $duration]; |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
/** |
|
502
|
|
|
* Get the total number of pages protected by the user. |
|
503
|
|
|
* @return int |
|
504
|
|
|
*/ |
|
505
|
|
|
public function countPagesProtected() |
|
506
|
|
|
{ |
|
507
|
|
|
$logCounts = $this->getLogCounts(); |
|
508
|
|
|
return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0; |
|
509
|
|
|
} |
|
510
|
|
|
|
|
511
|
|
|
/** |
|
512
|
|
|
* Get the total number of pages reprotected by the user. |
|
513
|
|
|
* @return int |
|
514
|
|
|
*/ |
|
515
|
|
|
public function countPagesReprotected() |
|
516
|
|
|
{ |
|
517
|
|
|
$logCounts = $this->getLogCounts(); |
|
518
|
|
|
return isset($logCounts['protect-modify']) ? (int)$logCounts['protect-modify'] : 0; |
|
519
|
|
|
} |
|
520
|
|
|
|
|
521
|
|
|
/** |
|
522
|
|
|
* Get the total number of pages unprotected by the user. |
|
523
|
|
|
* @return int |
|
524
|
|
|
*/ |
|
525
|
|
|
public function countPagesUnprotected() |
|
526
|
|
|
{ |
|
527
|
|
|
$logCounts = $this->getLogCounts(); |
|
528
|
|
|
return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0; |
|
529
|
|
|
} |
|
530
|
|
|
|
|
531
|
|
|
/** |
|
532
|
|
|
* Get the total number of edits deleted by the user. |
|
533
|
|
|
* @return int |
|
534
|
|
|
*/ |
|
535
|
|
|
public function countEditsDeleted() |
|
536
|
|
|
{ |
|
537
|
|
|
$logCounts = $this->getLogCounts(); |
|
538
|
|
|
return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0; |
|
539
|
|
|
} |
|
540
|
|
|
|
|
541
|
|
|
/** |
|
542
|
|
|
* Get the total number of pages restored by the user. |
|
543
|
|
|
* @return int |
|
544
|
|
|
*/ |
|
545
|
|
|
public function countPagesRestored() |
|
546
|
|
|
{ |
|
547
|
|
|
$logCounts = $this->getLogCounts(); |
|
548
|
|
|
return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0; |
|
549
|
|
|
} |
|
550
|
|
|
|
|
551
|
|
|
/** |
|
552
|
|
|
* Get the total number of times the user has modified the rights of a user. |
|
553
|
|
|
* @return int |
|
554
|
|
|
*/ |
|
555
|
|
|
public function countRightsModified() |
|
556
|
|
|
{ |
|
557
|
|
|
$logCounts = $this->getLogCounts(); |
|
558
|
|
|
return isset($logCounts['rights-rights']) ? (int)$logCounts['rights-rights'] : 0; |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
/** |
|
562
|
|
|
* Get the total number of pages imported by the user (through any import mechanism: |
|
563
|
|
|
* interwiki, or XML upload). |
|
564
|
|
|
* @return int |
|
565
|
|
|
*/ |
|
566
|
|
|
public function countPagesImported() |
|
567
|
|
|
{ |
|
568
|
|
|
$logCounts = $this->getLogCounts(); |
|
569
|
|
|
$import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0; |
|
570
|
|
|
$interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0; |
|
571
|
|
|
$upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0; |
|
572
|
|
|
return $import + $interwiki + $upload; |
|
573
|
|
|
} |
|
574
|
|
|
|
|
575
|
|
|
/** |
|
576
|
|
|
* Get the average number of edits per page (including deleted revisions and pages). |
|
577
|
|
|
* @return float |
|
578
|
|
|
*/ |
|
579
|
|
|
public function averageRevisionsPerPage() |
|
580
|
|
|
{ |
|
581
|
|
|
if ($this->countAllPagesEdited() == 0) { |
|
582
|
|
|
return 0; |
|
583
|
|
|
} |
|
584
|
|
|
return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3); |
|
585
|
|
|
} |
|
586
|
|
|
|
|
587
|
|
|
/** |
|
588
|
|
|
* Average number of edits made per day. |
|
589
|
|
|
* @return float |
|
590
|
|
|
*/ |
|
591
|
|
|
public function averageRevisionsPerDay() |
|
592
|
|
|
{ |
|
593
|
|
|
if ($this->getDays() == 0) { |
|
594
|
|
|
return 0; |
|
595
|
|
|
} |
|
596
|
|
|
return round($this->countAllRevisions() / $this->getDays(), 3); |
|
597
|
|
|
} |
|
598
|
|
|
|
|
599
|
|
|
/** |
|
600
|
|
|
* Get the total number of edits made by the user with semi-automating tools. |
|
601
|
|
|
*/ |
|
602
|
|
|
public function countAutomatedEdits() |
|
603
|
|
|
{ |
|
604
|
|
|
if ($this->autoEditCount) { |
|
605
|
|
|
return $this->autoEditCount; |
|
606
|
|
|
} |
|
607
|
|
|
$this->autoEditCount = $this->user->countAutomatedEdits($this->project); |
|
608
|
|
|
return $this->autoEditCount; |
|
609
|
|
|
} |
|
610
|
|
|
|
|
611
|
|
|
/** |
|
612
|
|
|
* Get the count of (non-deleted) edits made in the given timeframe to now. |
|
613
|
|
|
* @param string $time One of 'day', 'week', 'month', or 'year'. |
|
614
|
|
|
* @return int The total number of live edits. |
|
615
|
|
|
*/ |
|
616
|
|
|
public function countRevisionsInLast($time) |
|
617
|
|
|
{ |
|
618
|
|
|
$revCounts = $this->getPairData(); |
|
619
|
|
|
return isset($revCounts[$time]) ? $revCounts[$time] : 0; |
|
620
|
|
|
} |
|
621
|
|
|
|
|
622
|
|
|
/** |
|
623
|
|
|
* Get the date and time of the user's first edit. |
|
624
|
|
|
* @return DateTime|bool The time of the first revision, or false. |
|
625
|
|
|
*/ |
|
626
|
|
|
public function datetimeFirstRevision() |
|
627
|
|
|
{ |
|
628
|
|
|
$revDates = $this->getPairData(); |
|
629
|
|
|
return isset($revDates['first']) ? new DateTime($revDates['first']) : false; |
|
630
|
|
|
} |
|
631
|
|
|
|
|
632
|
|
|
/** |
|
633
|
|
|
* Get the date and time of the user's first edit. |
|
634
|
|
|
* @return DateTime|bool The time of the last revision, or false. |
|
635
|
|
|
*/ |
|
636
|
|
|
public function datetimeLastRevision() |
|
637
|
|
|
{ |
|
638
|
|
|
$revDates = $this->getPairData(); |
|
639
|
|
|
return isset($revDates['last']) ? new DateTime($revDates['last']) : false; |
|
640
|
|
|
} |
|
641
|
|
|
|
|
642
|
|
|
/** |
|
643
|
|
|
* Get the number of days between the first and last edits. |
|
644
|
|
|
* If there's only one edit, this is counted as one day. |
|
645
|
|
|
* @return int |
|
646
|
|
|
*/ |
|
647
|
|
|
public function getDays() |
|
648
|
|
|
{ |
|
649
|
|
|
$first = $this->datetimeFirstRevision(); |
|
650
|
|
|
$last = $this->datetimeLastRevision(); |
|
651
|
|
|
if ($first === false || $last === false) { |
|
652
|
|
|
return 0; |
|
653
|
|
|
} |
|
654
|
|
|
$days = $last->diff($first)->days; |
|
655
|
|
|
return $days > 0 ? $days : 1; |
|
656
|
|
|
} |
|
657
|
|
|
|
|
658
|
|
|
/** |
|
659
|
|
|
* Get the total number of files uploaded (including those now deleted). |
|
660
|
|
|
* @return int |
|
661
|
|
|
*/ |
|
662
|
|
|
public function countFilesUploaded() |
|
663
|
|
|
{ |
|
664
|
|
|
$logCounts = $this->getLogCounts(); |
|
665
|
|
|
return $logCounts['upload-upload'] ?: 0; |
|
666
|
|
|
} |
|
667
|
|
|
|
|
668
|
|
|
/** |
|
669
|
|
|
* Get the total number of files uploaded to Commons (including those now deleted). |
|
670
|
|
|
* This is only applicable for WMF labs installations. |
|
671
|
|
|
* @return int |
|
672
|
|
|
*/ |
|
673
|
|
|
public function countFilesUploadedCommons() |
|
674
|
|
|
{ |
|
675
|
|
|
$logCounts = $this->getLogCounts(); |
|
676
|
|
|
return $logCounts['files_uploaded_commons'] ?: 0; |
|
677
|
|
|
} |
|
678
|
|
|
|
|
679
|
|
|
/** |
|
680
|
|
|
* Get the total number of revisions the user has sent thanks for. |
|
681
|
|
|
* @return int |
|
682
|
|
|
*/ |
|
683
|
|
|
public function thanks() |
|
684
|
|
|
{ |
|
685
|
|
|
$logCounts = $this->getLogCounts(); |
|
686
|
|
|
return $logCounts['thanks-thank'] ?: 0; |
|
687
|
|
|
} |
|
688
|
|
|
|
|
689
|
|
|
/** |
|
690
|
|
|
* Get the total number of approvals |
|
691
|
|
|
* @return int |
|
692
|
|
|
*/ |
|
693
|
|
|
public function approvals() |
|
694
|
|
|
{ |
|
695
|
|
|
$logCounts = $this->getLogCounts(); |
|
696
|
|
|
$total = $logCounts['review-approve'] + |
|
697
|
|
|
(!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) + |
|
698
|
|
|
(!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) + |
|
699
|
|
|
(!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0); |
|
700
|
|
|
return $total; |
|
701
|
|
|
} |
|
702
|
|
|
|
|
703
|
|
|
/** |
|
704
|
|
|
* Get the total number of patrols performed by the user. |
|
705
|
|
|
* @return int |
|
706
|
|
|
*/ |
|
707
|
|
|
public function patrols() |
|
708
|
|
|
{ |
|
709
|
|
|
$logCounts = $this->getLogCounts(); |
|
710
|
|
|
return $logCounts['patrol-patrol'] ?: 0; |
|
711
|
|
|
} |
|
712
|
|
|
|
|
713
|
|
|
/** |
|
714
|
|
|
* Get the total number of accounts created by the user. |
|
715
|
|
|
* @return int |
|
716
|
|
|
*/ |
|
717
|
|
|
public function accountsCreated() |
|
718
|
|
|
{ |
|
719
|
|
|
$logCounts = $this->getLogCounts(); |
|
720
|
|
|
$create2 = $logCounts['newusers-create2'] ?: 0; |
|
721
|
|
|
$byemail = $logCounts['newusers-byemail'] ?: 0; |
|
722
|
|
|
return $create2 + $byemail; |
|
723
|
|
|
} |
|
724
|
|
|
|
|
725
|
|
|
/** |
|
726
|
|
|
* Get the given user's total edit counts per namespace. |
|
727
|
|
|
* @return integer[] Array keys are namespace IDs, values are the edit counts. |
|
728
|
|
|
*/ |
|
729
|
|
|
public function namespaceTotals() |
|
730
|
|
|
{ |
|
731
|
|
|
if ($this->namespaceTotals) { |
|
|
|
|
|
|
732
|
|
|
return $this->namespaceTotals; |
|
733
|
|
|
} |
|
734
|
|
|
$counts = $this->getRepository()->getNamespaceTotals($this->project, $this->user); |
|
|
|
|
|
|
735
|
|
|
arsort($counts); |
|
736
|
|
|
$this->namespaceTotals = $counts; |
|
737
|
|
|
return $counts; |
|
738
|
|
|
} |
|
739
|
|
|
|
|
740
|
|
|
/** |
|
741
|
|
|
* Get a summary of the times of day and the days of the week that the user has edited. |
|
742
|
|
|
* @return string[] |
|
743
|
|
|
*/ |
|
744
|
|
|
public function timeCard() |
|
745
|
|
|
{ |
|
746
|
|
|
if ($this->timeCardData) { |
|
|
|
|
|
|
747
|
|
|
return $this->timeCardData; |
|
748
|
|
|
} |
|
749
|
|
|
$totals = $this->getRepository()->getTimeCard($this->project, $this->user); |
|
|
|
|
|
|
750
|
|
|
$this->timeCardData = $totals; |
|
751
|
|
|
return $totals; |
|
752
|
|
|
} |
|
753
|
|
|
|
|
754
|
|
|
/** |
|
755
|
|
|
* Get the total numbers of edits per month. |
|
756
|
|
|
* @param null|DateTime $currentTime - *USED ONLY FOR UNIT TESTING* |
|
757
|
|
|
* so we can mock the current DateTime. |
|
758
|
|
|
* @return mixed[] With keys 'yearLabels', 'monthLabels' and 'totals', |
|
759
|
|
|
* the latter keyed by namespace, year and then month. |
|
760
|
|
|
*/ |
|
761
|
|
|
public function monthCounts($currentTime = null) |
|
762
|
|
|
{ |
|
763
|
|
|
if (isset($this->monthCounts)) { |
|
764
|
|
|
return $this->monthCounts; |
|
765
|
|
|
} |
|
766
|
|
|
|
|
767
|
|
|
// Set to current month if we're not unit-testing |
|
768
|
|
|
if (!($currentTime instanceof DateTime)) { |
|
769
|
|
|
$currentTime = new DateTime('last day of this month'); |
|
770
|
|
|
} |
|
771
|
|
|
|
|
772
|
|
|
$totals = $this->getRepository()->getMonthCounts($this->project, $this->user); |
|
|
|
|
|
|
773
|
|
|
$out = [ |
|
774
|
|
|
'yearLabels' => [], // labels for years |
|
775
|
|
|
'monthLabels' => [], // labels for months |
|
776
|
|
|
'totals' => [], // actual totals, grouped by namespace, year and then month |
|
777
|
|
|
]; |
|
778
|
|
|
|
|
779
|
|
|
/** @var DateTime Keep track of the date of their first edit. */ |
|
780
|
|
|
$firstEdit = new DateTime(); |
|
781
|
|
|
|
|
782
|
|
|
// Loop through the database results and fill in the values |
|
783
|
|
|
// for the months that we have data for. |
|
784
|
|
|
foreach ($totals as $total) { |
|
785
|
|
|
// Keep track of first edit |
|
786
|
|
|
$date = new DateTime($total['year'].'-'.$total['month'].'-01'); |
|
787
|
|
|
if ($date < $firstEdit) { |
|
788
|
|
|
$firstEdit = $date; |
|
789
|
|
|
} |
|
790
|
|
|
|
|
791
|
|
|
// Collate the counts by namespace, and then year and month. |
|
792
|
|
|
$ns = $total['page_namespace']; |
|
793
|
|
|
if (!isset($out['totals'][$ns])) { |
|
794
|
|
|
$out['totals'][$ns] = []; |
|
795
|
|
|
} |
|
796
|
|
|
|
|
797
|
|
|
// Start array for this year if not already present. |
|
798
|
|
|
if (!isset($out['totals'][$ns][$total['year']])) { |
|
799
|
|
|
$out['totals'][$ns][$total['year']] = []; |
|
800
|
|
|
} |
|
801
|
|
|
|
|
802
|
|
|
$out['totals'][$ns][$total['year']][$total['month']] = (int) $total['count']; |
|
803
|
|
|
} |
|
804
|
|
|
|
|
805
|
|
|
$dateRange = new DatePeriod( |
|
806
|
|
|
$firstEdit, |
|
807
|
|
|
new DateInterval('P1M'), |
|
808
|
|
|
$currentTime->modify('first day of this month') |
|
809
|
|
|
); |
|
810
|
|
|
|
|
811
|
|
|
foreach ($dateRange as $monthObj) { |
|
812
|
|
|
$year = (int) $monthObj->format('Y'); |
|
813
|
|
|
$month = (int) $monthObj->format('n'); |
|
814
|
|
|
|
|
815
|
|
|
// Fill in labels |
|
816
|
|
|
$out['monthLabels'][] = $monthObj->format('Y-m'); |
|
817
|
|
|
if (!in_array($year, $out['yearLabels'])) { |
|
818
|
|
|
$out['yearLabels'][] = $year; |
|
819
|
|
|
} |
|
820
|
|
|
|
|
821
|
|
|
foreach (array_keys($out['totals']) as $nsId) { |
|
822
|
|
|
if (!isset($out['totals'][$nsId][$year])) { |
|
823
|
|
|
$out['totals'][$nsId][$year] = []; |
|
824
|
|
|
} |
|
825
|
|
|
|
|
826
|
|
|
if (!isset($out['totals'][$nsId][$year][$month])) { |
|
827
|
|
|
$out['totals'][$nsId][$year][$month] = 0; |
|
828
|
|
|
} |
|
829
|
|
|
} |
|
830
|
|
|
} |
|
831
|
|
|
|
|
832
|
|
|
// One more set of loops to sort by year/month |
|
833
|
|
|
foreach (array_keys($out['totals']) as $nsId) { |
|
834
|
|
|
ksort($out['totals'][$nsId]); |
|
835
|
|
|
|
|
836
|
|
|
foreach ($out['totals'][$nsId] as &$yearData) { |
|
837
|
|
|
ksort($yearData); |
|
838
|
|
|
} |
|
839
|
|
|
} |
|
840
|
|
|
|
|
841
|
|
|
// Finally, sort the namespaces |
|
842
|
|
|
ksort($out['totals']); |
|
843
|
|
|
|
|
844
|
|
|
$this->monthCounts = $out; |
|
|
|
|
|
|
845
|
|
|
return $out; |
|
|
|
|
|
|
846
|
|
|
} |
|
847
|
|
|
|
|
848
|
|
|
/** |
|
849
|
|
|
* Get the total numbers of edits per year. |
|
850
|
|
|
* @param null|DateTime [$currentTime] - *USED ONLY FOR UNIT TESTING* |
|
851
|
|
|
* so we can mock the current DateTime. |
|
852
|
|
|
* @return mixed[] With keys 'yearLabels' and 'totals', the latter |
|
853
|
|
|
* keyed by namespace then year. |
|
854
|
|
|
*/ |
|
855
|
|
|
public function yearCounts($currentTime = null) |
|
856
|
|
|
{ |
|
857
|
|
|
if (isset($this->yearCounts)) { |
|
858
|
|
|
return $this->yearCounts; |
|
859
|
|
|
} |
|
860
|
|
|
|
|
861
|
|
|
$out = $this->monthCounts($currentTime); |
|
862
|
|
|
|
|
863
|
|
|
foreach ($out['totals'] as $nsId => $years) { |
|
864
|
|
|
foreach ($years as $year => $months) { |
|
865
|
|
|
$out['totals'][$nsId][$year] = array_sum(array_values($months)); |
|
866
|
|
|
} |
|
867
|
|
|
} |
|
868
|
|
|
|
|
869
|
|
|
$this->yearCounts = $out; |
|
870
|
|
|
return $out; |
|
871
|
|
|
} |
|
872
|
|
|
|
|
873
|
|
|
/** |
|
874
|
|
|
* Get the total edit counts for the top n projects of this user. |
|
875
|
|
|
* @param int $numProjects |
|
876
|
|
|
* @return mixed[] Each element has 'total' and 'project' keys. |
|
877
|
|
|
*/ |
|
878
|
|
|
public function globalEditCountsTopN($numProjects = 10) |
|
879
|
|
|
{ |
|
880
|
|
|
// Get counts. |
|
881
|
|
|
$editCounts = $this->globalEditCounts(true); |
|
882
|
|
|
// Truncate, and return. |
|
|
|
|
|
|
883
|
|
|
return array_slice($editCounts, 0, $numProjects); |
|
884
|
|
|
} |
|
885
|
|
|
|
|
886
|
|
|
/** |
|
887
|
|
|
* Get the total number of edits excluding the top n. |
|
888
|
|
|
* @param int $numProjects |
|
889
|
|
|
* @return int |
|
890
|
|
|
*/ |
|
891
|
|
|
public function globalEditCountWithoutTopN($numProjects = 10) |
|
892
|
|
|
{ |
|
893
|
|
|
$editCounts = $this->globalEditCounts(true); |
|
894
|
|
|
$bottomM = array_slice($editCounts, $numProjects); |
|
895
|
|
|
$total = 0; |
|
896
|
|
|
foreach ($bottomM as $editCount) { |
|
897
|
|
|
$total += $editCount['total']; |
|
898
|
|
|
} |
|
899
|
|
|
return $total; |
|
900
|
|
|
} |
|
901
|
|
|
|
|
902
|
|
|
/** |
|
903
|
|
|
* Get the grand total of all edits on all projects. |
|
904
|
|
|
* @return int |
|
905
|
|
|
*/ |
|
906
|
|
|
public function globalEditCount() |
|
907
|
|
|
{ |
|
908
|
|
|
$total = 0; |
|
909
|
|
|
foreach ($this->globalEditCounts() as $editCount) { |
|
910
|
|
|
$total += $editCount['total']; |
|
911
|
|
|
} |
|
912
|
|
|
return $total; |
|
913
|
|
|
} |
|
914
|
|
|
|
|
915
|
|
|
/** |
|
916
|
|
|
* Get the total revision counts for all projects for this user. |
|
917
|
|
|
* @param bool $sorted Whether to sort the list by total, or not. |
|
918
|
|
|
* @return mixed[] Each element has 'total' and 'project' keys. |
|
919
|
|
|
*/ |
|
920
|
|
|
public function globalEditCounts($sorted = false) |
|
921
|
|
|
{ |
|
922
|
|
|
if (empty($this->globalEditCounts)) { |
|
923
|
|
|
$this->globalEditCounts = $this->getRepository() |
|
|
|
|
|
|
924
|
|
|
->globalEditCounts($this->user, $this->project); |
|
925
|
|
|
} |
|
926
|
|
|
|
|
927
|
|
|
if ($sorted) { |
|
928
|
|
|
// Sort. |
|
929
|
|
|
uasort($this->globalEditCounts, function ($a, $b) { |
|
930
|
|
|
return $b['total'] - $a['total']; |
|
931
|
|
|
}); |
|
932
|
|
|
} |
|
933
|
|
|
|
|
934
|
|
|
return $this->globalEditCounts; |
|
935
|
|
|
} |
|
936
|
|
|
|
|
937
|
|
|
/** |
|
938
|
|
|
* Get the most recent n revisions across all projects. |
|
939
|
|
|
* @param int $max The maximum number of revisions to return. |
|
940
|
|
|
* @return Edit[] |
|
941
|
|
|
*/ |
|
942
|
|
|
public function globalEdits($max) |
|
943
|
|
|
{ |
|
944
|
|
|
// Collect all projects with any edits. |
|
945
|
|
|
$projects = []; |
|
946
|
|
|
foreach ($this->globalEditCounts() as $editCount) { |
|
947
|
|
|
// Don't query revisions if there aren't any. |
|
948
|
|
|
if ($editCount['total'] == 0) { |
|
949
|
|
|
continue; |
|
950
|
|
|
} |
|
951
|
|
|
$projects[$editCount['project']->getDatabaseName()] = $editCount['project']; |
|
952
|
|
|
} |
|
953
|
|
|
|
|
954
|
|
|
// Get all revisions for those projects. |
|
955
|
|
|
$globalRevisionsData = $this->getRepository() |
|
|
|
|
|
|
956
|
|
|
->getRevisions($projects, $this->user, $max); |
|
957
|
|
|
$globalEdits = []; |
|
958
|
|
|
foreach ($globalRevisionsData as $revision) { |
|
959
|
|
|
/** @var Project $project */ |
|
960
|
|
|
$project = $projects[$revision['project_name']]; |
|
961
|
|
|
$nsName = ''; |
|
962
|
|
|
if ($revision['page_namespace']) { |
|
963
|
|
|
$nsName = $project->getNamespaces()[$revision['page_namespace']]; |
|
964
|
|
|
} |
|
965
|
|
|
$page = $project->getRepository() |
|
|
|
|
|
|
966
|
|
|
->getPage($project, $nsName . ':' . $revision['page_title']); |
|
967
|
|
|
$edit = new Edit($page, $revision); |
|
968
|
|
|
$globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit; |
|
969
|
|
|
} |
|
970
|
|
|
|
|
971
|
|
|
// Sort and prune, before adding more. |
|
972
|
|
|
krsort($globalEdits); |
|
973
|
|
|
$globalEdits = array_slice($globalEdits, 0, $max); |
|
974
|
|
|
return $globalEdits; |
|
975
|
|
|
} |
|
976
|
|
|
|
|
977
|
|
|
/** |
|
978
|
|
|
* Get average edit size, and number of large and small edits. |
|
979
|
|
|
* @return int[] |
|
980
|
|
|
*/ |
|
981
|
|
|
public function getEditSizeData() |
|
982
|
|
|
{ |
|
983
|
|
|
if (!is_array($this->editSizeData)) { |
|
984
|
|
|
$this->editSizeData = $this->getRepository() |
|
|
|
|
|
|
985
|
|
|
->getEditSizeData($this->project, $this->user); |
|
986
|
|
|
} |
|
987
|
|
|
return $this->editSizeData; |
|
988
|
|
|
} |
|
989
|
|
|
|
|
990
|
|
|
/** |
|
991
|
|
|
* Get the total edit count of this user or 5,000 if they've made more than 5,000 edits. |
|
992
|
|
|
* This is used to ensure percentages of small and large edits are computed properly. |
|
993
|
|
|
* @return int |
|
994
|
|
|
*/ |
|
995
|
|
|
public function countLast5000() |
|
996
|
|
|
{ |
|
997
|
|
|
return $this->countLiveRevisions() > 5000 ? 5000 : $this->countLiveRevisions(); |
|
998
|
|
|
} |
|
999
|
|
|
|
|
1000
|
|
|
/** |
|
1001
|
|
|
* Get the number of edits under 20 bytes of the user's past 5000 edits. |
|
1002
|
|
|
* @return int |
|
1003
|
|
|
*/ |
|
1004
|
|
|
public function countSmallEdits() |
|
1005
|
|
|
{ |
|
1006
|
|
|
$editSizeData = $this->getEditSizeData(); |
|
1007
|
|
|
return isset($editSizeData['small_edits']) ? (int) $editSizeData['small_edits'] : 0; |
|
1008
|
|
|
} |
|
1009
|
|
|
|
|
1010
|
|
|
/** |
|
1011
|
|
|
* Get the total number of edits over 1000 bytes of the user's past 5000 edits. |
|
1012
|
|
|
* @return int |
|
1013
|
|
|
*/ |
|
1014
|
|
|
public function countLargeEdits() |
|
1015
|
|
|
{ |
|
1016
|
|
|
$editSizeData = $this->getEditSizeData(); |
|
1017
|
|
|
return isset($editSizeData['large_edits']) ? (int) $editSizeData['large_edits'] : 0; |
|
1018
|
|
|
} |
|
1019
|
|
|
|
|
1020
|
|
|
/** |
|
1021
|
|
|
* Get the average size of the user's past 5000 edits. |
|
1022
|
|
|
* @return float Size in bytes. |
|
1023
|
|
|
*/ |
|
1024
|
|
|
public function averageEditSize() |
|
1025
|
|
|
{ |
|
1026
|
|
|
$editSizeData = $this->getEditSizeData(); |
|
1027
|
|
|
if (isset($editSizeData['average_size'])) { |
|
1028
|
|
|
return round($editSizeData['average_size'], 3); |
|
1029
|
|
|
} else { |
|
1030
|
|
|
return 0; |
|
1031
|
|
|
} |
|
1032
|
|
|
} |
|
1033
|
|
|
} |
|
1034
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.