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