Passed
Push — master ( 95d2e4...8875f5 )
by MusikAnimal
05:41
created

TopEdits::__construct()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 6
nop 5
dl 0
loc 13
ccs 10
cts 10
cp 1
crap 4
rs 9.9332
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the TopEdits class.
4
 */
5
6
namespace Xtools;
7
8
/**
9
 * TopEdits returns the top-edited pages by a user.
10
 */
11
class TopEdits extends Model
12
{
13
    /** @var string[]|Edit[] Top edits, either to a page or across namespaces. */
14
    protected $topEdits = [];
15
16
    /** @var int Number of bytes added across all top edits. */
17
    protected $totalAdded = 0;
18
19
    /** @var int Number of bytes removed across all top edits. */
20
    protected $totalRemoved = 0;
21
22
    /** @var int Number of top edits marked as minor. */
23
    protected $totalMinor = 0;
24
25
    /** @var int Number of automated top edits. */
26
    protected $totalAutomated = 0;
27
28
    /** @var int Number of reverted top edits. */
29
    protected $totalReverted = 0;
30
31
    const DEFAULT_LIMIT_SINGLE_NAMESPACE = 100;
32
    const DEFAULT_LIMIT_ALL_NAMESPACES = 20;
33
34
    /**
35
     * TopEdits constructor.
36
     * @param Project $project
37
     * @param User $user
38
     * @param Page $page
39
     * @param string|int $namespace Namespace ID or 'all'.
40
     * @param int $limit Number of rows to fetch. This defaults to
41
     *   DEFAULT_LIMIT_SINGLE_NAMESPACE if $this->namespace is a single namespace (int),
42
     *   and DEFAULT_LIMIT_ALL_NAMESPACES if $this->namespace is 'all'.
43
     */
44 4
    public function __construct(Project $project, User $user, Page $page = null, $namespace = 0, $limit = null)
45
    {
46 4
        $this->project = $project;
47 4
        $this->user = $user;
48 4
        $this->page = $page;
49 4
        $this->namespace = $namespace === 'all' ? 'all' : (int)$namespace;
50
51 4
        if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
52 3
            $this->limit = $limit;
53
        } else {
54 2
            $this->limit = $this->namespace === 'all'
55 1
                ? self::DEFAULT_LIMIT_ALL_NAMESPACES
56 2
                : self::DEFAULT_LIMIT_SINGLE_NAMESPACE;
57
        }
58 4
    }
59
60
    /**
61
     * Get total number of bytes added.
62
     * @return int
63
     */
64 1
    public function getTotalAdded()
65
    {
66 1
        return $this->totalAdded;
67
    }
68
69
    /**
70
     * Get total number of bytes removed.
71
     * @return int
72
     */
73 1
    public function getTotalRemoved()
74
    {
75 1
        return $this->totalRemoved;
76
    }
77
78
    /**
79
     * Get total number of edits marked as minor.
80
     * @return int
81
     */
82 1
    public function getTotalMinor()
83
    {
84 1
        return $this->totalMinor;
85
    }
86
87
    /**
88
     * Get total number of automated edits.
89
     * @return int
90
     */
91 1
    public function getTotalAutomated()
92
    {
93 1
        return $this->totalAutomated;
94
    }
95
96
    /**
97
     * Get total number of edits that were reverted.
98
     * @return int
99
     */
100 1
    public function getTotalReverted()
101
    {
102 1
        return $this->totalReverted;
103
    }
104
105
    /**
106
     * Get the top edits data.
107
     * @return string[]|Edit[]
108
     */
109 3
    public function getTopEdits()
110
    {
111 3
        return $this->topEdits;
112
    }
113
114
    /**
115
     * Get the total number of top edits.
116
     * @return int
117
     */
118 1
    public function getNumTopEdits()
119
    {
120 1
        return count($this->topEdits);
121
    }
122
123
    /**
124
     * Get the averate time between edits (in days).
125
     * @return double
126
     */
127 1
    public function getAtbe()
128
    {
129 1
        $firstDateTime = $this->topEdits[0]->getTimestamp();
130 1
        $lastDateTime = end($this->topEdits)->getTimestamp();
131 1
        $secs = $firstDateTime->getTimestamp() - $lastDateTime->getTimestamp();
132 1
        $days = $secs / (60 * 60 * 24);
133 1
        return $days / count($this->topEdits);
134
    }
135
136
    /**
137
     * Set the Page on the TopEdits instance.
138
     * @param Page $page
139
     */
140 1
    public function setPage(Page $page)
141
    {
142 1
        $this->page = $page;
143 1
    }
144
145
    /**
146
     * Fetch and store all the data we need to show the TopEdits view.
147
     * This is the public method that should be called before using
148
     * the getter methods.
149
     * @param bool $format Whether to format the results, including stats for
150
     *     number of reverts, etc. This is set to false for the API endpoint.
151
     */
152 3
    public function prepareData($format = true)
153
    {
154 3
        if (isset($this->page)) {
155 1
            $this->topEdits = $this->getTopEditsPage($format);
156
        } else {
157 2
            $this->topEdits = $this->getTopEditsNamespace($format);
158
        }
159 3
    }
160
161
    /**
162
     * Get the top edits by a user in the given namespace, or 'all' namespaces.
163
     * @param bool $format Whether to format the results, including stats for
164
     *     number of reverts, etc. This is set to false for the API endpoint.
165
     * @return string[] Results keyed by namespace.
166
     */
167 2
    private function getTopEditsNamespace($format)
