PageAssessments   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 36
eloc 89
c 2
b 1
f 1
dl 0
loc 274
rs 9.52

11 Methods

Rating   Name   Duplication   Size   Complexity  
B getAssessments() 0 43 8
A getBadgeURL() 0 18 4
A getImportanceFromAssessment() 0 25 6
A getClassAttrs() 0 4 2
A getAssessment() 0 14 4
A isEnabled() 0 3 1
A hasImportanceRatings() 0 4 1
A getClassFromAssessment() 0 26 5
A isSupportedNamespace() 0 3 2
A __construct() 0 4 1
A getConfig() 0 7 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace App\Model;
6
7
use App\Repository\PageAssessmentsRepository;
8
9
/**
10
 * A PageAssessments is responsible for handling logic around
11
 * processing page assessments of a given set of Pages on a Project.
12
 * @see https://www.mediawiki.org/wiki/Extension:PageAssessments
13
 */
14
class PageAssessments extends Model
15
{
16
    /**
17
     * Namespaces in which there may be page assessments.
18
     * @var int[]
19
     * @todo Always JOIN on page_assessments and only display the data if it exists.
20
     */
21
    public const SUPPORTED_NAMESPACES = [
22
        // Core namespaces
23
        ...[0, 4, 6, 10, 12, 14],
24
        // Custom namespaces
25
        ...[
26
            100, // Portal
27
            102, // WikiProject (T360774)
28
            108, // Book
29
            118, // Draft
30
            828, // Module
31
        ],
32
    ];
33
34
    /** @var array|null The assessments config. */
35
    protected ?array $config;
36
37
    /**
38
     * Create a new PageAssessments.
39
     * @param PageAssessmentsRepository $repository
40
     * @param Project $project
41
     */
42
    public function __construct(PageAssessmentsRepository $repository, Project $project)
43
    {
44
        $this->repository = $repository;
45
        $this->project = $project;
46
    }
47
48
    /**
49
     * Get page assessments configuration for the Project and cache in static variable.
50
     * @return string[][][]|null As defined in config/assessments.yaml, or false if none exists.
51
     */
52
    public function getConfig(): ?array
53
    {
54
        if (!isset($this->config)) {
55
            return $this->config = $this->repository->getConfig($this->project);
0 ignored issues
show
Bug introduced by
The method getConfig() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\PageAssessmentsRepository or App\Repository\AdminStatsRepository. ( Ignorable by Annotation )

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

55
            return $this->config = $this->repository->/** @scrutinizer ignore-call */ getConfig($this->project);
Loading history...
56
        }
57
58
        return $this->config;
59
    }
60
61
    /**
62
     * Is the given namespace supported in Page Assessments?
63
     * @param  int $nsId Namespace ID.
64
     * @return bool
65
     */
66
    public function isSupportedNamespace(int $nsId): bool
67
    {
68
        return $this->isEnabled() && in_array($nsId, self::SUPPORTED_NAMESPACES);
69
    }
70
71
    /**
72
     * Does this project support page assessments?
73
     * @return bool
74
     */
75
    public function isEnabled(): bool
76
    {
77
        return (bool)$this->getConfig();
78
    }
79
80
    /**
81
     * Does this project have importance ratings through Page Assessments?
82
     * @return bool
83
     */
84
    public function hasImportanceRatings(): bool
85
    {
86
        $config = $this->getConfig();
87
        return isset($config['importance']);
88
    }
89
90
    /**
91
     * Get the image URL of the badge for the given page assessment.
92
     * @param string|null $class Valid classification for project, such as 'Start', 'GA', etc. Null for unknown.
93
     * @param bool $filenameOnly Get only the filename, not the URL.
94
     * @return string URL to image.
95
     */
96
    public function getBadgeURL(?string $class, bool $filenameOnly = false): string
97
    {
98
        $config = $this->getConfig();
99
100
        if (isset($config['class'][$class])) {
101
            $url = 'https://upload.wikimedia.org/wikipedia/commons/'.$config['class'][$class]['badge'];
102
        } elseif (isset($config['class']['Unknown'])) {
103
            $url = 'https://upload.wikimedia.org/wikipedia/commons/'.$config['class']['Unknown']['badge'];
104
        } else {
105
            $url = "";
106
        }
107
108
        if ($filenameOnly) {
109
            $parts = explode('/', $url);
110
            return end($parts);
111
        }
112
113
        return $url;
114
    }
115
116
    /**
117
     * Get the single overall assessment of the given page.
118
     * @param Page $page
119
     * @return string[]|false With keys 'value' and 'badge', or false if assessments are unsupported.
120
     */
121
    public function getAssessment(Page $page)
122
    {
123
        if (!$this->isEnabled() || !$this->isSupportedNamespace($page->getNamespace())) {
124
            return false;
125
        }
126
127
        $data = $this->repository->getAssessments($page, true);
0 ignored issues
show
Bug introduced by
The method getAssessments() does not exist on App\Repository\Repository. It seems like you code against a sub-type of App\Repository\Repository such as App\Repository\PageAssessmentsRepository. ( Ignorable by Annotation )

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

127
        /** @scrutinizer ignore-call */ 
128
        $data = $this->repository->getAssessments($page, true);
Loading history...
128
129
        if (isset($data[0])) {
130
            return $this->getClassFromAssessment($data[0]);
131
        }
132
133
        // 'Unknown' class.
134
        return $this->getClassFromAssessment(['class' => '']);
135
    }
136
137
    /**
138
     * Get assessments for the given Page.
139
     * @param Page $page
140
     * @return string[]|null null if unsupported, or array in the format of:
141
     *         [
142
     *             'assessment' => [
143
     *                 // overall assessment
144
     *                 'badge' => 'https://upload.wikimedia.org/wikipedia/commons/b/bc/Featured_article_star.svg',
145
     *                 'color' => '#9CBDFF',
146
     *                 'category' => 'Category:FA-Class articles',
147
     *                 'class' => 'FA',
148
     *             ]
149
     *             'wikiprojects' => [
150
     *                 'Biography' => [
151
     *                     'assessment' => 'C',
152
     *                     'badge' => 'url',
153
     *                 ],
154
     *                 ...
155
     *             ],
156
     *             'wikiproject_prefix' => 'Wikipedia:WikiProject_',
157
     *         ]
158
     * @todo Add option to get ORES prediction.
159
     */
160
    public function getAssessments(Page $page): ?array
161
    {
162
        if (!$this->isEnabled() || !$this->isSupportedNamespace($page->getNamespace())) {
163
            return null;
164
        }
165
166
        $config = $this->getConfig();
167
        $data = $this->repository->getAssessments($page);
168
169
        // Set the default decorations for the overall assessment.
170
        // This will be replaced with the first valid class defined for any WikiProject.
171
        $overallAssessment = array_merge(['class' => '???'], $config['class']['Unknown']);
172
        $overallAssessment['badge'] = $this->getBadgeURL($overallAssessment['badge']);
173
174
        $decoratedAssessments = [];
175
176
        // Go through each raw assessment data from the database, and decorate them
177
        // with the colours and badges as retrieved from the XTools assessments config.
178
        foreach ($data as $assessment) {
179
            $assessment['class'] = $this->getClassFromAssessment($assessment);
180
181
            // Replace the overall assessment with the first non-empty assessment.
182
            if ('???' === $overallAssessment['class'] && '???' !== $assessment['class']['value']) {
183
                $overallAssessment['class'] = $assessment['class']['value'];
184
                $overallAssessment['color'] = $assessment['class']['color'];
185
                $overallAssessment['category'] = $assessment['class']['category'];
186
                $overallAssessment['badge'] = $assessment['class']['badge'];
187
            }
188
189
            $assessment['importance'] = $this->getImportanceFromAssessment($assessment);
190
191
            $decoratedAssessments[$assessment['wikiproject']] = $assessment;
192
        }
193
194
        // Don't show 'Unknown' assessment outside of the mainspace.
195
        if (0 !== $page->getNamespace() && '???' === $overallAssessment['class']) {
196
            return [];
197
        }
198
199
        return [
200
            'assessment' => $overallAssessment,
201
            'wikiprojects' => $decoratedAssessments,
202
            'wikiproject_prefix' => $config['wikiproject_prefix'],
203
        ];
204
    }
205
206
    /**
207
     * Get the class attributes for the given class value, as fetched from the config.
208
     * @param string|null $classValue Such as 'FA', 'GA', 'Start', etc.
209
     * @return string[] Attributes as fetched from the XTools assessments config.
210
     */
211
    public function getClassAttrs(?string $classValue): array
212
    {
213
        $classValue = $classValue ?: 'Unknown';
214
        return $this->getConfig()['class'][$classValue] ?? $this->getConfig()['class']['Unknown'];
215
    }
216
217
    /**
218
     * Get the properties of the assessment class, including:
219
     *   'value' (class name in plain text),
220
     *   'color' (as hex RGB),
221
     *   'badge' (full URL to assessment badge),
222
     *   'category' (wiki path to related class category).
223
     * @param array $assessment
224
     * @return array Decorated class assessment.
225
     */
226
    private function getClassFromAssessment(array $assessment): array
227
    {
228
        $classValue = $assessment['class'];
229
230
        // Use ??? as the presented value when the class is unknown or is not defined in the config
231
        if ('Unknown' === $classValue || '' === $classValue || !isset($this->getConfig()['class'][$classValue])) {
232
            return array_merge($this->getClassAttrs('Unknown'), [
233
                'value' => '???',
234
                'badge' => $this->getBadgeURL('Unknown'),
235
            ]);
236
        }
237
238
        // Known class.
239
        $classAttrs = $this->getClassAttrs($classValue);
240
        $class = [
241
            'value' => $classValue,
242
            'color' => $classAttrs['color'],
243
            'category' => $classAttrs['category'],
244
        ];
245
246
        // add full URL to badge icon
247
        if ('' !== $classAttrs['badge']) {
248
            $class['badge'] = $this->getBadgeURL($classValue);
249
        }
250
251
        return $class;
252
    }
253
254
    /**
255
     * Get the properties of the assessment importance, including:
256
     *   'value' (importance in plain text),
257
     *   'color' (as hex RGB),
258
     *   'weight' (integer, 0 is lowest importance),
259
     *   'category' (wiki path to the related importance category).
260
     * @param  array $assessment
261
     * @return array|null Decorated importance assessment. Null if importance could not be determined.
262
     */
263
    private function getImportanceFromAssessment(array $assessment): ?array
264
    {
265
        $importanceValue = $assessment['importance'];
266
267
        if ('' == $importanceValue && !isset($this->getConfig()['importance'])) {
268
            return null;
269
        }
270
271
        // Known importance level.
272
        $importanceUnknown = 'Unknown' === $importanceValue || '' === $importanceValue;
273
274
        if ($importanceUnknown || !isset($this->getConfig()['importance'][$importanceValue])) {
275
            $importanceAttrs = $this->getConfig()['importance']['Unknown'];
276
277
            return array_merge($importanceAttrs, [
278
                'value' => '???',
279
                'category' => $importanceAttrs['category'],
280
            ]);
281
        } else {
282
            $importanceAttrs = $this->getConfig()['importance'][$importanceValue];
283
            return [
284
                'value' => $importanceValue,
285
                'color' => $importanceAttrs['color'],
286
                'weight' => $importanceAttrs['weight'], // numerical weight for sorting purposes
287
                'category' => $importanceAttrs['category'],
288
            ];
289
        }
290
    }
291
}
292