|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file contains only the ArticleInfo class. |
|
4
|
|
|
*/ |
|
5
|
|
|
|
|
6
|
|
|
namespace Xtools; |
|
7
|
|
|
|
|
8
|
|
|
use Symfony\Component\DependencyInjection\Container; |
|
9
|
|
|
use DateTime; |
|
10
|
|
|
|
|
11
|
|
|
/** |
|
12
|
|
|
* An ArticleInfo provides statistics about a page on a project. This model does not |
|
13
|
|
|
* have a separate Repository because it needs to use individual SQL statements to |
|
14
|
|
|
* traverse the page's history, saving class instance variables along the way. |
|
15
|
|
|
*/ |
|
16
|
|
|
class ArticleInfo extends Model |
|
17
|
|
|
{ |
|
18
|
|
|
/** @var Container The application's DI container. */ |
|
19
|
|
|
protected $container; |
|
20
|
|
|
|
|
21
|
|
|
/** @var Page The page. */ |
|
22
|
|
|
protected $page; |
|
23
|
|
|
|
|
24
|
|
|
/** @var int Number of revisions that belong to the page. */ |
|
25
|
|
|
protected $numRevisions; |
|
26
|
|
|
|
|
27
|
|
|
/** @var int Maximum number of revisions to process, as configured. */ |
|
28
|
|
|
protected $maxRevisions; |
|
29
|
|
|
|
|
30
|
|
|
/** @var int Number of revisions that were actually processed. */ |
|
31
|
|
|
protected $numRevisionsProcessed; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Various statistics about editors to the page. These are not User objects |
|
35
|
|
|
* so as to preserve memory. |
|
36
|
|
|
* @var mixed[] |
|
37
|
|
|
*/ |
|
38
|
|
|
protected $editors; |
|
39
|
|
|
|
|
40
|
|
|
/** @var mixed[] The top 10 editors to the page by number of edits. */ |
|
41
|
|
|
protected $topTenEditorsByEdits; |
|
42
|
|
|
|
|
43
|
|
|
/** @var mixed[] The top 10 editors to the page by added text. */ |
|
44
|
|
|
protected $topTenEditorsByAdded; |
|
45
|
|
|
|
|
46
|
|
|
/** @var int Number of edits made by the top 10 editors. */ |
|
47
|
|
|
protected $topTenCount; |
|
48
|
|
|
|
|
49
|
|
|
/** @var mixed[] Various statistics about bots that edited the page. */ |
|
50
|
|
|
protected $bots; |
|
51
|
|
|
|
|
52
|
|
|
/** @var int Number of edits made to the page by bots. */ |
|
53
|
|
|
protected $botRevisionCount; |
|
54
|
|
|
|
|
55
|
|
|
/** @var mixed[] Various counts about each individual year and month of the page's history. */ |
|
56
|
|
|
protected $yearMonthCounts; |
|
57
|
|
|
|
|
58
|
|
|
/** @var Edit The first edit to the page. */ |
|
59
|
|
|
protected $firstEdit; |
|
60
|
|
|
|
|
61
|
|
|
/** @var Edit The last edit to the page. */ |
|
62
|
|
|
protected $lastEdit; |
|
63
|
|
|
|
|
64
|
|
|
/** @var Edit Edit that made the largest addition by number of bytes. */ |
|
65
|
|
|
protected $maxAddition; |
|
66
|
|
|
|
|
67
|
|
|
/** @var Edit Edit that made the largest deletion by number of bytes. */ |
|
68
|
|
|
protected $maxDeletion; |
|
69
|
|
|
|
|
70
|
|
|
/** @var int[] Number of in and outgoing links and redirects to the page. */ |
|
71
|
|
|
protected $linksAndRedirects; |
|
72
|
|
|
|
|
73
|
|
|
/** @var string[] Assessments of the page (see Page::getAssessments). */ |
|
74
|
|
|
protected $assessments; |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* Maximum number of edits that were created across all months. This is used as a comparison |
|
78
|
|
|
* for the bar charts in the months section. |
|
79
|
|
|
* @var int |
|
80
|
|
|
*/ |
|
81
|
|
|
protected $maxEditsPerMonth; |
|
82
|
|
|
|
|
83
|
|
|
/** @var string[] List of (semi-)automated tools that were used to edit the page. */ |
|
84
|
|
|
protected $tools; |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Total number of bytes added throughout the page's history. This is used as a comparison |
|
88
|
|
|
* when computing the top 10 editors by added text. |
|
89
|
|
|
* @var int |
|
90
|
|
|
*/ |
|
91
|
|
|
protected $addedBytes = 0; |
|
92
|
|
|
|
|
93
|
|
|
/** @var int Number of days between first and last edit. */ |
|
94
|
|
|
protected $totalDays; |
|
95
|
|
|
|
|
96
|
|
|
/** @var int Number of minor edits to the page. */ |
|
97
|
|
|
protected $minorCount = 0; |
|
98
|
|
|
|
|
99
|
|
|
/** @var int Number of anonymous edits to the page. */ |
|
100
|
|
|
protected $anonCount = 0; |
|
101
|
|
|
|
|
102
|
|
|
/** @var int Number of automated edits to the page. */ |
|
103
|
|
|
protected $automatedCount = 0; |
|
104
|
|
|
|
|
105
|
|
|
/** @var int Number of edits to the page that were reverted with the subsequent edit. */ |
|
106
|
|
|
protected $revertCount = 0; |
|
107
|
|
|
|
|
108
|
|
|
/** @var int[] The "edits per <time>" counts. */ |
|
109
|
|
|
protected $countHistory = [ |
|
110
|
|
|
'day' => 0, |
|
111
|
|
|
'week' => 0, |
|
112
|
|
|
'month' => 0, |
|
113
|
|
|
'year' => 0 |
|
114
|
|
|
]; |
|
115
|
|
|
|
|
116
|
|
|
/** @var string[] List of wikidata and Checkwiki errors. */ |
|
117
|
|
|
protected $bugs; |
|
118
|
|
|
|
|
119
|
|
|
/** |
|
120
|
|
|
* ArticleInfo constructor. |
|
121
|
|
|
* @param Page $page The page to process. |
|
122
|
|
|
* @param Container $container The DI container. |
|
123
|
|
|
*/ |
|
124
|
9 |
|
public function __construct(Page $page, Container $container) |
|
125
|
|
|
{ |
|
126
|
9 |
|
$this->page = $page; |
|
127
|
9 |
|
$this->container = $container; |
|
128
|
9 |
|
} |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* Shorthand to get the page's project. |
|
132
|
|
|
* @return Project |
|
133
|
|
|
* @codeCoverageIgnore |
|
134
|
|
|
*/ |
|
135
|
|
|
public function getProject() |
|
136
|
|
|
{ |
|
137
|
|
|
return $this->page->getProject(); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* Get the number of revisions belonging to the page. |
|
142
|
|
|
* @return int |
|
143
|
|
|
*/ |
|
144
|
4 |
|
public function getNumRevisions() |
|
145
|
|
|
{ |
|
146
|
4 |
|
if (!isset($this->numRevisions)) { |
|
147
|
4 |
|
$this->numRevisions = $this->page->getNumRevisions(); |
|
148
|
|
|
} |
|
149
|
4 |
|
return $this->numRevisions; |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
/** |
|
153
|
|
|
* Get the maximum number of revisions that we should process. |
|
154
|
|
|
* @return int |
|
155
|
|
|
*/ |
|
156
|
3 |
|
public function getMaxRevisions() |
|
157
|
|
|
{ |
|
158
|
3 |
|
if (!isset($this->maxRevisions)) { |
|
159
|
3 |
|
$this->maxRevisions = (int) $this->container->getParameter('app.max_page_revisions'); |
|
160
|
|
|
} |
|
161
|
3 |
|
return $this->maxRevisions; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* Get the number of revisions that are actually getting processed. |
|
166
|
|
|
* This goes by the app.max_page_revisions parameter, or the actual |
|
167
|
|
|
* number of revisions, whichever is smaller. |
|
168
|
|
|
* @return int |
|
169
|
|
|
*/ |
|
170
|
5 |
|
public function getNumRevisionsProcessed() |
|
171
|
|
|
{ |
|
172
|
5 |
|
if (isset($this->numRevisionsProcessed)) { |
|
173
|
3 |
|
return $this->numRevisionsProcessed; |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
2 |
|
if ($this->tooManyRevisions()) { |
|
177
|
1 |
|
$this->numRevisionsProcessed = $this->getMaxRevisions(); |
|
178
|
|
|
} else { |
|
179
|
1 |
|
$this->numRevisionsProcessed = $this->getNumRevisions(); |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
2 |
|
return $this->numRevisionsProcessed; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
/** |
|
186
|
|
|
* Are there more revisions than we should process, based on the config? |
|
187
|
|
|
* @return bool |
|
188
|
|
|
*/ |
|
189
|
3 |
|
public function tooManyRevisions() |
|
190
|
|
|
{ |
|
191
|
3 |
|
return $this->getMaxRevisions() > 0 && $this->getNumRevisions() > $this->getMaxRevisions(); |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
/** |
|
195
|
|
|
* Fetch and store all the data we need to show the ArticleInfo view. |
|
196
|
|
|
* @codeCoverageIgnore |
|
197
|
|
|
*/ |
|
198
|
|
|
public function prepareData() |
|
199
|
|
|
{ |
|
200
|
|
|
$this->parseHistory(); |
|
201
|
|
|
$this->setLogsEvents(); |
|
202
|
|
|
$this->setTopTenCounts(); |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* Get the number of editors that edited the page. |
|
207
|
|
|
* @return int |
|
208
|
|
|
*/ |
|
209
|
1 |
|
public function getNumEditors() |
|
210
|
|
|
{ |
|
211
|
1 |
|
return count($this->editors); |
|
212
|
|
|
} |
|
213
|
|
|
|
|
214
|
|
|
/** |
|
215
|
|
|
* Get the number of bots that edited the page. |
|
216
|
|
|
* @return int |
|
217
|
|
|
*/ |
|
218
|
|
|
public function getNumBots() |
|
219
|
|
|
{ |
|
220
|
|
|
return count($this->getBots()); |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* Get the number of days between the first and last edit. |
|
225
|
|
|
* @return int |
|
226
|
|
|
*/ |
|
227
|
1 |
|
public function getTotalDays() |
|
228
|
|
|
{ |
|
229
|
1 |
|
if (isset($this->totalDays)) { |
|
230
|
1 |
|
return $this->totalDays; |
|
231
|
|
|
} |
|
232
|
1 |
|
$dateFirst = $this->firstEdit->getTimestamp(); |
|
233
|
1 |
|
$dateLast = $this->lastEdit->getTimestamp(); |
|
234
|
1 |
|
$interval = date_diff($dateLast, $dateFirst, true); |
|
235
|
1 |
|
$this->totalDays = $interval->format('%a'); |
|
236
|
1 |
|
return $this->totalDays; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
/** |
|
240
|
|
|
* Get the average number of days between edits to the page. |
|
241
|
|
|
* @return double |
|
242
|
|
|
*/ |
|
243
|
1 |
|
public function averageDaysPerEdit() |
|
244
|
|
|
{ |
|
245
|
1 |
|
return round($this->getTotalDays() / $this->getNumRevisionsProcessed(), 1); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* Get the average number of edits per day to the page. |
|
250
|
|
|
* @return double |
|
251
|
|
|
*/ |
|
252
|
1 |
|
public function editsPerDay() |
|
253
|
|
|
{ |
|
254
|
1 |
|
$editsPerDay = $this->getTotalDays() |
|
255
|
1 |
|
? $this->getNumRevisionsProcessed() / ($this->getTotalDays() / (365 / 12 / 24)) |
|
256
|
1 |
|
: 0; |
|
257
|
1 |
|
return round($editsPerDay, 1); |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* Get the average number of edits per month to the page. |
|
262
|
|
|
* @return double |
|
263
|
|
|
*/ |
|
264
|
1 |
View Code Duplication |
public function editsPerMonth() |
|
|
|
|
|
|
265
|
|
|
{ |
|
266
|
1 |
|
$editsPerMonth = $this->getTotalDays() |
|
267
|
1 |
|
? $this->getNumRevisionsProcessed() / ($this->getTotalDays() / (365 / 12)) |
|
268
|
1 |
|
: 0; |
|
269
|
1 |
|
return min($this->getNumRevisionsProcessed(), round($editsPerMonth, 1)); |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Get the average number of edits per year to the page. |
|
274
|
|
|
* @return double |
|
275
|
|
|
*/ |
|
276
|
1 |
View Code Duplication |
public function editsPerYear() |
|
|
|
|
|
|
277
|
|
|
{ |
|
278
|
1 |
|
$editsPerYear = $this->getTotalDays() |
|
279
|
1 |
|
? $this->getNumRevisionsProcessed() / ($this->getTotalDays() / 365) |
|
280
|
1 |
|
: 0; |
|
281
|
1 |
|
return min($this->getNumRevisionsProcessed(), round($editsPerYear, 1)); |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
/** |
|
285
|
|
|
* Get the average number of edits per editor. |
|
286
|
|
|
* @return double |
|
287
|
|
|
*/ |
|
288
|
1 |
|
public function editsPerEditor() |
|
289
|
|
|
{ |
|
290
|
1 |
|
return round($this->getNumRevisionsProcessed() / count($this->editors), 1); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Get the percentage of minor edits to the page. |
|
295
|
|
|
* @return double |
|
296
|
|
|
*/ |
|
297
|
1 |
|
public function minorPercentage() |
|
298
|
|
|
{ |
|
299
|
1 |
|
return round( |
|
300
|
1 |
|
($this->minorCount / $this->getNumRevisionsProcessed()) * 100, |
|
301
|
1 |
|
1 |
|
302
|
|
|
); |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
/** |
|
306
|
|
|
* Get the percentage of anonymous edits to the page. |
|
307
|
|
|
* @return double |
|
308
|
|
|
*/ |
|
309
|
1 |
|
public function anonPercentage() |
|
310
|
|
|
{ |
|
311
|
1 |
|
return round( |
|
312
|
1 |
|
($this->anonCount / $this->getNumRevisionsProcessed()) * 100, |
|
313
|
1 |
|
1 |
|
314
|
|
|
); |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Get the percentage of edits made by the top 10 editors. |
|
319
|
|
|
* @return double |
|
320
|
|
|
*/ |
|
321
|
1 |
|
public function topTenPercentage() |
|
322
|
|
|
{ |
|
323
|
1 |
|
return round(($this->topTenCount / $this->getNumRevisionsProcessed()) * 100, 1); |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
/** |
|
327
|
|
|
* Get the number of times the page has been viewed in the given timeframe. |
|
328
|
|
|
* @param int $latest Last N days. |
|
329
|
|
|
* @return int |
|
330
|
|
|
*/ |
|
331
|
|
|
public function getPageviews($latest) |
|
332
|
|
|
{ |
|
333
|
|
|
return $this->page->getLastPageviews($latest); |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* Get the page assessments of the page. |
|
338
|
|
|
* @see https://www.mediawiki.org/wiki/Extension:PageAssessments |
|
339
|
|
|
* @return string[]|false False if unsupported. |
|
340
|
|
|
* @codeCoverageIgnore |
|
341
|
|
|
*/ |
|
342
|
|
|
public function getAssessments() |
|
343
|
|
|
{ |
|
344
|
|
|
if (!is_array($this->assessments)) { |
|
345
|
|
|
$this->assessments = $this->page->getAssessments(); |
|
|
|
|
|
|
346
|
|
|
} |
|
347
|
|
|
return $this->assessments; |
|
348
|
|
|
} |
|
349
|
|
|
|
|
350
|
|
|
/** |
|
351
|
|
|
* Get the number of automated edits made to the page. |
|
352
|
|
|
* @return int |
|
353
|
|
|
*/ |
|
354
|
1 |
|
public function getAutomatedCount() |
|
355
|
|
|
{ |
|
356
|
1 |
|
return $this->automatedCount; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Get the number of edits to the page that were reverted with the subsequent edit. |
|
361
|
|
|
* @return int |
|
362
|
|
|
*/ |
|
363
|
1 |
|
public function getRevertCount() |
|
364
|
|
|
{ |
|
365
|
1 |
|
return $this->revertCount; |
|
366
|
|
|
} |
|
367
|
|
|
|
|
368
|
|
|
/** |
|
369
|
|
|
* Get the number of edits to the page made by logged out users. |
|
370
|
|
|
* @return int |
|
371
|
|
|
*/ |
|
372
|
1 |
|
public function getAnonCount() |
|
373
|
|
|
{ |
|
374
|
1 |
|
return $this->anonCount; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* Get the number of minor edits to the page. |
|
379
|
|
|
* @return int |
|
380
|
|
|
*/ |
|
381
|
1 |
|
public function getMinorCount() |
|
382
|
|
|
{ |
|
383
|
1 |
|
return $this->minorCount; |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
/** |
|
387
|
|
|
* Get the number of edits to the page made in the past day, week, month and year. |
|
388
|
|
|
* @return int[] With keys 'day', 'week', 'month' and 'year'. |
|
389
|
|
|
*/ |
|
390
|
|
|
public function getCountHistory() |
|
391
|
|
|
{ |
|
392
|
|
|
return $this->countHistory; |
|
393
|
|
|
} |
|
394
|
|
|
|
|
395
|
|
|
/** |
|
396
|
|
|
* Get the number of edits to the page made by the top 10 editors. |
|
397
|
|
|
* @return int |
|
398
|
|
|
*/ |
|
399
|
1 |
|
public function getTopTenCount() |
|
400
|
|
|
{ |
|
401
|
1 |
|
return $this->topTenCount; |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
/** |
|
405
|
|
|
* Get the first edit to the page. |
|
406
|
|
|
* @return Edit |
|
407
|
|
|
*/ |
|
408
|
|
|
public function getFirstEdit() |
|
409
|
|
|
{ |
|
410
|
|
|
return $this->firstEdit; |
|
411
|
|
|
} |
|
412
|
|
|
|
|
413
|
|
|
/** |
|
414
|
|
|
* Get the last edit to the page. |
|
415
|
|
|
* @return Edit |
|
416
|
|
|
*/ |
|
417
|
1 |
|
public function getLastEdit() |
|
418
|
|
|
{ |
|
419
|
1 |
|
return $this->lastEdit; |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
/** |
|
423
|
|
|
* Get the edit that made the largest addition to the page (by number of bytes). |
|
424
|
|
|
* @return Edit |
|
425
|
|
|
*/ |
|
426
|
1 |
|
public function getMaxAddition() |
|
427
|
|
|
{ |
|
428
|
1 |
|
return $this->maxAddition; |
|
429
|
|
|
} |
|
430
|
|
|
|
|
431
|
|
|
/** |
|
432
|
|
|
* Get the edit that made the largest removal to the page (by number of bytes). |
|
433
|
|
|
* @return Edit |
|
434
|
|
|
*/ |
|
435
|
1 |
|
public function getMaxDeletion() |
|
436
|
|
|
{ |
|
437
|
1 |
|
return $this->maxDeletion; |
|
438
|
|
|
} |
|
439
|
|
|
|
|
440
|
|
|
/** |
|
441
|
|
|
* Get the list of editors to the page, including various statistics. |
|
442
|
|
|
* @return mixed[] |
|
443
|
|
|
*/ |
|
444
|
1 |
|
public function getEditors() |
|
445
|
|
|
{ |
|
446
|
1 |
|
return $this->editors; |
|
447
|
|
|
} |
|
448
|
|
|
|
|
449
|
|
|
/** |
|
450
|
|
|
* Get the list of the top editors to the page (by edits), including various statistics. |
|
451
|
|
|
* @return mixed[] |
|
452
|
|
|
*/ |
|
453
|
1 |
|
public function topTenEditorsByEdits() |
|
454
|
|
|
{ |
|
455
|
1 |
|
return $this->topTenEditorsByEdits; |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
/** |
|
459
|
|
|
* Get the list of the top editors to the page (by added text), including various statistics. |
|
460
|
|
|
* @return mixed[] |
|
461
|
|
|
*/ |
|
462
|
1 |
|
public function topTenEditorsByAdded() |
|
463
|
|
|
{ |
|
464
|
1 |
|
return $this->topTenEditorsByAdded; |
|
465
|
|
|
} |
|
466
|
|
|
|
|
467
|
|
|
/** |
|
468
|
|
|
* Get various counts about each individual year and month of the page's history. |
|
469
|
|
|
* @return mixed[] |
|
470
|
|
|
*/ |
|
471
|
2 |
|
public function getYearMonthCounts() |
|
472
|
|
|
{ |
|
473
|
2 |
|
return $this->yearMonthCounts; |
|
474
|
|
|
} |
|
475
|
|
|
|
|
476
|
|
|
/** |
|
477
|
|
|
* Get the maximum number of edits that were created across all months. This is used as a |
|
478
|
|
|
* comparison for the bar charts in the months section. |
|
479
|
|
|
* @return int |
|
480
|
|
|
*/ |
|
481
|
1 |
|
public function getMaxEditsPerMonth() |
|
482
|
|
|
{ |
|
483
|
1 |
|
return $this->maxEditsPerMonth; |
|
484
|
|
|
} |
|
485
|
|
|
|
|
486
|
|
|
/** |
|
487
|
|
|
* Get a list of (semi-)automated tools that were used to edit the page, including |
|
488
|
|
|
* the number of times they were used, and a link to the tool's homepage. |
|
489
|
|
|
* @return mixed[] |
|
490
|
|
|
*/ |
|
491
|
1 |
|
public function getTools() |
|
492
|
|
|
{ |
|
493
|
1 |
|
return $this->tools; |
|
494
|
|
|
} |
|
495
|
|
|
|
|
496
|
|
|
/** |
|
497
|
|
|
* Get the list of page's wikidata and Checkwiki errors. |
|
498
|
|
|
* @see Page::getErrors() |
|
499
|
|
|
* @return string[] |
|
500
|
|
|
*/ |
|
501
|
|
|
public function getBugs() |
|
502
|
|
|
{ |
|
503
|
|
|
if (!is_array($this->bugs)) { |
|
504
|
|
|
$this->bugs = $this->page->getErrors(); |
|
505
|
|
|
} |
|
506
|
|
|
return $this->bugs; |
|
507
|
|
|
} |
|
508
|
|
|
|
|
509
|
|
|
/** |
|
510
|
|
|
* Get the number of wikidata nad CheckWiki errors. |
|
511
|
|
|
* @return int |
|
512
|
|
|
*/ |
|
513
|
|
|
public function numBugs() |
|
514
|
|
|
{ |
|
515
|
|
|
return count($this->getBugs()); |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
/** |
|
519
|
|
|
* Get the number of external links on the page. |
|
520
|
|
|
* @return int |
|
521
|
|
|
*/ |
|
522
|
1 |
|
public function linksExtCount() |
|
523
|
|
|
{ |
|
524
|
1 |
|
return $this->getLinksAndRedirects()['links_ext_count']; |
|
525
|
|
|
} |
|
526
|
|
|
|
|
527
|
|
|
/** |
|
528
|
|
|
* Get the number of incoming links to the page. |
|
529
|
|
|
* @return int |
|
530
|
|
|
*/ |
|
531
|
1 |
|
public function linksInCount() |
|
532
|
|
|
{ |
|
533
|
1 |
|
return $this->getLinksAndRedirects()['links_in_count']; |
|
534
|
|
|
} |
|
535
|
|
|
|
|
536
|
|
|
/** |
|
537
|
|
|
* Get the number of outgoing links from the page. |
|
538
|
|
|
* @return int |
|
539
|
|
|
*/ |
|
540
|
1 |
|
public function linksOutCount() |
|
541
|
|
|
{ |
|
542
|
1 |
|
return $this->getLinksAndRedirects()['links_out_count']; |
|
543
|
|
|
} |
|
544
|
|
|
|
|
545
|
|
|
/** |
|
546
|
|
|
* Get the number of redirects to the page. |
|
547
|
|
|
* @return int |
|
548
|
|
|
*/ |
|
549
|
1 |
|
public function redirectsCount() |
|
550
|
|
|
{ |
|
551
|
1 |
|
return $this->getLinksAndRedirects()['redirects_count']; |
|
552
|
|
|
} |
|
553
|
|
|
|
|
554
|
|
|
/** |
|
555
|
|
|
* Get the number of external, incoming and outgoing links, along with |
|
556
|
|
|
* the number of redirects to the page. |
|
557
|
|
|
* @return int |
|
558
|
|
|
* @codeCoverageIgnore |
|
559
|
|
|
*/ |
|
560
|
|
|
private function getLinksAndRedirects() |
|
561
|
|
|
{ |
|
562
|
|
|
if (!is_array($this->linksAndRedirects)) { |
|
563
|
|
|
$this->linksAndRedirects = $this->page->countLinksAndRedirects(); |
|
|
|
|
|
|
564
|
|
|
} |
|
565
|
|
|
return $this->linksAndRedirects; |
|
566
|
|
|
} |
|
567
|
|
|
|
|
568
|
|
|
/** |
|
569
|
|
|
* Parse the revision history, collecting our core statistics. |
|
570
|
|
|
* @return mixed[] Associative "master" array of metadata about the page. |
|
571
|
|
|
* |
|
572
|
|
|
* Untestable because it relies on getting a PDO statement. All the important |
|
573
|
|
|
* logic lives in other methods which are tested. |
|
574
|
|
|
* @codeCoverageIgnore |
|
575
|
|
|
*/ |
|
576
|
|
|
private function parseHistory() |
|
577
|
|
|
{ |
|
578
|
|
|
if ($this->tooManyRevisions()) { |
|
579
|
|
|
$limit = $this->getMaxRevisions(); |
|
580
|
|
|
} else { |
|
581
|
|
|
$limit = null; |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
// Third parameter is ignored if $limit is null. |
|
585
|
|
|
$revStmt = $this->page->getRevisionsStmt(null, $limit, $this->getNumRevisions()); |
|
586
|
|
|
$revCount = 0; |
|
587
|
|
|
|
|
588
|
|
|
/** |
|
589
|
|
|
* Data about previous edits so that we can use them as a basis for comparison. |
|
590
|
|
|
* @var Edit[] |
|
591
|
|
|
*/ |
|
592
|
|
|
$prevEdits = [ |
|
593
|
|
|
// The previous Edit, used to discount content that was reverted. |
|
594
|
|
|
'prev' => null, |
|
595
|
|
|
|
|
596
|
|
|
// The last edit deemed to be the max addition of content. This is kept track of |
|
597
|
|
|
// in case we find out the next edit was reverted (and was also a max edit), |
|
598
|
|
|
// in which case we'll want to discount it and use this one instead. |
|
599
|
|
|
'maxAddition' => null, |
|
600
|
|
|
|
|
601
|
|
|
// Same as with maxAddition, except the maximum amount of content deleted. |
|
602
|
|
|
// This is used to discount content that was reverted. |
|
603
|
|
|
'maxDeletion' => null, |
|
604
|
|
|
]; |
|
605
|
|
|
|
|
606
|
|
|
while ($rev = $revStmt->fetch()) { |
|
607
|
|
|
$edit = new Edit($this->page, $rev); |
|
608
|
|
|
|
|
609
|
|
|
if ($revCount === 0) { |
|
610
|
|
|
$this->firstEdit = $edit; |
|
611
|
|
|
} |
|
612
|
|
|
|
|
613
|
|
|
// Sometimes, with old revisions (2001 era), the revisions from 2002 come before 2001 |
|
614
|
|
|
if ($edit->getTimestamp() < $this->firstEdit->getTimestamp()) { |
|
615
|
|
|
$this->firstEdit = $edit; |
|
616
|
|
|
} |
|
617
|
|
|
|
|
618
|
|
|
$prevEdits = $this->updateCounts($edit, $prevEdits); |
|
|
|
|
|
|
619
|
|
|
|
|
620
|
|
|
$revCount++; |
|
621
|
|
|
} |
|
622
|
|
|
|
|
623
|
|
|
$this->numRevisionsProcessed = $revCount; |
|
624
|
|
|
|
|
625
|
|
|
// Various sorts |
|
626
|
|
|
arsort($this->editors); |
|
627
|
|
|
ksort($this->yearMonthCounts); |
|
628
|
|
|
if ($this->tools) { |
|
|
|
|
|
|
629
|
|
|
arsort($this->tools); |
|
630
|
|
|
} |
|
631
|
|
|
} |
|
632
|
|
|
|
|
633
|
|
|
/** |
|
634
|
|
|
* Update various counts based on the current edit. |
|
635
|
|
|
* @param Edit $edit |
|
636
|
|
|
* @param Edit[] $prevEdits With 'prev', 'maxAddition' and 'maxDeletion' |
|
637
|
|
|
* @return Edit[] Updated version of $prevEdits. |
|
638
|
|
|
*/ |
|
639
|
3 |
|
private function updateCounts(Edit $edit, $prevEdits) |
|
640
|
|
|
{ |
|
641
|
|
|
// Update the counts for the year and month of the current edit. |
|
642
|
3 |
|
$this->updateYearMonthCounts($edit); |
|
643
|
|
|
|
|
644
|
|
|
// Update counts for the user who made the edit. |
|
645
|
3 |
|
$this->updateUserCounts($edit); |
|
646
|
|
|
|
|
647
|
|
|
// Update the year/month/user counts of anon and minor edits. |
|
648
|
3 |
|
$this->updateAnonMinorCounts($edit); |
|
649
|
|
|
|
|
650
|
|
|
// Update counts for automated tool usage, if applicable. |
|
651
|
3 |
|
$this->updateToolCounts($edit); |
|
652
|
|
|
|
|
653
|
|
|
// Increment "edits per <time>" counts |
|
654
|
3 |
|
$this->updateCountHistory($edit); |
|
655
|
|
|
|
|
656
|
|
|
// Update figures regarding content addition/removal, and the revert count. |
|
657
|
3 |
|
$prevEdits = $this->updateContentSizes($edit, $prevEdits); |
|
658
|
|
|
|
|
659
|
|
|
// Now that we've updated all the counts, we can reset |
|
660
|
|
|
// the prev and last edits, which are used for tracking. |
|
661
|
3 |
|
$prevEdits['prev'] = $edit; |
|
662
|
3 |
|
$this->lastEdit = $edit; |
|
663
|
|
|
|
|
664
|
3 |
|
return $prevEdits; |
|
665
|
|
|
} |
|
666
|
|
|
|
|
667
|
|
|
/** |
|
668
|
|
|
* Update various figures about content sizes based on the given edit. |
|
669
|
|
|
* @param Edit $edit |
|
670
|
|
|
* @param Edit[] $prevEdits With 'prev', 'maxAddition' and 'maxDeletion' |
|
671
|
|
|
* @return Edit[] Updated version of $prevEdits. |
|
672
|
|
|
*/ |
|
673
|
3 |
|
private function updateContentSizes(Edit $edit, $prevEdits) |
|
674
|
|
|
{ |
|
675
|
|
|
// Check if it was a revert |
|
676
|
3 |
|
if ($edit->isRevert($this->container)) { |
|
677
|
3 |
|
return $this->updateContentSizesRevert($prevEdits); |
|
678
|
|
|
} else { |
|
679
|
3 |
|
return $this->updateContentSizesNonRevert($edit, $prevEdits); |
|
680
|
|
|
} |
|
681
|
|
|
} |
|
682
|
|
|
|
|
683
|
|
|
/** |
|
684
|
|
|
* Updates the figures on content sizes assuming the given edit was a revert of the previous one. |
|
685
|
|
|
* In such a case, we don't want to treat the previous edit as legit content addition or removal. |
|
686
|
|
|
* @param Edit[] $prevEdits With 'prev', 'maxAddition' and 'maxDeletion'. |
|
687
|
|
|
* @return Edit[] Updated version of $prevEdits, for tracking. |
|
688
|
|
|
*/ |
|
689
|
3 |
|
private function updateContentSizesRevert($prevEdits) |
|
690
|
|
|
{ |
|
691
|
3 |
|
$this->revertCount++; |
|
692
|
|
|
|
|
693
|
|
|
// Adjust addedBytes given this edit was a revert of the previous one. |
|
694
|
3 |
|
if ($prevEdits['prev'] && $prevEdits['prev']->getSize() > 0) { |
|
695
|
|
|
$this->addedBytes -= $prevEdits['prev']->getSize(); |
|
696
|
|
|
} |
|
697
|
|
|
|
|
698
|
|
|
// @TODO: Test this against an edit war (use your sandbox). |
|
699
|
|
|
// Also remove as max added or deleted, if applicable. |
|
700
|
3 |
|
if ($this->maxAddition && $prevEdits['prev']->getId() === $this->maxAddition->getId()) { |
|
701
|
|
|
$this->maxAddition = $prevEdits['maxAddition']; |
|
702
|
|
|
$prevEdits['maxAddition'] = $prevEdits['prev']; // in the event of edit wars |
|
703
|
3 |
|
} elseif ($this->maxDeletion && $prevEdits['prev']->getId() === $this->maxDeletion->getId()) { |
|
704
|
3 |
|
$this->maxDeletion = $prevEdits['maxDeletion']; |
|
705
|
3 |
|
$prevEdits['maxDeletion'] = $prevEdits['prev']; // in the event of edit wars |
|
706
|
|
|
} |
|
707
|
|
|
|
|
708
|
3 |
|
return $prevEdits; |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
/** |
|
712
|
|
|
* Updates the figures on content sizes assuming the given edit |
|
713
|
|
|
* was NOT a revert of the previous edit. |
|
714
|
|
|
* @param Edit $edit |
|
715
|
|
|
* @param Edit[] $prevEdits With 'prev', 'maxAddition' and 'maxDeletion'. |
|
716
|
|
|
* @return Edit[] Updated version of $prevEdits, for tracking. |
|
717
|
|
|
*/ |
|
718
|
3 |
|
private function updateContentSizesNonRevert(Edit $edit, $prevEdits) |
|
719
|
|
|
{ |
|
720
|
3 |
|
$editSize = $this->getEditSize($edit, $prevEdits); |
|
721
|
|
|
|
|
722
|
|
|
// Edit was not a revert, so treat size > 0 as content added. |
|
723
|
3 |
|
if ($editSize > 0) { |
|
724
|
3 |
|
$this->addedBytes += $editSize; |
|
725
|
3 |
|
$this->editors[$edit->getUser()->getUsername()]['added'] += $editSize; |
|
726
|
|
|
|
|
727
|
|
|
// Keep track of edit with max addition. |
|
728
|
3 |
|
if (!$this->maxAddition || $editSize > $this->maxAddition->getSize()) { |
|
729
|
|
|
// Keep track of old maxAddition in case we find out the next $edit was reverted |
|
730
|
|
|
// (and was also a max edit), in which case we'll want to use this one ($edit). |
|
731
|
3 |
|
$prevEdits['maxAddition'] = $this->maxAddition; |
|
732
|
|
|
|
|
733
|
3 |
|
$this->maxAddition = $edit; |
|
734
|
|
|
} |
|
735
|
3 |
|
} elseif ($editSize < 0 && (!$this->maxDeletion || $editSize < $this->maxDeletion->getSize())) { |
|
736
|
|
|
// Keep track of old maxDeletion in case we find out the next edit was reverted |
|
737
|
|
|
// (and was also a max deletion), in which case we'll want to use this one. |
|
738
|
3 |
|
$prevEdits['maxDeletion'] = $this->maxDeletion; |
|
739
|
|
|
|
|
740
|
3 |
|
$this->maxDeletion = $edit; |
|
741
|
|
|
} |
|
742
|
|
|
|
|
743
|
3 |
|
return $prevEdits; |
|
744
|
|
|
} |
|
745
|
|
|
|
|
746
|
|
|
/** |
|
747
|
|
|
* Get the size of the given edit, based on the previous edit (if present). |
|
748
|
|
|
* We also don't return the actual edit size if last revision had a length of null. |
|
749
|
|
|
* This happens when the edit follows other edits that were revision-deleted. |
|
750
|
|
|
* @see T148857 for more information. |
|
751
|
|
|
* @todo Remove once T101631 is resolved. |
|
752
|
|
|
* @param Edit $edit |
|
753
|
|
|
* @param Edit[] $prevEdits With 'prev', 'maxAddition' and 'maxDeletion'. |
|
754
|
|
|
* @return Edit[] Updated version of $prevEdits, for tracking. |
|
755
|
|
|
*/ |
|
756
|
3 |
|
private function getEditSize(Edit $edit, $prevEdits) |
|
757
|
|
|
{ |
|
758
|
3 |
|
if ($prevEdits['prev'] && $prevEdits['prev']->getLength() === null) { |
|
759
|
|
|
return 0; |
|
760
|
|
|
} else { |
|
761
|
3 |
|
return $edit->getSize(); |
|
762
|
|
|
} |
|
763
|
|
|
} |
|
764
|
|
|
|
|
765
|
|
|
/** |
|
766
|
|
|
* Update counts of automated tool usage for the given edit. |
|
767
|
|
|
* @param Edit $edit |
|
768
|
|
|
*/ |
|
769
|
3 |
|
private function updateToolCounts(Edit $edit) |
|
770
|
|
|
{ |
|
771
|
3 |
|
$automatedTool = $edit->getTool($this->container); |
|
772
|
|
|
|
|
773
|
3 |
|
if ($automatedTool === false) { |
|
774
|
|
|
// Nothing to do. |
|
775
|
3 |
|
return; |
|
776
|
|
|
} |
|
777
|
|
|
|
|
778
|
3 |
|
$editYear = $edit->getYear(); |
|
779
|
3 |
|
$editMonth = $edit->getMonth(); |
|
780
|
|
|
|
|
781
|
3 |
|
$this->automatedCount++; |
|
782
|
3 |
|
$this->yearMonthCounts[$editYear]['automated']++; |
|
783
|
3 |
|
$this->yearMonthCounts[$editYear]['months'][$editMonth]['automated']++; |
|
784
|
|
|
|
|
785
|
3 |
|
if (!isset($this->tools[$automatedTool['name']])) { |
|
786
|
3 |
|
$this->tools[$automatedTool['name']] = [ |
|
787
|
3 |
|
'count' => 1, |
|
788
|
3 |
|
'link' => $automatedTool['link'], |
|
789
|
|
|
]; |
|
790
|
|
|
} else { |
|
791
|
|
|
$this->tools[$automatedTool['name']]['count']++; |
|
792
|
|
|
} |
|
793
|
3 |
|
} |
|
794
|
|
|
|
|
795
|
|
|
/** |
|
796
|
|
|
* Update various counts for the year and month of the given edit. |
|
797
|
|
|
* @param Edit $edit |
|
798
|
|
|
*/ |
|
799
|
3 |
|
private function updateYearMonthCounts(Edit $edit) |
|
800
|
|
|
{ |
|
801
|
3 |
|
$editYear = $edit->getYear(); |
|
802
|
3 |
|
$editMonth = $edit->getMonth(); |
|
803
|
|
|
|
|
804
|
|
|
// Fill in the blank arrays for the year and 12 months if needed. |
|
805
|
3 |
|
if (!isset($this->yearMonthCounts[$editYear])) { |
|
806
|
3 |
|
$this->addYearMonthCountEntry($edit); |
|
807
|
|
|
} |
|
808
|
|
|
|
|
809
|
|
|
// Increment year and month counts for all edits |
|
810
|
3 |
|
$this->yearMonthCounts[$editYear]['all']++; |
|
811
|
3 |
|
$this->yearMonthCounts[$editYear]['months'][$editMonth]['all']++; |
|
812
|
|
|
// This will ultimately be the size of the page by the end of the year |
|
813
|
3 |
|
$this->yearMonthCounts[$editYear]['size'] = (int) $edit->getLength(); |
|
814
|
|
|
|
|
815
|
|
|
// Keep track of which month had the most edits |
|
816
|
3 |
|
$editsThisMonth = $this->yearMonthCounts[$editYear]['months'][$editMonth]['all']; |
|
817
|
3 |
|
if ($editsThisMonth > $this->maxEditsPerMonth) { |
|
818
|
3 |
|
$this->maxEditsPerMonth = $editsThisMonth; |
|
819
|
|
|
} |
|
820
|
3 |
|
} |
|
821
|
|
|
|
|
822
|
|
|
/** |
|
823
|
|
|
* Add a new entry to $this->yearMonthCounts for the given year, |
|
824
|
|
|
* with blank values for each month. This called during self::parseHistory(). |
|
825
|
|
|
* @param Edit $edit |
|
826
|
|
|
*/ |
|
827
|
3 |
|
private function addYearMonthCountEntry(Edit $edit) |
|
828
|
|
|
{ |
|
829
|
3 |
|
$editYear = $edit->getYear(); |
|
830
|
|
|
|
|
831
|
|
|
// Beginning of the month at 00:00:00. |
|
832
|
3 |
|
$firstEditTime = mktime(0, 0, 0, (int) $this->firstEdit->getMonth(), 1, $this->firstEdit->getYear()); |
|
833
|
|
|
|
|
834
|
3 |
|
$this->yearMonthCounts[$editYear] = [ |
|
835
|
|
|
'all' => 0, |
|
836
|
|
|
'minor' => 0, |
|
837
|
|
|
'anon' => 0, |
|
838
|
|
|
'automated' => 0, |
|
839
|
|
|
'size' => 0, // Keep track of the size by the end of the year. |
|
840
|
|
|
'events' => [], |
|
841
|
|
|
'months' => [], |
|
842
|
|
|
]; |
|
843
|
|
|
|
|
844
|
3 |
|
for ($i = 1; $i <= 12; $i++) { |
|
845
|
3 |
|
$timeObj = mktime(0, 0, 0, $i, 1, $editYear); |
|
846
|
|
|
|
|
847
|
|
|
// Don't show zeros for months before the first edit or after the current month. |
|
848
|
3 |
|
if ($timeObj < $firstEditTime || $timeObj > strtotime('last day of this month')) { |
|
849
|
3 |
|
continue; |
|
850
|
|
|
} |
|
851
|
|
|
|
|
852
|
3 |
|
$this->yearMonthCounts[$editYear]['months'][sprintf('%02d', $i)] = [ |
|
853
|
|
|
'all' => 0, |
|
854
|
|
|
'minor' => 0, |
|
855
|
|
|
'anon' => 0, |
|
856
|
|
|
'automated' => 0, |
|
857
|
|
|
]; |
|
858
|
|
|
} |
|
859
|
3 |
|
} |
|
860
|
|
|
|
|
861
|
|
|
/** |
|
862
|
|
|
* Update the counts of anon and minor edits for year, month, |
|
863
|
|
|
* and user of the given edit. |
|
864
|
|
|
* @param Edit $edit |
|
865
|
|
|
*/ |
|
866
|
3 |
|
private function updateAnonMinorCounts(Edit $edit) |
|
867
|
|
|
{ |
|
868
|
3 |
|
$editYear = $edit->getYear(); |
|
869
|
3 |
|
$editMonth = $edit->getMonth(); |
|
870
|
|
|
|
|
871
|
|
|
// If anonymous, increase counts |
|
872
|
3 |
|
if ($edit->isAnon()) { |
|
873
|
3 |
|
$this->anonCount++; |
|
874
|
3 |
|
$this->yearMonthCounts[$editYear]['anon']++; |
|
875
|
3 |
|
$this->yearMonthCounts[$editYear]['months'][$editMonth]['anon']++; |
|
876
|
|
|
} |
|
877
|
|
|
|
|
878
|
|
|
// If minor edit, increase counts |
|
879
|
3 |
|
if ($edit->isMinor()) { |
|
880
|
3 |
|
$this->minorCount++; |
|
881
|
3 |
|
$this->yearMonthCounts[$editYear]['minor']++; |
|
882
|
3 |
|
$this->yearMonthCounts[$editYear]['months'][$editMonth]['minor']++; |
|
883
|
|
|
} |
|
884
|
3 |
|
} |
|
885
|
|
|
|
|
886
|
|
|
/** |
|
887
|
|
|
* Update various counts for the user of the given edit. |
|
888
|
|
|
* @param Edit $edit |
|
889
|
|
|
*/ |
|
890
|
3 |
|
private function updateUserCounts(Edit $edit) |
|
891
|
|
|
{ |
|
892
|
3 |
|
$username = $edit->getUser()->getUsername(); |
|
893
|
|
|
|
|
894
|
|
|
// Initialize various user stats if needed. |
|
895
|
3 |
|
if (!isset($this->editors[$username])) { |
|
896
|
3 |
|
$this->editors[$username] = [ |
|
897
|
3 |
|
'all' => 0, |
|
898
|
3 |
|
'minor' => 0, |
|
899
|
3 |
|
'minorPercentage' => 0, |
|
900
|
3 |
|
'first' => $edit->getTimestamp(), |
|
901
|
3 |
|
'firstId' => $edit->getId(), |
|
902
|
|
|
'last' => null, |
|
903
|
|
|
'atbe' => null, |
|
904
|
3 |
|
'added' => 0, |
|
905
|
|
|
'sizes' => [], |
|
906
|
|
|
]; |
|
907
|
|
|
} |
|
908
|
|
|
|
|
909
|
|
|
// Increment user counts |
|
910
|
3 |
|
$this->editors[$username]['all']++; |
|
911
|
3 |
|
$this->editors[$username]['last'] = $edit->getTimestamp(); |
|
912
|
3 |
|
$this->editors[$username]['lastId'] = $edit->getId(); |
|
913
|
|
|
|
|
914
|
|
|
// Store number of KB added with this edit |
|
915
|
3 |
|
$this->editors[$username]['sizes'][] = $edit->getLength() / 1024; |
|
916
|
|
|
|
|
917
|
|
|
// Increment minor counts for this user |
|
918
|
3 |
|
if ($edit->isMinor()) { |
|
919
|
3 |
|
$this->editors[$username]['minor']++; |
|
920
|
|
|
} |
|
921
|
3 |
|
} |
|
922
|
|
|
|
|
923
|
|
|
/** |
|
924
|
|
|
* Increment "edits per <time>" counts based on the given edit. |
|
925
|
|
|
* @param Edit $edit |
|
926
|
|
|
*/ |
|
927
|
3 |
|
private function updateCountHistory(Edit $edit) |
|
928
|
|
|
{ |
|
929
|
3 |
|
$editTimestamp = $edit->getTimestamp(); |
|
930
|
|
|
|
|
931
|
3 |
|
if ($editTimestamp > new DateTime('-1 day')) { |
|
932
|
|
|
$this->countHistory['day']++; |
|
933
|
|
|
} |
|
934
|
3 |
|
if ($editTimestamp > new DateTime('-1 week')) { |
|
935
|
|
|
$this->countHistory['week']++; |
|
936
|
|
|
} |
|
937
|
3 |
|
if ($editTimestamp > new DateTime('-1 month')) { |
|
938
|
|
|
$this->countHistory['month']++; |
|
939
|
|
|
} |
|
940
|
3 |
|
if ($editTimestamp > new DateTime('-1 year')) { |
|
941
|
|
|
$this->countHistory['year']++; |
|
942
|
|
|
} |
|
943
|
3 |
|
} |
|
944
|
|
|
|
|
945
|
|
|
/** |
|
946
|
|
|
* Get info about bots that edited the page. |
|
947
|
|
|
* @return mixed[] Contains the bot's username, edit count to the page, |
|
948
|
|
|
* and whether or not they are currently a bot. |
|
949
|
|
|
*/ |
|
950
|
|
|
public function getBots() |
|
951
|
|
|
{ |
|
952
|
|
|
if (isset($this->bots)) { |
|
953
|
|
|
return $this->bots; |
|
954
|
|
|
} |
|
955
|
|
|
|
|
956
|
|
|
// Parse the botedits |
|
957
|
|
|
$bots = []; |
|
958
|
|
|
$botData = $this->getRepository()->getBotData($this->page); |
|
959
|
|
|
while ($bot = $botData->fetch()) { |
|
960
|
|
|
$bots[$bot['username']] = [ |
|
961
|
|
|
'count' => (int) $bot['count'], |
|
962
|
|
|
'current' => $bot['current'] === 'bot', |
|
963
|
|
|
]; |
|
964
|
|
|
} |
|
965
|
|
|
|
|
966
|
|
|
// Sort by edit count. |
|
967
|
|
|
uasort($bots, function ($a, $b) { |
|
968
|
|
|
return $b['count'] - $a['count']; |
|
969
|
|
|
}); |
|
970
|
|
|
|
|
971
|
|
|
$this->bots = $bots; |
|
972
|
|
|
return $bots; |
|
973
|
|
|
} |
|
974
|
|
|
|
|
975
|
|
|
/** |
|
976
|
|
|
* Number of edits made to the page by current or former bots. |
|
977
|
|
|
* @param string[] $bots Used only in unit tests, where we |
|
978
|
|
|
* supply mock data for the bots that will get processed. |
|
979
|
|
|
* @return int |
|
980
|
|
|
*/ |
|
981
|
1 |
|
public function getBotRevisionCount($bots = null) |
|
982
|
|
|
{ |
|
983
|
1 |
|
if (isset($this->botRevisionCount)) { |
|
984
|
|
|
return $this->botRevisionCount; |
|
985
|
|
|
} |
|
986
|
|
|
|
|
987
|
1 |
|
if ($bots === null) { |
|
988
|
|
|
$bots = $this->getBots(); |
|
989
|
|
|
} |
|
990
|
|
|
|
|
991
|
1 |
|
$count = 0; |
|
992
|
|
|
|
|
993
|
1 |
|
foreach ($bots as $username => $data) { |
|
994
|
1 |
|
$count += $data['count']; |
|
995
|
|
|
} |
|
996
|
|
|
|
|
997
|
1 |
|
$this->botRevisionCount = $count; |
|
998
|
1 |
|
return $count; |
|
999
|
|
|
} |
|
1000
|
|
|
|
|
1001
|
|
|
/** |
|
1002
|
|
|
* Query for log events during each year of the article's history, |
|
1003
|
|
|
* and set the results in $this->yearMonthCounts. |
|
1004
|
|
|
*/ |
|
1005
|
1 |
|
private function setLogsEvents() |
|
1006
|
|
|
{ |
|
1007
|
1 |
|
$logData = $this->getRepository()->getLogEvents($this->page); |
|
|
|
|
|
|
1008
|
|
|
|
|
1009
|
1 |
|
foreach ($logData as $event) { |
|
1010
|
1 |
|
$time = strtotime($event['timestamp']); |
|
1011
|
1 |
|
$year = date('Y', $time); |
|
1012
|
|
|
|
|
1013
|
1 |
|
if (!isset($this->yearMonthCounts[$year])) { |
|
1014
|
|
|
break; |
|
1015
|
|
|
} |
|
1016
|
|
|
|
|
1017
|
1 |
|
$yearEvents = $this->yearMonthCounts[$year]['events']; |
|
1018
|
|
|
|
|
1019
|
|
|
// Convert log type value to i18n key |
|
1020
|
1 |
|
switch ($event['log_type']) { |
|
1021
|
1 |
|
case 'protect': |
|
1022
|
1 |
|
$action = 'protections'; |
|
1023
|
1 |
|
break; |
|
1024
|
1 |
|
case 'delete': |
|
1025
|
1 |
|
$action = 'deletions'; |
|
1026
|
1 |
|
break; |
|
1027
|
|
|
case 'move': |
|
1028
|
|
|
$action = 'moves'; |
|
1029
|
|
|
break; |
|
1030
|
|
|
// count pending-changes protections along with normal protections |
|
1031
|
|
|
case 'stable': |
|
1032
|
|
|
$action = 'protections'; |
|
1033
|
|
|
break; |
|
1034
|
|
|
} |
|
1035
|
|
|
|
|
1036
|
1 |
|
if (empty($yearEvents[$action])) { |
|
1037
|
1 |
|
$yearEvents[$action] = 1; |
|
|
|
|
|
|
1038
|
|
|
} else { |
|
1039
|
|
|
$yearEvents[$action]++; |
|
1040
|
|
|
} |
|
1041
|
|
|
|
|
1042
|
1 |
|
$this->yearMonthCounts[$year]['events'] = $yearEvents; |
|
1043
|
|
|
} |
|
1044
|
1 |
|
} |
|
1045
|
|
|
|
|
1046
|
|
|
/** |
|
1047
|
|
|
* Set statistics about the top 10 editors by added text and number of edits. |
|
1048
|
|
|
* This is ran *after* parseHistory() since we need the grand totals first. |
|
1049
|
|
|
* Various stats are also set for each editor in $this->editors to be used in the charts. |
|
1050
|
|
|
* @return integer Number of edits |
|
1051
|
|
|
*/ |
|
1052
|
3 |
|
private function setTopTenCounts() |
|
1053
|
|
|
{ |
|
1054
|
3 |
|
$topTenCount = $counter = 0; |
|
1055
|
3 |
|
$topTenEditors = []; |
|
1056
|
|
|
|
|
1057
|
3 |
|
foreach ($this->editors as $editor => $info) { |
|
1058
|
|
|
// Count how many users are in the top 10% by number of edits |
|
1059
|
3 |
|
if ($counter < 10) { |
|
1060
|
3 |
|
$topTenCount += $info['all']; |
|
1061
|
3 |
|
$counter++; |
|
1062
|
|
|
|
|
1063
|
|
|
// To be used in the Top Ten charts |
|
1064
|
3 |
|
$topTenEditors[] = [ |
|
1065
|
3 |
|
'label' => $editor, |
|
1066
|
3 |
|
'value' => $info['all'], |
|
1067
|
|
|
'percentage' => ( |
|
1068
|
3 |
|
100 * ($info['all'] / $this->getNumRevisionsProcessed()) |
|
1069
|
|
|
) |
|
1070
|
|
|
]; |
|
1071
|
|
|
} |
|
1072
|
|
|
|
|
1073
|
|
|
// Compute the percentage of minor edits the user made |
|
1074
|
3 |
|
$this->editors[$editor]['minorPercentage'] = $info['all'] |
|
1075
|
3 |
|
? ($info['minor'] / $info['all']) * 100 |
|
1076
|
|
|
: 0; |
|
1077
|
|
|
|
|
1078
|
3 |
|
if ($info['all'] > 1) { |
|
1079
|
|
|
// Number of seconds/days between first and last edit |
|
1080
|
3 |
|
$secs = $info['last']->getTimestamp() - $info['first']->getTimestamp(); |
|
1081
|
3 |
|
$days = $secs / (60 * 60 * 24); |
|
1082
|
|
|
|
|
1083
|
|
|
// Average time between edits (in days) |
|
1084
|
3 |
|
$this->editors[$editor]['atbe'] = $days / $info['all']; |
|
1085
|
|
|
} |
|
1086
|
|
|
|
|
1087
|
3 |
|
if (count($info['sizes'])) { |
|
1088
|
|
|
// Average Total KB divided by number of stored sizes (user's edit count to this page) |
|
1089
|
3 |
|
$this->editors[$editor]['size'] = array_sum($info['sizes']) / count($info['sizes']); |
|
1090
|
|
|
} else { |
|
1091
|
3 |
|
$this->editors[$editor]['size'] = 0; |
|
1092
|
|
|
} |
|
1093
|
|
|
} |
|
1094
|
|
|
|
|
1095
|
3 |
|
$this->topTenEditorsByEdits = $topTenEditors; |
|
1096
|
|
|
|
|
1097
|
|
|
// First sort editors array by the amount of text they added |
|
1098
|
3 |
|
$topTenEditorsByAdded = $this->editors; |
|
1099
|
|
View Code Duplication |
uasort($topTenEditorsByAdded, function ($a, $b) { |
|
|
|
|
|
|
1100
|
3 |
|
if ($a['added'] === $b['added']) { |
|
1101
|
3 |
|
return 0; |
|
1102
|
|
|
} |
|
1103
|
3 |
|
return $a['added'] > $b['added'] ? -1 : 1; |
|
1104
|
3 |
|
}); |
|
1105
|
|
|
|
|
1106
|
|
|
// Then build a new array of top 10 editors by added text, |
|
1107
|
|
|
// in the data structure needed for the chart |
|
1108
|
3 |
|
$this->topTenEditorsByAdded = array_map(function ($editor) { |
|
1109
|
3 |
|
$added = $this->editors[$editor]['added']; |
|
1110
|
|
|
return [ |
|
1111
|
3 |
|
'label' => $editor, |
|
1112
|
3 |
|
'value' => $added, |
|
1113
|
|
|
'percentage' => ( |
|
1114
|
3 |
|
100 * ($added / $this->addedBytes) |
|
1115
|
|
|
) |
|
1116
|
|
|
]; |
|
1117
|
3 |
|
}, array_keys(array_slice($topTenEditorsByAdded, 0, 10))); |
|
1118
|
|
|
|
|
1119
|
3 |
|
$this->topTenCount = $topTenCount; |
|
1120
|
3 |
|
} |
|
1121
|
|
|
} |
|
1122
|
|
|
|
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.