Passed
Push — master ( 5f2715...9adfbc )
by MusikAnimal
06:16
created

TopEdits::getDisplayTitles()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 1
nop 1
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 2
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
    private const DEFAULT_LIMIT_SINGLE_NAMESPACE = 1000;
36
    private const DEFAULT_LIMIT_ALL_NAMESPACES = 20;
37
38
    /**
39
     * TopEdits constructor.
40
     * @param Project $project
41
     * @param User $user
42
     * @param Page $page
43
     * @param string|int $namespace Namespace ID or 'all'.
44
     * @param int $limit Number of rows to fetch. This defaults to DEFAULT_LIMIT_SINGLE_NAMESPACE if $this->namespace
45
     *   is a single namespace (int), and DEFAULT_LIMIT_ALL_NAMESPACES if $this->namespace is 'all'.
46
     * @param int $offset Number of pages past the initial dataset. Used for pagination.
47
     */
48 4
    public function __construct(
49
        Project $project,
50
        User $user,
51
        ?Page $page = null,
52
        $namespace = 0,
53
        $limit = null,
54
        $offset = 0
55
    ) {
56 4
        $this->project = $project;
57 4
        $this->user = $user;
58 4
        $this->page = $page;
59 4
        $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace;
60 4
        $this->offset = (int)$offset;
61
62 4
        if (null !== $limit) {
63 3
            $this->limit = (int)$limit;
64
        } else {
65 2
            $this->limit = 'all' === $this->namespace
66 1
                ? self::DEFAULT_LIMIT_ALL_NAMESPACES
67 2
                : self::DEFAULT_LIMIT_SINGLE_NAMESPACE;
68
        }
69 4
    }
70
71
    /**
72
     * Get total number of bytes added.
73
     * @return int
74
     */
75 1
    public function getTotalAdded(): int
76
    {
77 1
        return $this->totalAdded;
78
    }
79
80
    /**
81
     * Get total number of bytes removed.
82
     * @return int
83
     */
84 1
    public function getTotalRemoved(): int
85
    {
86 1
        return $this->totalRemoved;
87
    }
88
89
    /**
90
     * Get total number of edits marked as minor.
91
     * @return int
92
     */
93 1
    public function getTotalMinor(): int
94
    {
95 1
        return $this->totalMinor;
96
    }
97
98
    /**
99
     * Get total number of automated edits.
100
     * @return int
101
     */
102 1
    public function getTotalAutomated(): int
103
    {
104 1
        return $this->totalAutomated;
105
    }
106
107
    /**
108
     * Get total number of edits that were reverted.
109
     * @return int
110
     */
111 1
    public function getTotalReverted(): int
112
    {
113 1
        return $this->totalReverted;
114
    }
115
116
    /**
117
     * Get the top edits data.
118
     * @return array|Edit[]
119
     */
120 3
    public function getTopEdits(): array
121
    {
122 3
        return $this->topEdits;
123
    }
124
125
    /**
126
     * Get the total number of top edits.
127
     * @return int
128
     */
129 1
    public function getNumTopEdits(): int
130
    {
131 1
        return count($this->topEdits);
132
    }
133
134
    /**
135
     * Get the average time between edits (in days).
136
     * @return float
137
     */
138 1
    public function getAtbe(): float
139
    {
140 1
        $firstDateTime = $this->topEdits[0]->getTimestamp();
141 1
        $lastDateTime = end($this->topEdits)->getTimestamp();
142 1
        $secs = $firstDateTime->getTimestamp() - $lastDateTime->getTimestamp();
143 1
        $days = $secs / (60 * 60 * 24);
144 1
        return $days / count($this->topEdits);
145
    }
146
147
    /**
148
     * Set the Page on the TopEdits instance.
149
     * @param Page $page
150
     */
151 1
    public function setPage(Page $page): void
152
    {
153 1
        $this->page = $page;
154 1
    }
155
156
    /**
157
     * Fetch and store all the data we need to show the TopEdits view.
158
     * This is the public method that should be called before using
159
     * the getter methods.
160
     * @param bool $format Whether to format the results, including stats for
161
     *     number of reverts, etc. This is set to false for the API endpoint.
162
     */
163 3
    public function prepareData(bool $format = true): void
164
    {
165 3
        if (isset($this->page)) {
166 1
            $this->topEdits = $this->getTopEditsPage($format);
167
        } else {
168 2
            $this->topEdits = $this->getTopEditsNamespace($format);
169
        }
170 3
    }
171
172
    /**
173
     * Get the top edits by a user in the given namespace, or 'all' namespaces.
174
     * @param bool $format Whether to format the results, including stats for
175
     *     number of reverts, etc. This is set to false for the API endpoint.
176
     * @return string[] Results keyed by namespace.
177
     */
178 2
    private function getTopEditsNamespace(bool $format): array
