Passed
Push — swagger-docs ( 6837d7 )
by MusikAnimal
11:33
created

Pages::formatPages()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 32
nc 9
nop 1
dl 0
loc 45
rs 8.1635
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Model;
6
7
use App\Repository\PagesRepository;
8
use DateTime;
9
10
/**
11
 * A Pages provides statistics about the pages created by a given User.
12
 */
13
class Pages extends Model
14
{
15
    private const RESULTS_LIMIT_SINGLE_NAMESPACE = 1000;
16
    private const RESULTS_LIMIT_ALL_NAMESPACES = 50;
17
18
    /** @var string One of 'noredirects', 'onlyredirects' or 'all' for both. */
19
    protected string $redirects;
20
21
    /** @var string One of 'live', 'deleted' or 'all' for both. */
22
    protected string $deleted;
23
24
    /** @var array The list of pages including various statistics, keyed by namespace. */
25
    protected array $pages;
26
27
    /** @var array Number of redirects/pages that were created/deleted, broken down by namespace. */
28
    protected array $countsByNamespace;
29
30
    /**
31
     * Pages constructor.
32
     * @param PagesRepository $repository
33
     * @param Project $project
34
     * @param User $user
35
     * @param string|int $namespace Namespace ID or 'all'.
36
     * @param string $redirects One of 'noredirects', 'onlyredirects' or 'all' for both.
37
     * @param string $deleted One of 'live', 'deleted' or 'all' for both.
38
     * @param int|false $start Start date as Unix timestamp.
39
     * @param int|false $end End date as Unix timestamp.
40
     * @param int|false $offset Unix timestamp. Used for pagination.
41
     */
42
    public function __construct(
43
        PagesRepository $repository,
44
        Project $project,
45
        User $user,
46
        $namespace = 0,
47
        string $redirects = 'noredirects',
48
        string $deleted = 'all',
49
        $start = false,
50
        $end = false,
51
        $offset = false
52
    ) {
53
        $this->repository = $repository;
54
        $this->project = $project;
55
        $this->user = $user;
56
        $this->namespace = 'all' === $namespace ? 'all' : (int)$namespace;
57
        $this->start = $start;
58
        $this->end = $end;
59
        $this->redirects = $redirects ?: 'noredirects';
60
        $this->deleted = $deleted ?: 'all';
61
        $this->offset = $offset;
62
    }
63
64
    /**
65
     * The redirects option associated with this Pages instance.
66
     * @return string
67
     */
68
    public function getRedirects(): string
69
    {
70
        return $this->redirects;
71
    }
72
73
    /**
74
     * The deleted pages option associated with this Page instance.
75
     * @return string
76
     */
77
    public function getDeleted(): string
78
    {
79
        return $this->deleted;
80
    }
81
82
    /**
83
     * Fetch and prepare the pages created by the user.
84
     * @param bool $all Whether to get *all* results. This should only be used for
85
     *     export options. HTTP and JSON should paginate.
86
     * @return array
87
     * @codeCoverageIgnore
88
     */
89
    public function prepareData(bool $all = false): array
90
    {
91
        $this->pages = [];
92
93
        foreach ($this->getNamespaces() as $ns) {
94
            $data = $this->fetchPagesCreated($ns, $all);
95
            $this->pages[$ns] = count($data) > 0
96
                ? $this->formatPages($data)[$ns]
97
                : [];
98
        }
99
100
        return $this->pages;
101
    }
102
103
    /**
104
     * The public function to get the list of all pages created by the user,
105
     * up to self::resultsPerPage(), across all namespaces.
106
     * @param bool $all Whether to get *all* results. This should only be used for
107
     *     export options. HTTP and JSON should paginate.
108
     * @return array
109
     */
110
    public function getResults(bool $all = false): array
111
    {
112
        if (!isset($this->pages)) {
113
            $this->prepareData($all);
114
        }
115
        return $this->pages;
116
    }
117
118
    /**
119
     * Return a ISO 8601 timestamp of the last result. This is used for pagination purposes.
120
     * @return string|null
121
     */
122
    public function getLastTimestamp(): ?string
123
    {
124
        if ($this->isMultiNamespace()) {
125
            // No pagination in multi-namespace view.
126
            return null;
127
        }
128
129
        $numResults = count($this->getResults()[$this->getNamespace()]);
130
        $timestamp = new DateTime($this->getResults()[$this->getNamespace()][$numResults - 1]['rev_timestamp']);
131
        return $timestamp->format('Y-m-d\TH:i:s');
132
    }
133
134
    /**
135
     * Get the total number of pages the user has created.
136
     * @return int
137
     */
138
    public function getNumPages(): int
139
    {
140
        $total = 0;
141
        foreach (array_values($this->getCounts()) as $values) {
142
            $total += $values['count'];
143
        }
144
        return $total;
145
    }
146
147
    /**
148
     * Get the total number of pages we're showing data for.
149
     * @return int
150
     */
151
    public function getNumResults(): int
152
    {
153
        $total = 0;
154
        foreach (array_values($this->getResults()) as $pages) {
155
            $total += count($pages);
156
        }
157
        return $total;
158
    }
159
160
    /**
161
     * Get the total number of pages that are currently deleted.
162
     * @return int
163
     */
164
    public function getNumDeleted(): int
165
    {
166
        $total = 0;
167
        foreach (array_values($this->getCounts()) as $values) {
168
            $total += $values['deleted'];
169
        }
170
        return $total;
171
    }
172
173
    /**
174
     * Get the total number of pages that are currently redirects.
175
     * @return int
176
     */
177
    public function getNumRedirects(): int
178
    {
179
        $total = 0;
180
        foreach (array_values($this->getCounts()) as $values) {
181
            $total += $values['redirects'];
182
        }
183
        return $total;
184
    }
185
186
    /**
187
     * Get the namespaces in which this user has created pages.
188
     * @return int[] The IDs.
189
     */
190
    public function getNamespaces(): array
191
    {
192
        return array_keys($this->getCounts());
193
    }
194
195
    /**
196
     * Number of namespaces being reported.
197
     * @return int
198
     */
199
    public function getNumNamespaces(): int
200
    {
201
        return count(array_keys($this->getCounts()));
202
    }
203
204
    /**
205
     * Are there more than one namespace in the results?
206
     * @return bool
207
     */
208
    public function isMultiNamespace(): bool
209
    {
210
        return $this->getNumNamespaces() > 1 || ('all' === $this->getNamespace() && 1 === $this->getNumNamespaces());
211
    }
212
213
    /**
214
     * Get the sum of all page sizes, across all specified namespaces.
215
     * @return int
216
     */
217
    public function getTotalPageSize(): int
218
    {
219
        return array_sum(array_column($this->getCounts(), 'total_length'));
220
    }
221
222
    /**
223
     * Get average size across all pages.
224
     * @return float
225
     */
226
    public function averagePageSize(): float
227
    {
228
        return $this->getTotalPageSize() / $this->getNumPages();
229
    }
230
231
    /**
232
     * Number of redirects/pages that were created/deleted, broken down by namespace.
233
     * @return array Namespace IDs as the keys, with values 'count', 'deleted' and 'redirects'.
234
     */
235
    public function getCounts(): array
236
    {
237
        if (isset($this->countsByNamespace)) {
238
            return $this->countsByNamespace;
239
        }
240
241
        $counts = [];
242
243
        foreach ($this->countPagesCreated() as $row) {
244
            $ns = (int)$row['namespace'];
245
            $count = (int)$row['count'];
246
            $totalLength = (int)$row['total_length'];
247
            $counts[$ns] = [
248
                'count' => $count,
249
                'total_length' => $totalLength,
250
                'avg_length' => round($count > 0 ?$totalLength / $count : 0, 1),
251
            ];
252
            if ('live' !== $this->deleted) {
253
                $counts[$ns]['deleted'] = (int)$row['deleted'];
254
            }
255
            if ('noredirects' !== $this->redirects) {
256
                $counts[$ns]['redirects'] = (int)$row['redirects'];
257
            }
258
        }
259
260
        $this->countsByNamespace = $counts;
261
        return $this->countsByNamespace;
262
    }
263
264
    /**
265
     * Get the number of pages the user created by assessment.
266
     * @return array Keys are the assessment class, values are the counts.
267
     */
268
    public function getAssessmentCounts(): array
269
    {
270
        if ($this->getNumPages() > $this->resultsPerPage()) {
271
            $counts = $this->repository->getAssessmentCounts(
0 ignored issues
show
Bug introduced by
The method getAssessmentCounts() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

271
            /** @scrutinizer ignore-call */ 
272
            $counts = $this->repository->getAssessmentCounts(
Loading history...
272
                $this->project,
273
                $this->user,
274
                $this->namespace,
275
                $this->redirects
276
            );
277
        } else {
278
            $counts = [];
279
            foreach ($this->pages as $nsPages) {
280
                foreach ($nsPages as $page) {
281
                    if (!isset($counts[$page['assessment']['class'] ?? 'Unknown'])) {
282
                        $counts[$page['assessment']['class'] ?? 'Unknown'] = 1;
283
                    } else {
284
                        $counts[$page['assessment']['class'] ?? 'Unknown']++;
285
                    }
286
                }
287
            }
288
        }
289
290
        arsort($counts);
291
292
        return $counts;
293
    }
294
295
    /**
296
     * Number of results to show, depending on the namespace.
297
     * @param bool $all Whether to get *all* results. This should only be used for
298
     *     export options. HTTP and JSON should paginate.
299
     * @return int|false
300
     */
301
    public function resultsPerPage(bool $all = false)
302
    {
303
        if (true === $all) {
304
            return false;
305
        }
306
        if ('all' === $this->namespace) {
307
            return self::RESULTS_LIMIT_ALL_NAMESPACES;
308
        }
309
        return self::RESULTS_LIMIT_SINGLE_NAMESPACE;
310
    }
311
312
    /**
313
     * Run the query to get pages created by the user with options.
314
     * This is ran independently for each namespace if $this->namespace is 'all'.
315
     * @param int $namespace Namespace ID.
316
     * @param bool $all Whether to get *all* results. This should only be used for
317
     *     export options. HTTP and JSON should paginate.
318
     * @return array
319
     */
320
    private function fetchPagesCreated(int $namespace, bool $all = false): array
321
    {
322
        return $this->repository->getPagesCreated(
0 ignored issues
show
Bug introduced by
The method getPagesCreated() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

322
        return $this->repository->/** @scrutinizer ignore-call */ getPagesCreated(
Loading history...
323
            $this->project,
324
            $this->user,
325
            $namespace,
326
            $this->redirects,
327
            $this->deleted,
328
            $this->start,
329
            $this->end,
330
            $this->resultsPerPage($all),
331
            $this->offset
332
        );
333
    }
334
335
    /**
336
     * Run the query to get the number of pages created by the user with given options.
337
     * @return array
338
     */
339
    private function countPagesCreated(): array
340
    {
341
        return $this->repository->countPagesCreated(
0 ignored issues
show
Bug introduced by
The method countPagesCreated() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\PagesRepository. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
        return $this->repository->/** @scrutinizer ignore-call */ countPagesCreated(
Loading history...
342
            $this->project,
343
            $this->user,
344
            $this->namespace,
345
            $this->redirects,
346
            $this->deleted,
347
            $this->start,
348
            $this->end
349
        );
350
    }
351
352
    /**
353
     * Format the data, adding page titles, assessment badges,
354
     * and sorting by namespace and then timestamp.
355
     * @param array $pages As returned by self::fetchPagesCreated()
356
     * @return array
357
     */
358
    private function formatPages(array $pages): array
359
    {
360
        $results = [];
361
362
        foreach ($pages as $row) {
363
            $fullPageTitle = $row['namespace'] > 0
364
                ? $this->project->getNamespaces()[$row['namespace']].':'.$row['page_title']
365
                : $row['page_title'];
366
            $pageData = [
367
                'deleted' => 'arc' === $row['type'],
368
                'namespace' => $row['namespace'],
369
                'page_title' => $row['page_title'],
370
                'full_page_title' => $fullPageTitle,
371
                'redirect' => (bool)$row['redirect'],
372
                'timestamp' => $row['timestamp'],
373
                'rev_id' => $row['rev_id'],
374
                'rev_length' => $row['rev_length'],
375
                'length' => $row['length'],
376
            ];
377
378
            if ($row['recreated']) {
379
                $pageData['recreated'] = (bool)$row['recreated'];
380
            } else {
381
                // This is always NULL for live pages, in which case 'recreated' doesn't apply.
382
                unset($pageData['recreated']);
383
            }
384
385
            if ($this->project->hasPageAssessments()) {
386
                $attrs = $this->project
387
                    ->getPageAssessments()
388
                    ->getClassAttrs($row['pa_class'] ?: 'Unknown');
389
                $pageData['assessment'] = [
390
                    'class' => $row['pa_class'] ?: 'Unknown',
391
                    'badge' => $this->project
392
                        ->getPageAssessments()
393
                        ->getBadgeURL($row['pa_class'] ?: 'Unknown'),
394
                    'color' => $attrs['color'],
395
                    'category' => $attrs['category'],
396
                ];
397
            }
398
399
            $results[$row['namespace']][] = $pageData;
400
        }
401
402
        return $results;
403
    }
404
}
405