Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.83% |
69 / 72 |
|
83.33% |
5 / 6 |
CRAP | |
0.00% |
0 / 1 |
CommonsLinkChecker | |
95.83% |
69 / 72 |
|
83.33% |
5 / 6 |
28 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getSupportedContextTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getDefaultContextTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getSupportedEntityTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getCommonsNamespace | |
76.92% |
10 / 13 |
|
0.00% |
0 / 1 |
8.79 | |||
checkConstraint | |
100.00% |
41 / 41 |
|
100.00% |
1 / 1 |
11 | |||
checkConstraintParameters | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
commonsLinkIsWellFormed | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
valueIncludesNamespace | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker; |
6 | |
7 | use MediaWiki\Site\MediaWikiPageNameNormalizer; |
8 | use Wikibase\DataModel\Entity\ItemId; |
9 | use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup; |
10 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
11 | use WikibaseQuality\ConstraintReport\Constraint; |
12 | use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker; |
13 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context; |
14 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException; |
15 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser; |
16 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage; |
17 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult; |
18 | use WikibaseQuality\ConstraintReport\Role; |
19 | |
20 | /** |
21 | * @author BP2014N1 |
22 | * @license GPL-2.0-or-later |
23 | */ |
24 | class CommonsLinkChecker implements ConstraintChecker { |
25 | |
26 | /** |
27 | * @var ConstraintParameterParser |
28 | */ |
29 | private $constraintParameterParser; |
30 | |
31 | /** |
32 | * @var MediaWikiPageNameNormalizer |
33 | */ |
34 | private $pageNameNormalizer; |
35 | |
36 | /** |
37 | * @var PropertyDataTypeLookup |
38 | */ |
39 | private $propertyDatatypeLookup; |
40 | |
41 | public function __construct( |
42 | ConstraintParameterParser $constraintParameterParser, |
43 | MediaWikiPageNameNormalizer $pageNameNormalizer, |
44 | PropertyDataTypeLookup $propertyDatatypeLookup |
45 | ) { |
46 | $this->constraintParameterParser = $constraintParameterParser; |
47 | $this->pageNameNormalizer = $pageNameNormalizer; |
48 | $this->propertyDatatypeLookup = $propertyDatatypeLookup; |
49 | } |
50 | |
51 | /** |
52 | * @codeCoverageIgnore This method is purely declarative. |
53 | */ |
54 | public function getSupportedContextTypes(): array { |
55 | return self::ALL_CONTEXT_TYPES_SUPPORTED; |
56 | } |
57 | |
58 | /** |
59 | * @codeCoverageIgnore This method is purely declarative. |
60 | */ |
61 | public function getDefaultContextTypes(): array { |
62 | return Context::ALL_CONTEXT_TYPES; |
63 | } |
64 | |
65 | /** @codeCoverageIgnore This method is purely declarative. */ |
66 | public function getSupportedEntityTypes() { |
67 | return self::ALL_ENTITY_TYPES_SUPPORTED; |
68 | } |
69 | |
70 | /** |
71 | * Get the number of a namespace on Wikimedia Commons (commonswiki). |
72 | * All namespaces not known to this function will be looked up by the TitleParser. |
73 | * |
74 | * @return array first element is the namespace number (default namespace for TitleParser), |
75 | * second element is a string to prepend to the title before giving it to the TitleParser |
76 | */ |
77 | private function getCommonsNamespace( string $namespace ): array { |
78 | switch ( $namespace ) { |
79 | case '': |
80 | return [ NS_MAIN, '' ]; |
81 | // extra namespaces, see operations/mediawiki-config.git, |
82 | // wmf-config/InitialiseSettings.php, 'wgExtraNamespaces' key, 'commonswiki' subkey |
83 | case 'Creator': |
84 | return [ 100, '' ]; |
85 | case 'TimedText': |
86 | return [ 102, '' ]; |
87 | case 'Sequence': |
88 | return [ 104, '' ]; |
89 | case 'Institution': |
90 | return [ 106, '' ]; |
91 | // extension namespace, see mediawiki/extensions/JsonConfig.git, |
92 | // extension.json, 'namespaces' key, third element |
93 | case 'Data': |
94 | return [ 486, '' ]; |
95 | default: |
96 | return [ NS_MAIN, $namespace . ':' ]; |
97 | } |
98 | } |
99 | |
100 | /** |
101 | * Checks 'Commons link' constraint. |
102 | * |
103 | * @throws ConstraintParameterException |
104 | */ |
105 | public function checkConstraint( Context $context, Constraint $constraint ): CheckResult { |
106 | $constraintParameters = $constraint->getConstraintParameters(); |
107 | $constraintTypeItemId = $constraint->getConstraintTypeItemId(); |
108 | |
109 | $namespace = $this->constraintParameterParser->parseNamespaceParameter( |
110 | $constraintParameters, |
111 | $constraintTypeItemId |
112 | ); |
113 | |
114 | $snak = $context->getSnak(); |
115 | |
116 | if ( !$snak instanceof PropertyValueSnak ) { |
117 | // nothing to check |
118 | return new CheckResult( $context, $constraint, CheckResult::STATUS_COMPLIANCE ); |
119 | } |
120 | |
121 | $dataValue = $snak->getDataValue(); |
122 | |
123 | /* |
124 | * error handling: |
125 | * type of $dataValue for properties with 'Commons link' constraint has to be 'string' |
126 | * parameter $namespace can be null, works for commons galleries |
127 | */ |
128 | if ( $dataValue->getType() !== 'string' ) { |
129 | $message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-type' ) ) |
130 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
131 | ->withDataValueType( 'string' ); |
132 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, $message ); |
133 | } |
134 | |
135 | $commonsLink = $dataValue->getValue(); |
136 | if ( !$this->commonsLinkIsWellFormed( $commonsLink ) ) { |
137 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, |
138 | new ViolationMessage( 'wbqc-violation-message-commons-link-not-well-formed' ) ); |
139 | } |
140 | |
141 | $dataType = $this->propertyDatatypeLookup->getDataTypeIdForProperty( $snak->getPropertyId() ); |
142 | switch ( $dataType ) { |
143 | case 'geo-shape': |
144 | case 'tabular-data': |
145 | if ( strpos( $commonsLink, $namespace . ':' ) !== 0 ) { |
146 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, |
147 | new ViolationMessage( 'wbqc-violation-message-commons-link-not-well-formed' ) ); |
148 | } |
149 | $pageName = $commonsLink; |
150 | break; |
151 | default: |
152 | $pageName = $namespace ? $namespace . ':' . $commonsLink : $commonsLink; |
153 | break; |
154 | } |
155 | |
156 | $prefix = $this->getCommonsNamespace( $namespace )[1]; |
157 | $normalizedTitle = $this->pageNameNormalizer->normalizePageName( |
158 | $pageName, |
159 | 'https://commons.wikimedia.org/w/api.php' |
160 | ); |
161 | if ( $normalizedTitle === false ) { |
162 | if ( $this->valueIncludesNamespace( $commonsLink, $namespace ) ) { |
163 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, |
164 | new ViolationMessage( 'wbqc-violation-message-commons-link-not-well-formed' ) ); |
165 | } |
166 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, |
167 | new ViolationMessage( 'wbqc-violation-message-commons-link-no-existent' ) ); |
168 | } |
169 | |
170 | return new CheckResult( $context, $constraint, CheckResult::STATUS_COMPLIANCE, null ); |
171 | } |
172 | |
173 | public function checkConstraintParameters( Constraint $constraint ): array { |
174 | $constraintParameters = $constraint->getConstraintParameters(); |
175 | $constraintTypeItemId = $constraint->getConstraintTypeItemId(); |
176 | $exceptions = []; |
177 | try { |
178 | $this->constraintParameterParser->parseNamespaceParameter( |
179 | $constraintParameters, |
180 | $constraintTypeItemId |
181 | ); |
182 | } catch ( ConstraintParameterException $e ) { |
183 | $exceptions[] = $e; |
184 | } |
185 | return $exceptions; |
186 | } |
187 | |
188 | private function commonsLinkIsWellFormed( string $commonsLink ): bool { |
189 | $toReplace = [ "_", "%20" ]; |
190 | $compareString = trim( str_replace( $toReplace, '', $commonsLink ) ); |
191 | |
192 | return $commonsLink === $compareString; |
193 | } |
194 | |
195 | /** |
196 | * Checks whether the value of the statement already includes the namespace. |
197 | * This special case should be reported as “malformed title” instead of “title does not exist”. |
198 | */ |
199 | private function valueIncludesNamespace( string $value, string $namespace ): bool { |
200 | return $namespace !== '' && |
201 | strncasecmp( $value, $namespace . ':', strlen( $namespace ) + 1 ) === 0; |
202 | } |
203 | |
204 | } |