168
    {
169 2
        if ($this->namespace === 'all') {
170 1
            $pages = $this->getRepository()->getTopEditsAllNamespaces(
171 1
                $this->project,
172 1
                $this->user,
173 1
                $this->limit
174
            );
175
        } else {
176 1
            $pages = $this->getRepository()->getTopEditsNamespace(
177 1
                $this->project,
178 1
                $this->user,
179 1
                $this->namespace,
180 1
                $this->limit
181
            );
182
        }
183
184 2
        if ($format) {
185 2
            return $this->formatTopPagesNamespace($pages);
186
        } else {
187
            return $pages;
188
        }
189
    }
190
191
    /**
192
     * Get the top edits to the given page.
193
     * @param bool $format Whether to format the results, including stats for
194
     *     number of reverts, etc. This is set to false for the API endpoint.
195
     * @return Edit[]
196
     */
197 1
    private function getTopEditsPage($format = true)
198
    {
199 1
        $revs = $this->getRepository()->getTopEditsPage(
200 1
            $this->page,
201 1
            $this->user
202
        );
203
204 1
        if ($format) {
205 1
            return $this->formatTopEditsPage($revs);
206
        } else {
207
            return $revs;
208
        }
209
    }
210
211
    /**
212
     * Format the results for top edits to a single page. This method also computes
213
     * totals for added/removed text, automated and reverted edits.
214
     * @param array[] $revs As returned by TopEditsRepository::getTopEditsPage.
215
     * @return Edit[]
216
     */
217 1
    private function formatTopEditsPage($revs)
218
    {
219 1
        $edits = [];
220
221 1
        $aeh = $this->getRepository()
222 1
            ->getContainer()
223 1
            ->get('app.automated_edits_helper');
224
225 1
        foreach ($revs as $revision) {
226
            // Check if the edit was reverted based on the edit summary of the following edit.
227
            // If so, update $revision so that when an Edit is instantiated, it will
228
            // have the 'reverted' option set.
229 1
            if ($aeh->isRevert($revision['parent_comment'], $this->project)) {
230 1
                $revision['reverted'] = 1;
231
            }
232
233 1
            $edit = $this->getEditAndIncrementCounts($revision);
234
235 1
            $edits[] = $edit;
236
        }
237
238 1
        return $edits;
239
    }
240
241
    /**
242
     * Create an Edit instance for the given revision, and increment running totals.
243
     * This is used by self::formatTopEditsPage().
244
     * @param  string[] $revision Revision row as retrieved from the database.
245
     * @return Edit
246
     */
247 1
    private function getEditAndIncrementCounts($revision)
248
    {
249 1
        $edit = new Edit($this->page, $revision);
250
251 1
        if ($edit->isAutomated($this->getRepository()->getContainer())) {
252 1
            $this->totalAutomated++;
253
        }
254
255 1
        if ($edit->isMinor()) {
256 1
            $this->totalMinor++;
257
        }
258
259 1
        if ($edit->isReverted()) {
260 1
            $this->totalReverted++;
261
        } else {
262
            // Length changes don't count if they were reverted.
263 1
            if ($revision['length_change'] > 0) {
264 1
                $this->totalAdded += $revision['length_change'];
265
            } else {
266 1
                $this->totalRemoved += $revision['length_change'];
267
            }
268
        }
269
270 1
        return $edit;
271
    }
272
273
    /**
274
     * Format the results returned from the database.
275
     * @param array $pages As returned by TopEditsRepository::getTopEditsNamespace
276
     *   or TopEditsRepository::getTopEditsAllNamespaces.
277
     * @return string[] Same as input but with 'displaytitle' and 'page_title_ns'.
278
     */
279 2
    private function formatTopPagesNamespace($pages)
280
    {
281
        /** @var string[] The top edited pages, keyed by namespace ID. */
282 2
        $topEditedPages = [];
283
284
        /** @var string[] Display titles of the pages, which need to be fetched ahead of time. */
285 2
        $displayTitles = $this->getDisplayTitles($pages);
286
287 2
        foreach ($pages as $page) {
288 2
            $nsId = (int) $page['page_namespace'];
289 2
            $nsTitle = $nsId > 0 ? $this->project->getNamespaces()[$page['page_namespace']] . ':' : '';
290 2
            $pageTitle = $nsTitle . $page['page_title'];
291 2
            $page['displaytitle'] = $displayTitles[$pageTitle];
292
293
            // $page['page_title'] is retained without the namespace
294
            //  so we can link to TopEdits for that page.
295 2
            $page['page_title_ns'] = $pageTitle;
296
297 2
            if (isset($topEditedPages[$nsId])) {
298 2
                $topEditedPages[$nsId][] = $page;
299
            } else {
300 2
                $topEditedPages[$nsId] = [$page];
301
            }
302
        }
303
304 2
        return $topEditedPages;
305
    }
306
307
    /**
308
     * Get the display titles of the given pages.
309
     * @param string[] $topPages As returned by $this->getRepository()->getTopEdits()
310
     * @return string[] Keys are the original supplied titles, and values are the display titles.
311
     */
312 2
    private function getDisplayTitles($topPages)
313
    {
314 2
        $namespaces = $this->project->getNamespaces();
315
316
        // First extract page titles including namespace.
317 2
        $pageTitles = array_map(function ($page) use ($namespaces) {
318
            // If non-mainspace, prepend namespace to the titles.
319 2
            $ns = $page['page_namespace'];
320 2
            $nsTitle = $ns > 0 ? $namespaces[$page['page_namespace']] . ':' : '';
321 2
            return $nsTitle . $page['page_title'];
322 2
        }, $topPages);
323
324 2
        return $this->getRepository()->getDisplayTitles($this->project, $pageTitles);
325
    }
326
}
327