Pages::__construct()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 2
nop 9
dl 0
loc 20
rs 9.9666
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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

278
            /** @scrutinizer ignore-call */ 
279
            $counts = $this->repository->getAssessmentCounts(
Loading history...
279
                $this->project,
280
                $this->user,
281
                $this->namespace,
282
                $this->redirects
283
            );
284
        } else {
285
            $counts = [];
286
            foreach ($this->pages as $nsPages) {
287
                foreach ($nsPages as $page) {
288
                    if (!isset($counts[$page['assessment']['class'] ?? 'Unknown'])) {
289
                        $counts[$page['assessment']['class'] ?? 'Unknown'] = 1;
290
                    } else {
291
                        $counts[$page['assessment']['class'] ?? 'Unknown']++;
292
                    }
293
                }
294
            }
295
        }
296
297
        arsort($counts);
298
299
        return $counts;
300
    }
301
302
    /**
303
     * Number of results to show, depending on the namespace.
304
     * @param bool $all Whether to get *all* results. This should only be used for
305
     *     export options. HTTP and JSON should paginate.
306
     * @return int|false
307
     */
308
    public function resultsPerPage(bool $all = false)
309
    {
310
        if (true === $all) {
311
            return false;
312
        }
313
        if ('all' === $this->namespace) {
314
            return self::RESULTS_LIMIT_ALL_NAMESPACES;
315
        }
316
        return self::RESULTS_LIMIT_SINGLE_NAMESPACE;
317
    }
318
319
    /**
320
     * What columns to show in namespace totals table.
321
     * @return string[]
322
     */
323
    public function getSummaryColumns(): array
324
    {
325
        $order = ['namespace', 'pages', 'redirects', 'deleted', 'live', 'total-page-size', 'average-page-size'];
326
327
        $summaryColumns = ['namespace'];
328
        if (in_array($this->getDeleted(), [self::DEL_ALL, self::DEL_ONLY])) {
329
            $summaryColumns[] = 'deleted';
330
        }
331
        if (self::DEL_ALL === $this->getDeleted()) {
332
            $summaryColumns[] = 'live';
333
        }
334
        if (in_array($this->getRedirects(), [self::REDIR_ALL, self::REDIR_ONLY])) {
335
            $summaryColumns[] = 'redirects';
336
        }
337
        if (self::DEL_ONLY !== $this->getDeleted() && self::REDIR_ONLY !== $this->getRedirects()) {
338
            $summaryColumns[] = 'pages';
339
        }
340
341
        $summaryColumns[] = 'total-page-size';
342
        $summaryColumns[] = 'average-page-size';
343
344
        // Re-sort based on $order
345
        return array_values(array_filter($order, static function ($column) use ($summaryColumns) {
346
            return in_array($column, $summaryColumns);
347
        }));
348
    }
349
350
    /**
351
     * Get the deletion summary to be shown when hovering over the "Deleted" text in the UI.
352
     * @param int $namespace
353
     * @param string $pageTitle
354
     * @param string $offset
355
     * @return string|null null if no deletion summary is available.
356
     */
357
    public function getDeletionSummary(int $namespace, string $pageTitle, string $offset): ?string
358
    {
359
        $ret = $this->repository->getDeletionSummary($this->project, $namespace, $pageTitle, $offset);
0 ignored issues
show
Bug introduced by
The method getDeletionSummary() 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

359
        /** @scrutinizer ignore-call */ 
360
        $ret = $this->repository->getDeletionSummary($this->project, $namespace, $pageTitle, $offset);
Loading history...
360
        if (!$ret) {
361
            return null;
362
        }
363
        $timestampStr = (new DateTime($ret['log_timestamp']))->format('Y-m-d H:i');
364
        $summary = Edit::wikifyString($ret['comment_text'], $this->project, $this->page, true);
365
        $userpageUrl = $this->project->getUrlForPage("User:{$ret['actor_name']}");
366
        return "$timestampStr (<a target='_blank' href=\"$userpageUrl\">{$ret['actor_name']}</a>): <i>$summary</i>";
367
    }
368
369
    /**
370
     * Run the query to get pages created by the user with options.
371
     * This is ran independently for each namespace if $this->namespace is 'all'.
372
     * @param int $namespace Namespace ID.
373
     * @param bool $all Whether to get *all* results. This should only be used for
374
     *     export options. HTTP and JSON should paginate.
375
     * @return array
376
     */
377
    private function fetchPagesCreated(int $namespace, bool $all = false): array
378
    {
379
        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

379
        return $this->repository->/** @scrutinizer ignore-call */ getPagesCreated(
Loading history...
380
            $this->project,
381
            $this->user,
382
            $namespace,
383
            $this->redirects,
384
            $this->deleted,
385
            $this->start,
386
            $this->end,
387
            $this->resultsPerPage($all),
388
            $this->offset
389
        );
390
    }
391
392
    /**
393
     * Run the query to get the number of pages created by the user with given options.
394
     * @return array
395
     */
396
    private function countPagesCreated(): array
397
    {
398
        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

398
        return $this->repository->/** @scrutinizer ignore-call */ countPagesCreated(
Loading history...
399
            $this->project,
400
            $this->user,
401
            $this->namespace,
402
            $this->redirects,
403
            $this->deleted,
404
            $this->start,
405
            $this->end
406
        );
407
    }
408
409
    /**
410
     * Format the data, adding page titles, assessment badges,
411
     * and sorting by namespace and then timestamp.
412
     * @param array $pages As returned by self::fetchPagesCreated()
413
     * @return array
414
     */
415
    private function formatPages(array $pages): array
416
    {
417
        $results = [];
418
419
        foreach ($pages as $row) {
420
            $fullPageTitle = $row['namespace'] > 0
421
                ? $this->project->getNamespaces()[$row['namespace']].':'.$row['page_title']
422
                : $row['page_title'];
423
            $pageData = [
424
                'deleted' => 'arc' === $row['type'],
425
                'namespace' => $row['namespace'],
426
                'page_title' => $row['page_title'],
427
                'full_page_title' => $fullPageTitle,
428
                'redirect' => (bool)$row['redirect'] || (bool)$row['was_redirect'],
429
                'timestamp' => $row['timestamp'],
430
                'rev_id' => $row['rev_id'],
431
                'rev_length' => $row['rev_length'],
432
                'length' => $row['length'],
433
            ];
434
435
            if ($row['recreated']) {
436
                $pageData['recreated'] = (bool)$row['recreated'];
437
            } else {
438
                // This is always NULL for live pages, in which case 'recreated' doesn't apply.
439
                unset($pageData['recreated']);
440
            }
441
442
            if ($this->project->hasPageAssessments()) {
443
                $attrs = $this->project
444
                    ->getPageAssessments()
445
                    ->getClassAttrs($row['pa_class'] ?: 'Unknown');
446
                $pageData['assessment'] = [
447
                    'class' => $row['pa_class'] ?: 'Unknown',
448
                    'badge' => $this->project
449
                        ->getPageAssessments()
450
                        ->getBadgeURL($row['pa_class'] ?: 'Unknown'),
451
                    'color' => $attrs['color'],
452
                    'category' => $attrs['category'],
453
                ];
454
            }
455
456
            $results[$row['namespace']][] = $pageData;
457
        }
458
459
        return $results;
460
    }
461
}
462