TopEdits::setPage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Model;
6
7
use App\Helper\AutomatedEditsHelper;
8
use App\Repository\TopEditsRepository;
9
10
/**
11
 * TopEdits returns the top-edited pages by a user.
12
 */
13
class TopEdits extends Model
14
{
15
    protected AutomatedEditsHelper $autoEditsHelper;
16
17
    /** @var string[]|Edit[] Top edits, either to a page or across namespaces. */
18
    protected array $topEdits = [];
19
20
    /** @var int Number of bytes added across all top edits. */
21
    protected int $totalAdded = 0;
22
23
    /** @var int Number of bytes removed across all top edits. */
24
    protected int $totalRemoved = 0;
25
26
    /** @var int Number of top edits marked as minor. */
27
    protected int $totalMinor = 0;
28
29
    /** @var int Number of automated top edits. */
30
    protected int $totalAutomated = 0;
31
32
    /** @var int Number of reverted top edits. */
33
    protected int $totalReverted = 0;
34
35
    /** @var int Which page of results to show. */
36
    protected int $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 TopEditsRepository $repository
44
     * @param AutomatedEditsHelper $autoEditsHelper
45
     * @param Project $project
46
     * @param User $user
47
     * @param Page|null $page
48
     * @param string|int $namespace Namespace ID or 'all'.
49
     * @param int|false $start Start date as Unix timestamp.
50
     * @param int|false $end End date as Unix timestamp.
51
     * @param int|null $limit Number of rows to fetch. This defaults to DEFAULT_LIMIT_SINGLE_NAMESPACE if
52
     *   $this->namespace is a single namespace (int), and DEFAULT_LIMIT_ALL_NAMESPACES if $this->namespace is 'all'.
53
     * @param int $pagination Which page of results to show.
54
     */
55
    public function __construct(
56
        TopEditsRepository $repository,
57
        AutomatedEditsHelper $autoEditsHelper,
58
        Project $project,
59
        User $user,
60
        ?Page $page = null,
61
        $namespace = 0,
62
        $start = false,
63
        $end = false,
64
        ?int $limit = null,
65
        int $pagination = 0
66
    ) {
67
        $this->repository = $repository;
68
        $this->autoEditsHelper = $autoEditsHelper;
69
        $this->project = $project;
70
        $this->user = $user;
71
        $this->page = $page;
72
        $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace;
73
        $this->start = $start;
74
        $this->end = $end;
75
        $this->pagination = $pagination;
76
77
        if (null !== $limit) {
78
            $this->limit = $limit;
79
        } else {
80
            $this->limit = 'all' === $this->namespace
81
                ? self::DEFAULT_LIMIT_ALL_NAMESPACES
82
                : self::DEFAULT_LIMIT_SINGLE_NAMESPACE;
83
        }
84
    }
85
86
    /**
87
     * Which page of results we're showing.
88
     * @return int
89
     */
90
    public function getPagination(): int
91
    {
92
        return $this->pagination;
93
    }
94
95
    /**
96
     * Get total number of bytes added.
97
     * @return int
98
     */
99
    public function getTotalAdded(): int
100
    {
101
        return $this->totalAdded;
102
    }
103
104
    /**
105
     * Get total number of bytes removed.
106
     * @return int
107
     */
108
    public function getTotalRemoved(): int
109
    {
110
        return $this->totalRemoved;
111
    }
112
113
    /**
114
     * Get total number of edits marked as minor.
115
     * @return int
116
     */
117
    public function getTotalMinor(): int
118
    {
119
        return $this->totalMinor;
120
    }
121
122
    /**
123
     * Get total number of automated edits.
124
     * @return int
125
     */
126
    public function getTotalAutomated(): int
127
    {
128
        return $this->totalAutomated;
129
    }
130
131
    /**
132
     * Get total number of edits that were reverted.
133
     * @return int
134
     */
135
    public function getTotalReverted(): int
136
    {
137
        return $this->totalReverted;
138
    }
139
140
    /**
141
     * Get the top edits data.
142
     * @return Edit[]
143
     */
144
    public function getTopEdits(): array
145
    {
146
        return $this->topEdits;
147
    }
148
149
    /**
150
     * Get the total number of top edits.
151
     * @return int
152
     */
153
    public function getNumTopEdits(): int
154
    {
155
        return count($this->topEdits);
156
    }
157
158
    /**
159
     * Get the average time between edits (in days).
160
     * @return float
161
     */
162
    public function getAtbe(): float
163
    {
164
        $firstDateTime = $this->topEdits[0]->getTimestamp();
165
        $lastDateTime = end($this->topEdits)->getTimestamp();
166
        $secs = $firstDateTime->getTimestamp() - $lastDateTime->getTimestamp();
167
        $days = $secs / (60 * 60 * 24);
168
        return $days / count($this->topEdits);
169
    }
170
171
    /**
172
     * Set the Page on the TopEdits instance.
173
     * @param Page $page
174
     */
175
    public function setPage(Page $page): void
176
    {
177
        $this->page = $page;
178
    }
179
180
    /**
181
     * Fetch and store all the data we need to show the TopEdits view.
182
     * This is the public method that should be called before using
183
     * the getter methods.
184
     */
185
    public function prepareData(): void
186
    {
187
        if (isset($this->page)) {
188
            $this->topEdits = $this->getTopEditsPage();
189
        } else {
190
            $this->topEdits = $this->getTopEditsNamespace();
191
        }
192
    }
193
194
    /**
195
     * Get the top edits by a user in the given namespace, or 'all' namespaces.
196
     * @return string[] Results keyed by namespace.
197
     */
198
    private function getTopEditsNamespace(): array
199
    {
200
        if ('all' === $this->namespace) {
201
            $pages = $this->repository->getTopEditsAllNamespaces(
0 ignored issues
show
Bug introduced by
The method getTopEditsAllNamespaces() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

201
            /** @scrutinizer ignore-call */ 
202
            $pages = $this->repository->getTopEditsAllNamespaces(
Loading history...
202
                $this->project,
203
                $this->user,
204
                $this->start,
205
                $this->end,
206
                $this->limit
207
            );
208
        } else {
209
            $pages = $this->repository->getTopEditsNamespace(
0 ignored issues
show
Bug introduced by
The method getTopEditsNamespace() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
            /** @scrutinizer ignore-call */ 
210
            $pages = $this->repository->getTopEditsNamespace(
Loading history...
210
                $this->project,
211
                $this->user,
212
                $this->namespace,
213
                $this->start,
214
                $this->end,
215
                $this->limit,
216
                $this->pagination
217
            );
218
        }
219
220
        return $this->formatTopPagesNamespace($pages);
221
    }
222
223
    /**
224
     * Get the total number of pages edited in the namespace.
225
     * @return int|null
226
     */
227
    public function getNumPagesNamespace(): ?int
228
    {
229
        if ('all' === $this->namespace) {
230
            return null;
231
        }
232
233
        return (int)$this->repository->countEditsNamespace(
0 ignored issues
show
Bug introduced by
The method countEditsNamespace() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

233
        return (int)$this->repository->/** @scrutinizer ignore-call */ countEditsNamespace(
Loading history...
234
            $this->project,
235
            $this->user,
236
            $this->namespace,
237
            $this->start,
238
            $this->end
239
        );
240
    }
241
242
    /**
243
     * Get the top edits to the given page.
244
     * @return Edit[]
245
     */
246
    private function getTopEditsPage(): array
247
    {
248
        $revs = $this->repository->getTopEditsPage(
0 ignored issues
show
Bug introduced by
The method getTopEditsPage() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

248
        /** @scrutinizer ignore-call */ 
249
        $revs = $this->repository->getTopEditsPage(
Loading history...
249
            $this->page,
250
            $this->user,
251
            $this->start,
252
            $this->end
253
        );
254
255
        return $this->formatTopEditsPage($revs);
256
    }
257
258
    /**
259
     * Format the results for top edits to a single page. This method also computes
260
     * totals for added/removed text, automated and reverted edits.
261
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
262
     * @return Edit[]
263
     */
264
    private function formatTopEditsPage(array $revs): array
265
    {
266
        $edits = [];
267
268
        foreach ($revs as $revision) {
269
            // Check if the edit was reverted based on the edit summary of the following edit.
270
            // If so, update $revision so that when an Edit is instantiated, it will have the 'reverted' option set.
271
            if ($this->autoEditsHelper->isRevert($revision['parent_comment'], $this->project)) {
272
                $revision['reverted'] = 1;
273
            }
274
275
            $edits[] = $this->getEditAndIncrementCounts($revision);
276
        }
277
278
        return $edits;
279
    }
280
281
    /**
282
     * Create an Edit instance for the given revision, and increment running totals.
283
     * This is used by self::formatTopEditsPage().
284
     * @param string[] $revision Revision row as retrieved from the database.
285
     * @return Edit
286
     */
287
    private function getEditAndIncrementCounts(array $revision): Edit
288
    {
289
        $edit = $this->repository->getEdit($this->page, $revision);
0 ignored issues
show
Bug introduced by
The method getEdit() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\TopEditsRepository or App\Repository\PageInfoRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

289
        /** @scrutinizer ignore-call */ 
290
        $edit = $this->repository->getEdit($this->page, $revision);
Loading history...
290
291
        if ($edit->isAutomated()) {
292
            $this->totalAutomated++;
293
        }
294
295
        if ($edit->isMinor()) {
296
            $this->totalMinor++;
297
        }
298
299
        if ($edit->isReverted()) {
300
            $this->totalReverted++;
301
        } else {
302
            // Length changes don't count if they were reverted.
303
            if ($revision['length_change'] > 0) {
304
                $this->totalAdded += (int)$revision['length_change'];
305
            } else {
306
                $this->totalRemoved += (int)$revision['length_change'];
307
            }
308
        }
309
310
        return $edit;
311
    }
312
313
    /**
314
     * Format the results to be keyed by namespace.
315
     * @param array $pages As returned by TopEditsRepository::getTopEditsNamespace()
316
     *   or TopEditsRepository::getTopEditsAllNamespaces().
317
     * @return array Same results but keyed by namespace.
318
     */
319
    private function formatTopPagesNamespace(array $pages): array
320
    {
321
        /** @var string[] $topEditedPages The top edited pages, keyed by namespace ID. */
322
        $topEditedPages = [];
323
324
        foreach ($pages as $page) {
325
            $nsId = (int)$page['namespace'];
326
            $page['page_title'] = str_replace('_', ' ', $page['page_title']);
327
328
            // FIXME: needs refactoring, done in PagesController::getPagepileResult() and AppExtension::titleWithNs().
329
            if (0 === $nsId) {
330
                $page['full_page_title'] = $page['page_title'];
331
            } else {
332
                $page['full_page_title'] = str_replace('_', ' ', (
333
                    $this->project->getNamespaces()[$page['namespace']] ?? ''
334
                ).':'.$page['page_title']);
335
            }
336
337
            if (array_key_exists('pa_class', $page)) {
338
                $page['assessment'] = array_merge(
339
                    ['class' => $page['pa_class']],
340
                    $this->project->getPageAssessments()->getClassAttrs($page['pa_class'])
341
                );
342
                unset($page['pa_class']);
343
            }
344
345
            if (isset($topEditedPages[$nsId])) {
346
                $topEditedPages[$nsId][] = $page;
347
            } else {
348
                $topEditedPages[$nsId] = [$page];
349
            }
350
        }
351
352
        return $topEditedPages;
353
    }
354
}
355