Passed
Push — master ( 994f84...0e30e3 )
by MusikAnimal
06:32
created

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

187
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsAllNamespaces(
Loading history...
188 1
                $this->project,
189 1
                $this->user,
190 1
                $this->start,
191 1
                $this->end,
192 1
                $this->limit
193
            );
194
        } else {
195 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

195
            $pages = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsNamespace(
Loading history...
196 1
                $this->project,
197 1
                $this->user,
198 1
                $this->namespace,
199 1
                $this->start,
200 1
                $this->end,
201 1
                $this->limit,
202 1
                $this->offset * $this->limit
203
            );
204
        }
205
206 2
        if ($format) {
207 2
            return $this->formatTopPagesNamespace($pages);
208
        } else {
209
            return $pages;
210
        }
211
    }
212
213
    /**
214
     * Get the total number of pages edited in the namespace.
215
     * @return int|null
216
     */
217
    public function getNumPagesNamespace(): ?int
218
    {
219
        if ('all' === $this->namespace) {
220
            return null;
221
        }
222
223
        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

223
        return (int)$this->getRepository()->/** @scrutinizer ignore-call */ countEditsNamespace(
Loading history...
224
            $this->project,
225
            $this->user,
226
            $this->namespace,
227
            $this->start,
228
            $this->end
229
        );
230
    }
231
232
    /**
233
     * Get the top edits to the given page.
234
     * @param bool $format Whether to format the results, including stats for
235
     *     number of reverts, etc. This is set to false for the API endpoint.
236
     * @return Edit[]
237
     */
238 1
    private function getTopEditsPage(bool $format = true): array
239
    {
240 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

240
        $revs = $this->getRepository()->/** @scrutinizer ignore-call */ getTopEditsPage(
Loading history...
241 1
            $this->page,
242 1
            $this->user,
243 1
            $this->start,
244 1
            $this->end
245
        );
246
247 1
        if ($format) {
248 1
            return $this->formatTopEditsPage($revs);
249
        } else {
250
            return $revs;
251
        }
252
    }
253
254
    /**
255
     * Format the results for top edits to a single page. This method also computes
256
     * totals for added/removed text, automated and reverted edits.
257
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
258
     * @return Edit[]
259
     */
260 1
    private function formatTopEditsPage(array $revs): array
261
    {
262 1
        $edits = [];
263
264
        /** @var AutomatedEditsHelper $aeh */
265 1
        $aeh = $this->getRepository()
266 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

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