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