179
    {
180 2
        if ('all' === $this->namespace) {
181 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

181
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsAllNamespaces(
Loading history...
182 1
                $this->project,
183 1
                $this->user,
184 1
                $this->limit
185
            );
186
        } else {
187 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

187
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsNamespace(
Loading history...
188 1
                $this->project,
189 1
                $this->user,
190 1
                $this->namespace,
191 1
                $this->limit,
192 1
                $this->offset * $this->limit
193
            );
194
        }
195
196 2
        if ($format) {
197 2
            return $this->formatTopPagesNamespace($pages);
198
        } else {
199
            return $pages;
200
        }
201
    }
202
203
    /**
204
     * Get the total number of pages edited in the namespace.
205
     * @return int|null
206
     */
207
    public function getNumPagesNamespace(): ?int
208
    {
209
        if ('all' === $this->namespace) {
210
            return null;
211
        }
212
213
        return (int)$this->getRepository()->countEditsNamespace($this->project, $this->user, $this->namespace);
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

213
        return (int)$this->getRepository()->/** @scrutinizer ignore-call */ countEditsNamespace($this->project, $this->user, $this->namespace);
Loading history...
214
    }
215
216
    /**
217
     * Get the top edits to the given page.
218
     * @param bool $format Whether to format the results, including stats for
219
     *     number of reverts, etc. This is set to false for the API endpoint.
220
     * @return Edit[]
221
     */
222 1
    private function getTopEditsPage(bool $format = true): array
223
    {
224 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

224
        $revs = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsPage(
Loading history...
225 1
            $this->page,
226 1
            $this->user
227
        );
228
229 1
        if ($format) {
230 1
            return $this->formatTopEditsPage($revs);
231
        } else {
232
            return $revs;
233
        }
234
    }
235
236
    /**
237
     * Format the results for top edits to a single page. This method also computes
238
     * totals for added/removed text, automated and reverted edits.
239
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
240
     * @return Edit[]
241
     */
242 1
    private function formatTopEditsPage(array $revs): array
243
    {
244 1
        $edits = [];
245
246
        /** @var AutomatedEditsHelper $aeh */
247 1
        $aeh = $this->getRepository()
248 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

248
            ->/** @scrutinizer ignore-call */ getContainer()
Loading history...
249 1
            ->get('app.automated_edits_helper');
250
251 1
        foreach ($revs as $revision) {
252
            // Check if the edit was reverted based on the edit summary of the following edit.
253
            // If so, update $revision so that when an Edit is instantiated, it will have the 'reverted' option set.
254 1
            if ($aeh->isRevert($revision['parent_comment'], $this->project)) {
255 1
                $revision['reverted'] = 1;
256
            }
257
258 1
            $edit = $this->getEditAndIncrementCounts($revision);
259
260 1
            $edits[] = $edit;
261
        }
262
263 1
        return $edits;
264
    }
265
266
    /**
267
     * Create an Edit instance for the given revision, and increment running totals.
268
     * This is used by self::formatTopEditsPage().
269
     * @param string[] $revision Revision row as retrieved from the database.
270
     * @return Edit
271
     */
272 1
    private function getEditAndIncrementCounts(array $revision): Edit
273
    {
274 1
        $edit = new Edit($this->page, $revision);
275
276 1
        if ($edit->isAutomated($this->getRepository()->getContainer())) {
277 1
            $this->totalAutomated++;
278
        }
279
280 1
        if ($edit->isMinor()) {
281 1
            $this->totalMinor++;
282
        }
283
284 1
        if ($edit->isReverted()) {
285 1
            $this->totalReverted++;
286
        } else {
287
            // Length changes don't count if they were reverted.
288 1
            if ($revision['length_change'] > 0) {
289 1
                $this->totalAdded += $revision['length_change'];
290
            } else {
291 1
                $this->totalRemoved += $revision['length_change'];
292
            }
293
        }
294
295 1
        return $edit;
296
    }
297
298
    /**
299
     * Format the results to be keyed by namespace.
300
     * @param array $pages As returned by TopEditsRepository::getTopEditsNamespace()
301
     *   or TopEditsRepository::getTopEditsAllNamespaces().
302
     * @return array Same results but keyed by namespace.
303
     */
304 2
    private function formatTopPagesNamespace(array $pages): array
305
    {
306
        /** @var string[] $topEditedPages The top edited pages, keyed by namespace ID. */
307 2
        $topEditedPages = [];
308
309 2
        foreach ($pages as $page) {
310 2
            $nsId = (int)$page['page_namespace'];
311
312
            // FIXME: needs refactoring, done in PagesController::getPagepileResult() and AppExtension::titleWithNs().
313 2
            if (0 === $nsId) {
314 1
                $page['page_title_ns'] = $page['page_title'];
315
            } else {
316 2
                $page['page_title_ns'] = (
317 2
                    $this->project->getNamespaces()[$page['page_namespace']] ?? ''
318 2
                ).':'.$page['page_title'];
319
            }
320
321 2
            if (isset($topEditedPages[$nsId])) {
322 2
                $topEditedPages[$nsId][] = $page;
323
            } else {
324 2
                $topEditedPages[$nsId] = [$page];
325
            }
326
        }
327
328 2
        return $topEditedPages;
329
    }
330
}
331