|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This file contains only the TopEdits class. |
|
4
|
|
|
*/ |
|
5
|
|
|
|
|
6
|
|
|
namespace Xtools; |
|
7
|
|
|
|
|
8
|
|
|
use DateTime; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* TopEdits returns the top-edited pages by a user. |
|
12
|
|
|
*/ |
|
13
|
|
|
class TopEdits extends Model |
|
14
|
|
|
{ |
|
15
|
|
|
/** @var Project The project. */ |
|
16
|
|
|
protected $project; |
|
17
|
|
|
|
|
18
|
|
|
/** @var User The user. */ |
|
19
|
|
|
protected $user; |
|
20
|
|
|
|
|
21
|
|
|
/** @var Page The page (if applicable). */ |
|
22
|
|
|
protected $page; |
|
23
|
|
|
|
|
24
|
|
|
/** @var string[]|Edit[] Top edits, either to a page or across namespaces. */ |
|
25
|
|
|
protected $topEdits = []; |
|
26
|
|
|
|
|
27
|
|
|
/** @var int Number of rows to fetch. */ |
|
28
|
|
|
protected $limit; |
|
29
|
|
|
|
|
30
|
|
|
/** @var int Which namespace we are querying for. */ |
|
31
|
|
|
protected $namespace; |
|
32
|
|
|
|
|
33
|
|
|
/** @var int Number of bytes added across all top edits. */ |
|
34
|
|
|
protected $totalAdded = 0; |
|
35
|
|
|
|
|
36
|
|
|
/** @var int Number of bytes removed across all top edits. */ |
|
37
|
|
|
protected $totalRemoved = 0; |
|
38
|
|
|
|
|
39
|
|
|
/** @var int Number of top edits marked as minor. */ |
|
40
|
|
|
protected $totalMinor = 0; |
|
41
|
|
|
|
|
42
|
|
|
/** @var int Number of automated top edits. */ |
|
43
|
|
|
protected $totalAutomated = 0; |
|
44
|
|
|
|
|
45
|
|
|
/** @var int Number of reverted top edits. */ |
|
46
|
|
|
protected $totalReverted = 0; |
|
47
|
|
|
|
|
48
|
|
|
const DEFAULT_LIMIT_SINGLE_NAMESPACE = 100; |
|
49
|
|
|
const DEFAULT_LIMIT_ALL_NAMESPACES = 20; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* TopEdits constructor. |
|
53
|
|
|
* @param Project $project |
|
54
|
|
|
* @param User $user |
|
55
|
|
|
* @param Page $page |
|
56
|
|
|
* @param string|int $namespace Namespace ID or 'all'. |
|
57
|
|
|
* @param int $limit Number of rows to fetch. This defaults to |
|
58
|
|
|
* DEFAULT_LIMIT_SINGLE_NAMESPACE if $this->namespace is a single namespace (int), |
|
59
|
|
|
* and DEFAULT_LIMIT_ALL_NAMESPACES if $this->namespace is 'all'. |
|
60
|
|
|
*/ |
|
61
|
4 |
|
public function __construct(Project $project, User $user, Page $page = null, $namespace = 0, $limit = null) |
|
62
|
|
|
{ |
|
63
|
4 |
|
$this->project = $project; |
|
64
|
4 |
|
$this->user = $user; |
|
65
|
4 |
|
$this->page = $page; |
|
66
|
4 |
|
$this->namespace = $namespace === 'all' ? 'all' : (int)$namespace; |
|
|
|
|
|
|
67
|
|
|
|
|
68
|
4 |
|
if ($limit) { |
|
|
|
|
|
|
69
|
3 |
|
$this->limit = $limit; |
|
70
|
|
|
} else { |
|
71
|
2 |
|
$this->limit = $this->namespace === 'all' |
|
72
|
1 |
|
? self::DEFAULT_LIMIT_ALL_NAMESPACES |
|
73
|
2 |
|
: self::DEFAULT_LIMIT_SINGLE_NAMESPACE; |
|
74
|
|
|
} |
|
75
|
4 |
|
} |
|
76
|
|
|
|
|
77
|
|
|
/** |
|
78
|
|
|
* Get the limit set on number of rows to fetch. |
|
79
|
|
|
* @return int |
|
80
|
|
|
*/ |
|
81
|
1 |
|
public function getLimit() |
|
82
|
|
|
{ |
|
83
|
1 |
|
return $this->limit; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Get the namespace set on the instance. |
|
88
|
|
|
* @return int|string Namespace ID or 'all'. |
|
89
|
|
|
*/ |
|
90
|
1 |
|
public function getNamespace() |
|
91
|
|
|
{ |
|
92
|
1 |
|
return $this->namespace; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* Get total number of bytes added. |
|
97
|
|
|
* @return int |
|
98
|
|
|
*/ |
|
99
|
1 |
|
public function getTotalAdded() |
|
100
|
|
|
{ |
|
101
|
1 |
|
return $this->totalAdded; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* Get total number of bytes removed. |
|
106
|
|
|
* @return int |
|
107
|
|
|
*/ |
|
108
|
1 |
|
public function getTotalRemoved() |
|
109
|
|
|
{ |
|
110
|
1 |
|
return $this->totalRemoved; |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* Get total number of edits marked as minor. |
|
115
|
|
|
* @return int |
|
116
|
|
|
*/ |
|
117
|
1 |
|
public function getTotalMinor() |
|
118
|
|
|
{ |
|
119
|
1 |
|
return $this->totalMinor; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Get total number of automated edits. |
|
124
|
|
|
* @return int |
|
125
|
|
|
*/ |
|
126
|
1 |
|
public function getTotalAutomated() |
|
127
|
|
|
{ |
|
128
|
1 |
|
return $this->totalAutomated; |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* Get total number of edits that were reverted. |
|
133
|
|
|
* @return int |
|
134
|
|
|
*/ |
|
135
|
1 |
|
public function getTotalReverted() |
|
136
|
|
|
{ |
|
137
|
1 |
|
return $this->totalReverted; |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* Get the top edits data. |
|
142
|
|
|
* @return string[]|Edit[] |
|
143
|
|
|
*/ |
|
144
|
3 |
|
public function getTopEdits() |
|
145
|
|
|
{ |
|
146
|
3 |
|
return $this->topEdits; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Get the total number of top edits. |
|
151
|
|
|
* @return int |
|
152
|
|
|
*/ |
|
153
|
1 |
|
public function getNumTopEdits() |
|
154
|
|
|
{ |
|
155
|
1 |
|
return count($this->topEdits); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* Get the averate time between edits (in days). |
|
160
|
|
|
* @return double |
|
161
|
|
|
*/ |
|
162
|
1 |
|
public function getAtbe() |
|
163
|
|
|
{ |
|
164
|
1 |
|
$firstDateTime = $this->topEdits[0]->getTimestamp(); |
|
165
|
1 |
|
$lastDateTime = end($this->topEdits)->getTimestamp(); |
|
166
|
1 |
|
$secs = $firstDateTime->getTimestamp() - $lastDateTime->getTimestamp(); |
|
167
|
1 |
|
$days = $secs / (60 * 60 * 24); |
|
168
|
1 |
|
return $days / count($this->topEdits); |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
/** |
|
172
|
|
|
* Get the Page that's set on the TopEdits instance. |
|
173
|
|
|
* @return Page $page |
|
174
|
|
|
*/ |
|
175
|
1 |
|
public function getPage() |
|
176
|
|
|
{ |
|
177
|
1 |
|
return $this->page; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* Set the Page on the TopEdits instance. |
|
182
|
|
|
* @param Page $page |
|
183
|
|
|
*/ |
|
184
|
1 |
|
public function setPage(Page $page) |
|
185
|
|
|
{ |
|
186
|
1 |
|
$this->page = $page; |
|
187
|
1 |
|
} |
|
188
|
|
|
|
|
189
|
|
|
/** |
|
190
|
|
|
* Fetch and store all the data we need to show the TopEdits view. |
|
191
|
|
|
* This is the public method that should be called before using |
|
192
|
|
|
* the getter methods. |
|
193
|
|
|
* @param bool $format Whether to format the results, including stats for |
|
194
|
|
|
* number of reverts, etc. This is set to false for the API endpoint. |
|
195
|
|
|
*/ |
|
196
|
3 |
|
public function prepareData($format = true) |
|
197
|
|
|
{ |
|
198
|
3 |
|
if (isset($this->page)) { |
|
199
|
1 |
|
$this->topEdits = $this->getTopEditsPage($format); |
|
200
|
|
|
} else { |
|
201
|
2 |
|
$this->topEdits = $this->getTopEditsNamespace($format); |
|
202
|
|
|
} |
|
203
|
3 |
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* Get the top edits by a user in the given namespace, or 'all' namespaces. |
|
207
|
|
|
* @param bool $format Whether to format the results, including stats for |
|
208
|
|
|
* number of reverts, etc. This is set to false for the API endpoint. |
|
209
|
|
|
* @return string[] Results keyed by namespace. |
|
210
|
|
|
*/ |
|
211
|
2 |
|
private function getTopEditsNamespace($format) |
|
212
|
|
|
{ |
|
213
|
2 |
|
if ($this->namespace === 'all') { |
|
|
|
|
|
|
214
|
1 |
|
$pages = $this->getRepository()->getTopEditsAllNamespaces( |
|
215
|
1 |
|
$this->project, |
|
216
|
1 |
|
$this->user, |
|
217
|
1 |
|
$this->limit |
|
218
|
|
|
); |
|
219
|
|
|
} else { |
|
220
|
1 |
|
$pages = $this->getRepository()->getTopEditsNamespace( |
|
221
|
1 |
|
$this->project, |
|
222
|
1 |
|
$this->user, |
|
223
|
1 |
|
$this->namespace, |
|
224
|
1 |
|
$this->limit |
|
225
|
|
|
); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
2 |
|
if ($format) { |
|
229
|
2 |
|
return $this->formatTopPagesNamespace($pages); |
|
230
|
|
|
} else { |
|
231
|
|
|
return $pages; |
|
232
|
|
|
} |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
/** |
|
236
|
|
|
* Get the top edits to the given page. |
|
237
|
|
|
* @param bool $format Whether to format the results, including stats for |
|
238
|
|
|
* number of reverts, etc. This is set to false for the API endpoint. |
|
239
|
|
|
* @return Edit[] |
|
240
|
|
|
*/ |
|
241
|
1 |
|
private function getTopEditsPage($format = true) |
|
242
|
|
|
{ |
|
243
|
1 |
|
$revs = $this->getRepository()->getTopEditsPage( |
|
244
|
1 |
|
$this->page, |
|
245
|
1 |
|
$this->user |
|
246
|
|
|
); |
|
247
|
|
|
|
|
248
|
1 |
|
if ($format) { |
|
249
|
1 |
|
return $this->formatTopEditsPage($revs); |
|
250
|
|
|
} else { |
|
251
|
|
|
return $revs; |
|
252
|
|
|
} |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/** |
|
256
|
|
|
* Format the results for top edits to a single page. This method also computes |
|
257
|
|
|
* totals for added/removed text, automated and reverted edits. |
|
258
|
|
|
* @param string[] $revs As returned by TopEditsRepository::getTopEditsPage. |
|
259
|
|
|
* @return Edit[] |
|
260
|
|
|
*/ |
|
261
|
1 |
|
private function formatTopEditsPage($revs) |
|
262
|
|
|
{ |
|
263
|
1 |
|
$edits = []; |
|
264
|
|
|
|
|
265
|
1 |
|
$aeh = $this->getRepository() |
|
266
|
1 |
|
->getContainer() |
|
267
|
1 |
|
->get('app.automated_edits_helper'); |
|
268
|
|
|
|
|
269
|
1 |
|
foreach ($revs as $revision) { |
|
270
|
|
|
// Check if the edit was reverted based on the edit summary of the following edit. |
|
271
|
|
|
// If so, update $revision so that when an Edit is instantiated, it will |
|
272
|
|
|
// have the 'reverted' option set. |
|
273
|
1 |
|
if ($aeh->isRevert($revision['parent_comment'], $this->project)) { |
|
274
|
1 |
|
$revision['reverted'] = 1; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
1 |
|
$edit = $this->getEditAndIncrementCounts($revision); |
|
|
|
|
|
|
278
|
|
|
|
|
279
|
1 |
|
$edits[] = $edit; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
1 |
|
return $edits; |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* Create an Edit instance for the given revision, and increment running totals. |
|
287
|
|
|
* This is used by self::formatTopEditsPage(). |
|
288
|
|
|
* @param string[] $revision Revision row as retrieved from the database. |
|
289
|
|
|
* @return Edit |
|
290
|
|
|
*/ |
|
291
|
1 |
|
private function getEditAndIncrementCounts($revision) |
|
292
|
|
|
{ |
|
293
|
1 |
|
$edit = new Edit($this->page, $revision); |
|
294
|
|
|
|
|
295
|
1 |
|
if ($edit->isAutomated($this->getRepository()->getContainer())) { |
|
296
|
1 |
|
$this->totalAutomated++; |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
1 |
|
if ($edit->isMinor()) { |
|
300
|
1 |
|
$this->totalMinor++; |
|
301
|
|
|
} |
|
302
|
|
|
|
|
303
|
1 |
|
if ($edit->isReverted()) { |
|
304
|
1 |
|
$this->totalReverted++; |
|
305
|
|
|
} else { |
|
306
|
|
|
// Length changes don't count if they were reverted. |
|
307
|
1 |
|
if ($revision['length_change'] > 0) { |
|
308
|
1 |
|
$this->totalAdded += $revision['length_change']; |
|
309
|
|
|
} else { |
|
310
|
1 |
|
$this->totalRemoved += $revision['length_change']; |
|
311
|
|
|
} |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
1 |
|
return $edit; |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Format the results returned from the database. |
|
319
|
|
|
* @param string[] $pages As returned by TopEditsRepository::getTopEditsNamespace |
|
320
|
|
|
* or TopEditsRepository::getTopEditsAllNamespaces. |
|
321
|
|
|
* @return string[] Same as input but with 'displaytitle' and 'page_title_ns'. |
|
322
|
|
|
*/ |
|
323
|
2 |
|
private function formatTopPagesNamespace($pages) |
|
324
|
|
|
{ |
|
325
|
|
|
/** @var string[] The top edited pages, keyed by namespace ID. */ |
|
326
|
2 |
|
$topEditedPages = []; |
|
327
|
|
|
|
|
328
|
|
|
/** @var string[] Display titles of the pages, which need to be fetched ahead of time. */ |
|
329
|
2 |
|
$displayTitles = $this->getDisplayTitles($pages); |
|
330
|
|
|
|
|
331
|
2 |
|
foreach ($pages as $page) { |
|
332
|
2 |
|
$nsId = (int) $page['page_namespace']; |
|
333
|
2 |
|
$nsTitle = $nsId > 0 ? $this->project->getNamespaces()[$page['page_namespace']] . ':' : ''; |
|
334
|
2 |
|
$pageTitle = $nsTitle . $page['page_title']; |
|
335
|
2 |
|
$page['displaytitle'] = $displayTitles[$pageTitle]; |
|
336
|
|
|
|
|
337
|
|
|
// $page['page_title'] is retained without the namespace |
|
338
|
|
|
// so we can link to TopEdits for that page. |
|
339
|
2 |
|
$page['page_title_ns'] = $pageTitle; |
|
340
|
|
|
|
|
341
|
2 |
|
if (isset($topEditedPages[$nsId])) { |
|
342
|
2 |
|
$topEditedPages[$nsId][] = $page; |
|
343
|
|
|
} else { |
|
344
|
2 |
|
$topEditedPages[$nsId] = [$page]; |
|
345
|
|
|
} |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
2 |
|
return $topEditedPages; |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Get the display titles of the given pages. |
|
353
|
|
|
* @param string[] $topPages As returned by $this->getRepository()->getTopEdits() |
|
354
|
|
|
* @return string[] Keys are the original supplied titles, and values are the display titles. |
|
355
|
|
|
*/ |
|
356
|
2 |
|
private function getDisplayTitles($topPages) |
|
357
|
|
|
{ |
|
358
|
2 |
|
$namespaces = $this->project->getNamespaces(); |
|
359
|
|
|
|
|
360
|
|
|
// First extract page titles including namespace. |
|
361
|
2 |
|
$pageTitles = array_map(function ($page) use ($namespaces) { |
|
362
|
|
|
// If non-mainspace, prepend namespace to the titles. |
|
363
|
2 |
|
$ns = $page['page_namespace']; |
|
364
|
2 |
|
$nsTitle = $ns > 0 ? $namespaces[$page['page_namespace']] . ':' : ''; |
|
365
|
2 |
|
return $nsTitle . $page['page_title']; |
|
366
|
2 |
|
}, $topPages); |
|
367
|
|
|
|
|
368
|
2 |
|
return $this->getRepository()->getDisplayTitles($this->project, $pageTitles); |
|
369
|
|
|
} |
|
370
|
|
|
} |
|
371
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.