Passed
Push — master ( c121a7...5fa1a0 )
by MusikAnimal
05:15
created

TopEdits::getNumPagesNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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