Passed
Push — master ( 4fbd4f...e8494b )
by MusikAnimal
11:14
created

TopEdits::getTopEdits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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(
0 ignored issues
show
Bug introduced by
The method getTopEditsAllNamespaces() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

199
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsAllNamespaces(
Loading history...
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(
0 ignored issues
show
Bug introduced by
The method getTopEditsNamespace() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

207
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsNamespace(
Loading history...
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(
0 ignored issues
show
Bug introduced by
The method countEditsNamespace() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

235
        return (int)$this->getRepository()->/** @scrutinizer ignore-call */ countEditsNamespace(
Loading history...
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(
0 ignored issues
show
Bug introduced by
The method getTopEditsPage() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

252
        $revs = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsPage(
Loading history...
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()
0 ignored issues
show
Bug introduced by
The method getContainer() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\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

278
            ->/** @scrutinizer ignore-call */ getContainer()
Loading history...
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