Passed
Push — swagger-docs ( 6837d7...7bc867 )
by MusikAnimal
11:22
created

TopEdits::formatTopPagesNamespace()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 19
c 0
b 0
f 0
nc 9
nop 1
dl 0
loc 33
rs 8.8333
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 array|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
     * @param bool $format Whether to format the results, including stats for
185
     *     number of reverts, etc. This is set to false for the API endpoints.
186
     */
187
    public function prepareData(bool $format = true): void
188
    {
189
        if (isset($this->page)) {
190
            $this->topEdits = $this->getTopEditsPage($format);
191
        } else {
192
            $this->topEdits = $this->getTopEditsNamespace($format);
193
        }
194
    }
195
196
    /**
197
     * Get the top edits by a user in the given namespace, or 'all' namespaces.
198
     * @param bool $format Whether to format the results, including stats for
199
     *     number of reverts, etc. This is set to false for the API endpoint.
200
     * @return string[] Results keyed by namespace.
201
     */
202
    private function getTopEditsNamespace(bool $format): array
203
    {
204
        if ('all' === $this->namespace) {
205
            $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

205
            /** @scrutinizer ignore-call */ 
206
            $pages = $this->repository->getTopEditsAllNamespaces(
Loading history...
206
                $this->project,
207
                $this->user,
208
                $this->start,
209
                $this->end,
210
                $this->limit
211
            );
212
        } else {
213
            $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

213
            /** @scrutinizer ignore-call */ 
214
            $pages = $this->repository->getTopEditsNamespace(
Loading history...
214
                $this->project,
215
                $this->user,
216
                $this->namespace,
217
                $this->start,
218
                $this->end,
219
                $this->limit,
220
                $this->pagination
221
            );
222
        }
223
224
        if ($format) {
225
            return $this->formatTopPagesNamespace($pages);
226
        } else {
227
            return $pages;
228
        }
229
    }
230
231
    /**
232
     * Get the total number of pages edited in the namespace.
233
     * @return int|null
234
     */
235
    public function getNumPagesNamespace(): ?int
236
    {
237
        if ('all' === $this->namespace) {
238
            return null;
239
        }
240
241
        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

241
        return (int)$this->repository->/** @scrutinizer ignore-call */ countEditsNamespace(
Loading history...
242
            $this->project,
243
            $this->user,
244
            $this->namespace,
245
            $this->start,
246
            $this->end
247
        );
248
    }
249
250
    /**
251
     * Get the top edits to the given page.
252
     * @param bool $format Whether to format the results, including stats for
253
     *     number of reverts, etc. This is set to false for the API endpoint.
254
     * @return Edit[]
255
     */
256
    private function getTopEditsPage(bool $format = true): array
257
    {
258
        $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

258
        /** @scrutinizer ignore-call */ 
259
        $revs = $this->repository->getTopEditsPage(
Loading history...
259
            $this->page,
260
            $this->user,
261
            $this->start,
262
            $this->end
263
        );
264
265
        if ($format) {
266
            return $this->formatTopEditsPage($revs);
267
        } else {
268
            return array_map(function ($rev) {
269
                $rev['minor'] = (bool)$rev['minor'];
270
                $rev['reverted'] = (bool)$rev['reverted'];
271
                return $rev;
272
            }, $revs);
273
        }
274
    }
275
276
    /**
277
     * Format the results for top edits to a single page. This method also computes
278
     * totals for added/removed text, automated and reverted edits.
279
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
280
     * @return Edit[]
281
     */
282
    private function formatTopEditsPage(array $revs): array
283
    {
284
        $edits = [];
285
286
        foreach ($revs as $revision) {
287
            // Check if the edit was reverted based on the edit summary of the following edit.
288
            // If so, update $revision so that when an Edit is instantiated, it will have the 'reverted' option set.
289
            if ($this->autoEditsHelper->isRevert($revision['parent_comment'], $this->project)) {
290
                $revision['reverted'] = 1;
291
            }
292
293
            $edits[] = $this->getEditAndIncrementCounts($revision);
294
        }
295
296
        return $edits;
297
    }
298
299
    /**
300
     * Create an Edit instance for the given revision, and increment running totals.
301
     * This is used by self::formatTopEditsPage().
302
     * @param string[] $revision Revision row as retrieved from the database.
303
     * @return Edit
304
     */
305
    private function getEditAndIncrementCounts(array $revision): Edit
306
    {
307
        $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\ArticleInfoRepository. ( Ignorable by Annotation )

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

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