Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.75% |
435 / 464 |
|
87.80% |
36 / 41 |
CRAP | |
0.00% |
0 / 1 |
ConstraintParameterParser | |
93.75% |
435 / 464 |
|
87.80% |
36 / 41 |
121.40 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
checkError | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
requireSingleParameter | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
requireValueParameter | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
parseEntityIdParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
parseClassParameter | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
parseRelationParameter | |
76.19% |
16 / 21 |
|
0.00% |
0 / 1 |
3.12 | |||
parsePropertyIdParameter | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
parsePropertyParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
parseItemIdParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
parseItemsParameter | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
8 | |||
parseItemIdsParameter | |
69.23% |
9 / 13 |
|
0.00% |
0 / 1 |
2.12 | |||
mapItemId | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
parsePropertiesParameter | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
5 | |||
parseValueOrNoValueParameter | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
parseValueOrNoValueOrNowParameter | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
exactlyOneQuantityWithUnit | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
parseRangeParameter | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
10 | |||
parseQuantityRangeParameter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
parseTimeRangeParameter | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
parseLanguageParameter | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
parseStringParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
parseNamespaceParameter | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
parseFormatParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
parseExceptionParameter | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
parseConstraintStatusParameter | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
5 | |||
requireMonolingualTextParameter | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
parseMultilingualTextParameter | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
3 | |||
parseSyntaxClarificationParameter | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
parseConstraintClarificationParameter | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
parseConstraintScopeParameters | |
100.00% |
41 / 41 |
|
100.00% |
1 / 1 |
6 | |||
checkValidScope | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
parseUnitParameter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseUnitItem | |
66.67% |
10 / 15 |
|
0.00% |
0 / 1 |
4.59 | |||
parseUnitsParameter | |
78.57% |
11 / 14 |
|
0.00% |
0 / 1 |
5.25 | |||
getEntityTypeMapping | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
parseEntityTypesParameter | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
3 | |||
parseSeparatorsParameter | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
getConstraintScopeContextTypeMapping | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getPropertyScopeContextTypeMapping | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
parsePropertyScopeParameter | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper; |
6 | |
7 | use DataValues\DataValue; |
8 | use DataValues\MonolingualTextValue; |
9 | use DataValues\MultilingualTextValue; |
10 | use DataValues\StringValue; |
11 | use DataValues\UnboundedQuantityValue; |
12 | use LogicException; |
13 | use MediaWiki\Config\Config; |
14 | use Wikibase\DataModel\Deserializers\DeserializerFactory; |
15 | use Wikibase\DataModel\Deserializers\SnakDeserializer; |
16 | use Wikibase\DataModel\Entity\EntityId; |
17 | use Wikibase\DataModel\Entity\EntityIdValue; |
18 | use Wikibase\DataModel\Entity\ItemId; |
19 | use Wikibase\DataModel\Entity\NumericPropertyId; |
20 | use Wikibase\DataModel\Entity\PropertyId; |
21 | use Wikibase\DataModel\Snak\PropertyNoValueSnak; |
22 | use Wikibase\DataModel\Snak\PropertySomeValueSnak; |
23 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
24 | use Wikibase\DataModel\Snak\Snak; |
25 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context; |
26 | use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue; |
27 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage; |
28 | use WikibaseQuality\ConstraintReport\Role; |
29 | |
30 | /** |
31 | * Helper for parsing constraint parameters |
32 | * that were imported from constraint statements. |
33 | * |
34 | * All public methods of this class expect constraint parameters |
35 | * (see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()}) |
36 | * and return parameter objects or throw {@link ConstraintParameterException}s. |
37 | * The results are used by the checkers, |
38 | * which may include rendering them into violation messages. |
39 | * |
40 | * @author Lucas Werkmeister |
41 | * @license GPL-2.0-or-later |
42 | */ |
43 | class ConstraintParameterParser { |
44 | |
45 | private Config $config; |
46 | private SnakDeserializer $snakDeserializer; |
47 | private string $unitItemConceptBaseUri; |
48 | |
49 | /** |
50 | * @param Config $config |
51 | * contains entity IDs used in constraint parameters (constraint statement qualifiers) |
52 | * @param DeserializerFactory $factory |
53 | * used to parse constraint statement qualifiers into constraint parameters |
54 | * @param string $unitItemConceptBaseUri |
55 | * concept base URI of items used for units |
56 | */ |
57 | public function __construct( |
58 | Config $config, |
59 | DeserializerFactory $factory, |
60 | string $unitItemConceptBaseUri |
61 | ) { |
62 | $this->config = $config; |
63 | $this->snakDeserializer = $factory->newSnakDeserializer(); |
64 | $this->unitItemConceptBaseUri = $unitItemConceptBaseUri; |
65 | } |
66 | |
67 | /** |
68 | * Check if any errors are recorded in the constraint parameters. |
69 | * @throws ConstraintParameterException |
70 | */ |
71 | public function checkError( array $parameters ): void { |
72 | if ( array_key_exists( '@error', $parameters ) ) { |
73 | $error = $parameters['@error']; |
74 | if ( array_key_exists( 'toolong', $error ) && $error['toolong'] ) { |
75 | $msg = 'wbqc-violation-message-parameters-error-toolong'; |
76 | } else { |
77 | $msg = 'wbqc-violation-message-parameters-error-unknown'; |
78 | } |
79 | throw new ConstraintParameterException( new ViolationMessage( $msg ) ); |
80 | } |
81 | } |
82 | |
83 | /** |
84 | * Require that $parameters contains exactly one $parameterId parameter. |
85 | * @throws ConstraintParameterException |
86 | */ |
87 | private function requireSingleParameter( array $parameters, string $parameterId ): void { |
88 | if ( count( $parameters[$parameterId] ) !== 1 ) { |
89 | throw new ConstraintParameterException( |
90 | ( new ViolationMessage( 'wbqc-violation-message-parameter-single' ) ) |
91 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
92 | ); |
93 | } |
94 | } |
95 | |
96 | /** |
97 | * Require that $snak is a {@link PropertyValueSnak}. |
98 | * @throws ConstraintParameterException |
99 | */ |
100 | private function requireValueParameter( Snak $snak, string $parameterId ): void { |
101 | if ( !( $snak instanceof PropertyValueSnak ) ) { |
102 | throw new ConstraintParameterException( |
103 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) |
104 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
105 | ); |
106 | } |
107 | } |
108 | |
109 | /** |
110 | * Parse a single entity ID parameter. |
111 | * @throws ConstraintParameterException |
112 | */ |
113 | private function parseEntityIdParameter( array $snakSerialization, string $parameterId ): EntityId { |
114 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
115 | $this->requireValueParameter( $snak, $parameterId ); |
116 | $value = $snak->getDataValue(); |
117 | if ( $value instanceof EntityIdValue ) { |
118 | return $value->getEntityId(); |
119 | } else { |
120 | throw new ConstraintParameterException( |
121 | ( new ViolationMessage( 'wbqc-violation-message-parameter-entity' ) ) |
122 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
123 | ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) |
124 | ); |
125 | } |
126 | } |
127 | |
128 | /** |
129 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
130 | * @param string $constraintTypeItemId used in error messages |
131 | * @throws ConstraintParameterException if the parameter is invalid or missing |
132 | * @return string[] class entity ID serializations |
133 | */ |
134 | public function parseClassParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
135 | $this->checkError( $constraintParameters ); |
136 | $classId = $this->config->get( 'WBQualityConstraintsClassId' ); |
137 | if ( !array_key_exists( $classId, $constraintParameters ) ) { |
138 | throw new ConstraintParameterException( |
139 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
140 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
141 | ->withEntityId( new NumericPropertyId( $classId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
142 | ); |
143 | } |
144 | |
145 | $classes = []; |
146 | foreach ( $constraintParameters[$classId] as $class ) { |
147 | $classes[] = $this->parseEntityIdParameter( $class, $classId )->getSerialization(); |
148 | } |
149 | return $classes; |
150 | } |
151 | |
152 | /** |
153 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
154 | * @param string $constraintTypeItemId used in error messages |
155 | * @throws ConstraintParameterException if the parameter is invalid or missing |
156 | * @return string 'instance', 'subclass', or 'instanceOrSubclass' |
157 | */ |
158 | public function parseRelationParameter( array $constraintParameters, string $constraintTypeItemId ): string { |
159 | $this->checkError( $constraintParameters ); |
160 | $relationId = $this->config->get( 'WBQualityConstraintsRelationId' ); |
161 | if ( !array_key_exists( $relationId, $constraintParameters ) ) { |
162 | throw new ConstraintParameterException( |
163 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
164 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
165 | ->withEntityId( new NumericPropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
166 | ); |
167 | } |
168 | |
169 | $this->requireSingleParameter( $constraintParameters, $relationId ); |
170 | $relationEntityId = $this->parseEntityIdParameter( $constraintParameters[$relationId][0], $relationId ); |
171 | if ( !( $relationEntityId instanceof ItemId ) ) { |
172 | throw new ConstraintParameterException( |
173 | ( new ViolationMessage( 'wbqc-violation-message-parameter-item' ) ) |
174 | ->withEntityId( new NumericPropertyId( $relationId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
175 | ->withDataValue( new EntityIdValue( $relationEntityId ), Role::CONSTRAINT_PARAMETER_VALUE ) |
176 | ); |
177 | } |
178 | return $this->mapItemId( $relationEntityId, [ |
179 | $this->config->get( 'WBQualityConstraintsInstanceOfRelationId' ) => 'instance', |
180 | $this->config->get( 'WBQualityConstraintsSubclassOfRelationId' ) => 'subclass', |
181 | $this->config->get( 'WBQualityConstraintsInstanceOrSubclassOfRelationId' ) => 'instanceOrSubclass', |
182 | ], $relationId ); |
183 | } |
184 | |
185 | /** |
186 | * Parse a single property ID parameter. |
187 | * @param array $snakSerialization |
188 | * @param string $parameterId used in error messages |
189 | * @throws ConstraintParameterException |
190 | * @return PropertyId |
191 | */ |
192 | private function parsePropertyIdParameter( array $snakSerialization, string $parameterId ): PropertyId { |
193 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
194 | $this->requireValueParameter( $snak, $parameterId ); |
195 | $value = $snak->getDataValue(); |
196 | if ( $value instanceof EntityIdValue ) { |
197 | $id = $value->getEntityId(); |
198 | if ( $id instanceof PropertyId ) { |
199 | return $id; |
200 | } |
201 | } |
202 | throw new ConstraintParameterException( |
203 | ( new ViolationMessage( 'wbqc-violation-message-parameter-property' ) ) |
204 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
205 | ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) |
206 | ); |
207 | } |
208 | |
209 | /** |
210 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
211 | * @param string $constraintTypeItemId used in error messages |
212 | * |
213 | * @throws ConstraintParameterException if the parameter is invalid or missing |
214 | * @return PropertyId |
215 | */ |
216 | public function parsePropertyParameter( array $constraintParameters, string $constraintTypeItemId ): PropertyId { |
217 | $this->checkError( $constraintParameters ); |
218 | $propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' ); |
219 | if ( !array_key_exists( $propertyId, $constraintParameters ) ) { |
220 | throw new ConstraintParameterException( |
221 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
222 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
223 | ->withEntityId( new NumericPropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
224 | ); |
225 | } |
226 | |
227 | $this->requireSingleParameter( $constraintParameters, $propertyId ); |
228 | return $this->parsePropertyIdParameter( $constraintParameters[$propertyId][0], $propertyId ); |
229 | } |
230 | |
231 | private function parseItemIdParameter( PropertyValueSnak $snak, string $parameterId ): ItemIdSnakValue { |
232 | $dataValue = $snak->getDataValue(); |
233 | if ( $dataValue instanceof EntityIdValue ) { |
234 | $entityId = $dataValue->getEntityId(); |
235 | if ( $entityId instanceof ItemId ) { |
236 | return ItemIdSnakValue::fromItemId( $entityId ); |
237 | } |
238 | } |
239 | throw new ConstraintParameterException( |
240 | ( new ViolationMessage( 'wbqc-violation-message-parameter-item' ) ) |
241 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
242 | ->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE ) |
243 | ); |
244 | } |
245 | |
246 | /** |
247 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
248 | * @param string $constraintTypeItemId used in error messages |
249 | * @param bool $required whether the parameter is required (error if absent) or not ([] if absent) |
250 | * @param string|null $parameterId the property ID to use, defaults to 'qualifier of property constraint' |
251 | * @throws ConstraintParameterException if the parameter is invalid or missing |
252 | * @return ItemIdSnakValue[] array of values |
253 | */ |
254 | public function parseItemsParameter( |
255 | array $constraintParameters, |
256 | string $constraintTypeItemId, |
257 | bool $required, |
258 | string $parameterId = null |
259 | ): array { |
260 | $this->checkError( $constraintParameters ); |
261 | if ( $parameterId === null ) { |
262 | $parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
263 | } |
264 | if ( !array_key_exists( $parameterId, $constraintParameters ) ) { |
265 | if ( $required ) { |
266 | throw new ConstraintParameterException( |
267 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
268 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
269 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
270 | ); |
271 | } else { |
272 | return []; |
273 | } |
274 | } |
275 | |
276 | $values = []; |
277 | foreach ( $constraintParameters[$parameterId] as $parameter ) { |
278 | $snak = $this->snakDeserializer->deserialize( $parameter ); |
279 | switch ( true ) { |
280 | case $snak instanceof PropertyValueSnak: |
281 | $values[] = $this->parseItemIdParameter( $snak, $parameterId ); |
282 | break; |
283 | case $snak instanceof PropertySomeValueSnak: |
284 | $values[] = ItemIdSnakValue::someValue(); |
285 | break; |
286 | case $snak instanceof PropertyNoValueSnak: |
287 | $values[] = ItemIdSnakValue::noValue(); |
288 | break; |
289 | } |
290 | } |
291 | return $values; |
292 | } |
293 | |
294 | /** |
295 | * Parse a parameter that must contain item IDs. |
296 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
297 | * @param string $constraintTypeItemId used in error messages |
298 | * @param bool $required whether the parameter is required (error if absent) or not ([] if absent) |
299 | * @param string $parameterId the property ID to use |
300 | * @throws ConstraintParameterException |
301 | * @return ItemId[] |
302 | */ |
303 | private function parseItemIdsParameter( |
304 | array $constraintParameters, |
305 | string $constraintTypeItemId, |
306 | bool $required, |
307 | string $parameterId |
308 | ): array { |
309 | return array_map( static function ( ItemIdSnakValue $value ) use ( $parameterId ): ItemId { |
310 | if ( $value->isValue() ) { |
311 | return $value->getItemId(); |
312 | } else { |
313 | throw new ConstraintParameterException( |
314 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) |
315 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
316 | ); |
317 | } |
318 | }, $this->parseItemsParameter( |
319 | $constraintParameters, |
320 | $constraintTypeItemId, |
321 | $required, |
322 | $parameterId |
323 | ) ); |
324 | } |
325 | |
326 | /** |
327 | * Map an item ID parameter to a well-known value or throw an appropriate error. |
328 | * @throws ConstraintParameterException |
329 | * @return mixed elements of $mapping |
330 | */ |
331 | private function mapItemId( ItemId $itemId, array $mapping, string $parameterId ) { |
332 | $serialization = $itemId->getSerialization(); |
333 | if ( array_key_exists( $serialization, $mapping ) ) { |
334 | return $mapping[$serialization]; |
335 | } else { |
336 | $allowed = array_map( static function ( $id ) { |
337 | return new ItemId( $id ); |
338 | }, array_keys( $mapping ) ); |
339 | throw new ConstraintParameterException( |
340 | ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) |
341 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
342 | ->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE ) |
343 | ); |
344 | } |
345 | } |
346 | |
347 | /** |
348 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
349 | * @param string $constraintTypeItemId used in error messages |
350 | * @throws ConstraintParameterException if the parameter is invalid or missing |
351 | * @return PropertyId[] |
352 | */ |
353 | public function parsePropertiesParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
354 | $this->checkError( $constraintParameters ); |
355 | $propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' ); |
356 | if ( !array_key_exists( $propertyId, $constraintParameters ) ) { |
357 | throw new ConstraintParameterException( |
358 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
359 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
360 | ->withEntityId( new NumericPropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
361 | ); |
362 | } |
363 | |
364 | $parameters = $constraintParameters[$propertyId]; |
365 | if ( count( $parameters ) === 1 && |
366 | $this->snakDeserializer->deserialize( $parameters[0] ) instanceof PropertyNoValueSnak |
367 | ) { |
368 | return []; |
369 | } |
370 | |
371 | $properties = []; |
372 | foreach ( $parameters as $parameter ) { |
373 | $properties[] = $this->parsePropertyIdParameter( $parameter, $propertyId ); |
374 | } |
375 | return $properties; |
376 | } |
377 | |
378 | /** |
379 | * @throws ConstraintParameterException |
380 | */ |
381 | private function parseValueOrNoValueParameter( array $snakSerialization, string $parameterId ): ?DataValue { |
382 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
383 | if ( $snak instanceof PropertyValueSnak ) { |
384 | return $snak->getDataValue(); |
385 | } elseif ( $snak instanceof PropertyNoValueSnak ) { |
386 | return null; |
387 | } else { |
388 | throw new ConstraintParameterException( |
389 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) |
390 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
391 | ); |
392 | } |
393 | } |
394 | |
395 | private function parseValueOrNoValueOrNowParameter( array $snakSerialization, string $parameterId ): ?DataValue { |
396 | try { |
397 | return $this->parseValueOrNoValueParameter( $snakSerialization, $parameterId ); |
398 | } catch ( ConstraintParameterException $e ) { |
399 | // unknown value means “now” |
400 | return new NowValue(); |
401 | } |
402 | } |
403 | |
404 | /** |
405 | * Checks whether there is exactly one non-null quantity with the given unit. |
406 | */ |
407 | private function exactlyOneQuantityWithUnit( ?DataValue $min, ?DataValue $max, string $unit ): bool { |
408 | if ( !( $min instanceof UnboundedQuantityValue ) || |
409 | !( $max instanceof UnboundedQuantityValue ) |
410 | ) { |
411 | return false; |
412 | } |
413 | |
414 | return ( $min->getUnit() === $unit ) !== ( $max->getUnit() === $unit ); |
415 | } |
416 | |
417 | /** |
418 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
419 | * @param string $minimumId |
420 | * @param string $maximumId |
421 | * @param string $constraintTypeItemId used in error messages |
422 | * @param string $type 'quantity' or 'time' (can be data type or data value type) |
423 | * |
424 | * @throws ConstraintParameterException if the parameter is invalid or missing |
425 | * @return DataValue[] if the parameter is invalid or missing |
426 | */ |
427 | private function parseRangeParameter( |
428 | array $constraintParameters, |
429 | string $minimumId, |
430 | string $maximumId, |
431 | string $constraintTypeItemId, |
432 | string $type |
433 | ): array { |
434 | $this->checkError( $constraintParameters ); |
435 | if ( !array_key_exists( $minimumId, $constraintParameters ) || |
436 | !array_key_exists( $maximumId, $constraintParameters ) |
437 | ) { |
438 | throw new ConstraintParameterException( |
439 | ( new ViolationMessage( 'wbqc-violation-message-range-parameters-needed' ) ) |
440 | ->withDataValueType( $type ) |
441 | ->withEntityId( new NumericPropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
442 | ->withEntityId( new NumericPropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
443 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
444 | ); |
445 | } |
446 | |
447 | $this->requireSingleParameter( $constraintParameters, $minimumId ); |
448 | $this->requireSingleParameter( $constraintParameters, $maximumId ); |
449 | $parseFunction = $type === 'time' ? 'parseValueOrNoValueOrNowParameter' : 'parseValueOrNoValueParameter'; |
450 | $min = $this->$parseFunction( $constraintParameters[$minimumId][0], $minimumId ); |
451 | $max = $this->$parseFunction( $constraintParameters[$maximumId][0], $maximumId ); |
452 | |
453 | $yearUnit = $this->config->get( 'WBQualityConstraintsYearUnit' ); |
454 | if ( $this->exactlyOneQuantityWithUnit( $min, $max, $yearUnit ) ) { |
455 | throw new ConstraintParameterException( |
456 | new ViolationMessage( 'wbqc-violation-message-range-parameters-one-year' ) |
457 | ); |
458 | } |
459 | if ( ( $min === null && $max === null ) || |
460 | ( $min !== null && $max !== null && $min->equals( $max ) ) |
461 | ) { |
462 | throw new ConstraintParameterException( |
463 | ( new ViolationMessage( 'wbqc-violation-message-range-parameters-same' ) ) |
464 | ->withEntityId( new NumericPropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
465 | ->withEntityId( new NumericPropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
466 | ); |
467 | } |
468 | |
469 | return [ $min, $max ]; |
470 | } |
471 | |
472 | /** |
473 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
474 | * @param string $constraintTypeItemId used in error messages |
475 | * |
476 | * @throws ConstraintParameterException if the parameter is invalid or missing |
477 | * @return DataValue[] a pair of two data values, either of which may be null to signify an open range |
478 | */ |
479 | public function parseQuantityRangeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
480 | return $this->parseRangeParameter( |
481 | $constraintParameters, |
482 | $this->config->get( 'WBQualityConstraintsMinimumQuantityId' ), |
483 | $this->config->get( 'WBQualityConstraintsMaximumQuantityId' ), |
484 | $constraintTypeItemId, |
485 | 'quantity' |
486 | ); |
487 | } |
488 | |
489 | /** |
490 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
491 | * @param string $constraintTypeItemId used in error messages |
492 | * |
493 | * @throws ConstraintParameterException if the parameter is invalid or missing |
494 | * @return DataValue[] a pair of two data values, either of which may be null to signify an open range |
495 | */ |
496 | public function parseTimeRangeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
497 | return $this->parseRangeParameter( |
498 | $constraintParameters, |
499 | $this->config->get( 'WBQualityConstraintsMinimumDateId' ), |
500 | $this->config->get( 'WBQualityConstraintsMaximumDateId' ), |
501 | $constraintTypeItemId, |
502 | 'time' |
503 | ); |
504 | } |
505 | |
506 | /** |
507 | * Parse language parameter. |
508 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
509 | * @param string $constraintTypeItemId used in error messages |
510 | * @throws ConstraintParameterException |
511 | * @return string[] |
512 | */ |
513 | public function parseLanguageParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
514 | $this->checkError( $constraintParameters ); |
515 | $languagePropertyId = $this->config->get( 'WBQualityConstraintsLanguagePropertyId' ); |
516 | if ( !array_key_exists( $languagePropertyId, $constraintParameters ) ) { |
517 | throw new ConstraintParameterException( |
518 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
519 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
520 | ->withEntityId( new NumericPropertyId( $languagePropertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
521 | ); |
522 | } |
523 | |
524 | $languages = []; |
525 | foreach ( $constraintParameters[$languagePropertyId] as $snak ) { |
526 | $languages[] = $this->parseStringParameter( $snak, $languagePropertyId ); |
527 | } |
528 | return $languages; |
529 | } |
530 | |
531 | /** |
532 | * Parse a single string parameter. |
533 | * @throws ConstraintParameterException |
534 | */ |
535 | private function parseStringParameter( array $snakSerialization, string $parameterId ): string { |
536 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
537 | $this->requireValueParameter( $snak, $parameterId ); |
538 | $value = $snak->getDataValue(); |
539 | if ( $value instanceof StringValue ) { |
540 | return $value->getValue(); |
541 | } else { |
542 | throw new ConstraintParameterException( |
543 | ( new ViolationMessage( 'wbqc-violation-message-parameter-string' ) ) |
544 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
545 | ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) |
546 | ); |
547 | } |
548 | } |
549 | |
550 | /** |
551 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
552 | * @param string $constraintTypeItemId used in error messages |
553 | * @throws ConstraintParameterException if the parameter is invalid or missing |
554 | * @return string |
555 | */ |
556 | public function parseNamespaceParameter( array $constraintParameters, string $constraintTypeItemId ): string { |
557 | $this->checkError( $constraintParameters ); |
558 | $namespaceId = $this->config->get( 'WBQualityConstraintsNamespaceId' ); |
559 | if ( !array_key_exists( $namespaceId, $constraintParameters ) ) { |
560 | return ''; |
561 | } |
562 | |
563 | $this->requireSingleParameter( $constraintParameters, $namespaceId ); |
564 | return $this->parseStringParameter( $constraintParameters[$namespaceId][0], $namespaceId ); |
565 | } |
566 | |
567 | /** |
568 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
569 | * @param string $constraintTypeItemId used in error messages |
570 | * @throws ConstraintParameterException if the parameter is invalid or missing |
571 | * @return string |
572 | */ |
573 | public function parseFormatParameter( array $constraintParameters, string $constraintTypeItemId ): string { |
574 | $this->checkError( $constraintParameters ); |
575 | $formatId = $this->config->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); |
576 | if ( !array_key_exists( $formatId, $constraintParameters ) ) { |
577 | throw new ConstraintParameterException( |
578 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
579 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
580 | ->withEntityId( new NumericPropertyId( $formatId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
581 | ); |
582 | } |
583 | |
584 | $this->requireSingleParameter( $constraintParameters, $formatId ); |
585 | return $this->parseStringParameter( $constraintParameters[$formatId][0], $formatId ); |
586 | } |
587 | |
588 | /** |
589 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
590 | * @throws ConstraintParameterException if the parameter is invalid |
591 | * @return EntityId[] |
592 | */ |
593 | public function parseExceptionParameter( array $constraintParameters ): array { |
594 | $this->checkError( $constraintParameters ); |
595 | $exceptionId = $this->config->get( 'WBQualityConstraintsExceptionToConstraintId' ); |
596 | if ( !array_key_exists( $exceptionId, $constraintParameters ) ) { |
597 | return []; |
598 | } |
599 | |
600 | return array_map( |
601 | function ( $snakSerialization ) use ( $exceptionId ) { |
602 | return $this->parseEntityIdParameter( $snakSerialization, $exceptionId ); |
603 | }, |
604 | $constraintParameters[$exceptionId] |
605 | ); |
606 | } |
607 | |
608 | /** |
609 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
610 | * @throws ConstraintParameterException if the parameter is invalid |
611 | * @return string|null 'mandatory', 'suggestion' or null |
612 | */ |
613 | public function parseConstraintStatusParameter( array $constraintParameters ): ?string { |
614 | $this->checkError( $constraintParameters ); |
615 | $constraintStatusId = $this->config->get( 'WBQualityConstraintsConstraintStatusId' ); |
616 | if ( !array_key_exists( $constraintStatusId, $constraintParameters ) ) { |
617 | return null; |
618 | } |
619 | |
620 | $mandatoryId = $this->config->get( 'WBQualityConstraintsMandatoryConstraintId' ); |
621 | $supportedStatuses = [ new ItemId( $mandatoryId ) ]; |
622 | if ( $this->config->get( 'WBQualityConstraintsEnableSuggestionConstraintStatus' ) ) { |
623 | $suggestionId = $this->config->get( 'WBQualityConstraintsSuggestionConstraintId' ); |
624 | $supportedStatuses[] = new ItemId( $suggestionId ); |
625 | } else { |
626 | $suggestionId = null; |
627 | } |
628 | |
629 | $this->requireSingleParameter( $constraintParameters, $constraintStatusId ); |
630 | $snak = $this->snakDeserializer->deserialize( $constraintParameters[$constraintStatusId][0] ); |
631 | $this->requireValueParameter( $snak, $constraintStatusId ); |
632 | '@phan-var \Wikibase\DataModel\Snak\PropertyValueSnak $snak'; |
633 | $dataValue = $snak->getDataValue(); |
634 | '@phan-var EntityIdValue $dataValue'; |
635 | $entityId = $dataValue->getEntityId(); |
636 | $statusId = $entityId->getSerialization(); |
637 | |
638 | if ( $statusId === $mandatoryId ) { |
639 | return 'mandatory'; |
640 | } elseif ( $statusId === $suggestionId ) { |
641 | return 'suggestion'; |
642 | } else { |
643 | throw new ConstraintParameterException( |
644 | ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) |
645 | ->withEntityId( new NumericPropertyId( $constraintStatusId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
646 | ->withEntityIdList( |
647 | $supportedStatuses, |
648 | Role::CONSTRAINT_PARAMETER_VALUE |
649 | ) |
650 | ); |
651 | } |
652 | } |
653 | |
654 | /** |
655 | * Require that $dataValue is a {@link MonolingualTextValue}. |
656 | * @throws ConstraintParameterException |
657 | */ |
658 | private function requireMonolingualTextParameter( DataValue $dataValue, string $parameterId ): void { |
659 | if ( !( $dataValue instanceof MonolingualTextValue ) ) { |
660 | throw new ConstraintParameterException( |
661 | ( new ViolationMessage( 'wbqc-violation-message-parameter-monolingualtext' ) ) |
662 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
663 | ->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE ) |
664 | ); |
665 | } |
666 | } |
667 | |
668 | /** |
669 | * Parse a series of monolingual text snaks (serialized) into a map from language code to string. |
670 | * |
671 | * @throws ConstraintParameterException if invalid snaks are found or a language has multiple texts |
672 | */ |
673 | private function parseMultilingualTextParameter( array $snakSerializations, string $parameterId ): MultilingualTextValue { |
674 | $result = []; |
675 | |
676 | foreach ( $snakSerializations as $snakSerialization ) { |
677 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
678 | $this->requireValueParameter( $snak, $parameterId ); |
679 | |
680 | $value = $snak->getDataValue(); |
681 | $this->requireMonolingualTextParameter( $value, $parameterId ); |
682 | /** @var MonolingualTextValue $value */ |
683 | '@phan-var MonolingualTextValue $value'; |
684 | |
685 | $code = $value->getLanguageCode(); |
686 | if ( array_key_exists( $code, $result ) ) { |
687 | throw new ConstraintParameterException( |
688 | ( new ViolationMessage( 'wbqc-violation-message-parameter-single-per-language' ) ) |
689 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
690 | ->withLanguage( $code ) |
691 | ); |
692 | } |
693 | |
694 | $result[$code] = $value; |
695 | } |
696 | |
697 | return new MultilingualTextValue( $result ); |
698 | } |
699 | |
700 | /** |
701 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
702 | * @throws ConstraintParameterException if the parameter is invalid |
703 | * @return MultilingualTextValue |
704 | */ |
705 | public function parseSyntaxClarificationParameter( array $constraintParameters ): MultilingualTextValue { |
706 | $syntaxClarificationId = $this->config->get( 'WBQualityConstraintsSyntaxClarificationId' ); |
707 | |
708 | if ( !array_key_exists( $syntaxClarificationId, $constraintParameters ) ) { |
709 | return new MultilingualTextValue( [] ); |
710 | } |
711 | |
712 | $syntaxClarifications = $this->parseMultilingualTextParameter( |
713 | $constraintParameters[$syntaxClarificationId], |
714 | $syntaxClarificationId |
715 | ); |
716 | |
717 | return $syntaxClarifications; |
718 | } |
719 | |
720 | /** |
721 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
722 | * @throws ConstraintParameterException if the parameter is invalid |
723 | * @return MultilingualTextValue |
724 | */ |
725 | public function parseConstraintClarificationParameter( array $constraintParameters ): MultilingualTextValue { |
726 | $constraintClarificationId = $this->config->get( 'WBQualityConstraintsConstraintClarificationId' ); |
727 | |
728 | if ( !array_key_exists( $constraintClarificationId, $constraintParameters ) ) { |
729 | return new MultilingualTextValue( [] ); |
730 | } |
731 | |
732 | $constraintClarifications = $this->parseMultilingualTextParameter( |
733 | $constraintParameters[$constraintClarificationId], |
734 | $constraintClarificationId |
735 | ); |
736 | |
737 | return $constraintClarifications; |
738 | } |
739 | |
740 | /** |
741 | * Parse the constraint scope parameters: |
742 | * the context types and entity types where the constraint should be checked. |
743 | * Depending on configuration, this may be the same property ID or two different ones. |
744 | * |
745 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
746 | * @param string $constraintTypeItemId used in error messages |
747 | * @param string[] $validContextTypes a list of Context::TYPE_* constants which are valid for this constraint type. |
748 | * If one of the specified scopes is not in this list, a ConstraintParameterException is thrown. |
749 | * @param string[] $validEntityTypes a list of entity types which are valid for this constraint type. |
750 | * If one of the specified entity types is not in this list, a ConstraintParameterException is thrown. |
751 | * @throws ConstraintParameterException |
752 | * @return array [ string[]|null $contextTypes, string[]|null $entityTypes ] |
753 | * the context types and entity types in the parameters (each may be null if not specified) |
754 | * @suppress PhanTypeArraySuspicious |
755 | */ |
756 | public function parseConstraintScopeParameters( |
757 | array $constraintParameters, |
758 | string $constraintTypeItemId, |
759 | array $validContextTypes, |
760 | array $validEntityTypes |
761 | ): array { |
762 | $contextTypeParameterId = $this->config->get( 'WBQualityConstraintsConstraintScopeId' ); |
763 | $contextTypeItemIds = $this->parseItemIdsParameter( |
764 | $constraintParameters, |
765 | $constraintTypeItemId, |
766 | false, |
767 | $contextTypeParameterId |
768 | ); |
769 | $entityTypeParameterId = $this->config->get( 'WBQualityConstraintsConstraintEntityTypesId' ); |
770 | $entityTypeItemIds = $this->parseItemIdsParameter( |
771 | $constraintParameters, |
772 | $constraintTypeItemId, |
773 | false, |
774 | $entityTypeParameterId |
775 | ); |
776 | |
777 | $contextTypeMapping = $this->getConstraintScopeContextTypeMapping(); |
778 | $entityTypeMapping = $this->getEntityTypeMapping(); |
779 | |
780 | // these nulls will turn into arrays the first time $contextTypes[] or $entityTypes[] is reached, |
781 | // so they’ll be returned as null iff the parameter was not specified |
782 | $contextTypes = null; |
783 | $entityTypes = null; |
784 | |
785 | if ( $contextTypeParameterId === $entityTypeParameterId ) { |
786 | $itemIds = $contextTypeItemIds; |
787 | $mapping = $contextTypeMapping + $entityTypeMapping; |
788 | foreach ( $itemIds as $itemId ) { |
789 | $mapped = $this->mapItemId( $itemId, $mapping, $contextTypeParameterId ); |
790 | if ( in_array( $mapped, $contextTypeMapping, true ) ) { |
791 | $contextTypes[] = $mapped; |
792 | } else { |
793 | $entityTypes[] = $mapped; |
794 | } |
795 | } |
796 | } else { |
797 | foreach ( $contextTypeItemIds as $contextTypeItemId ) { |
798 | $contextTypes[] = $this->mapItemId( |
799 | $contextTypeItemId, |
800 | $contextTypeMapping, |
801 | $contextTypeParameterId |
802 | ); |
803 | } |
804 | foreach ( $entityTypeItemIds as $entityTypeItemId ) { |
805 | $entityTypes[] = $this->mapItemId( |
806 | $entityTypeItemId, |
807 | $entityTypeMapping, |
808 | $entityTypeParameterId |
809 | ); |
810 | } |
811 | } |
812 | |
813 | $this->checkValidScope( $constraintTypeItemId, $contextTypes, $validContextTypes ); |
814 | $this->checkValidScope( $constraintTypeItemId, $entityTypes, $validEntityTypes ); |
815 | |
816 | return [ $contextTypes, $entityTypes ]; |
817 | } |
818 | |
819 | private function checkValidScope( string $constraintTypeItemId, ?array $types, array $validTypes ): void { |
820 | $invalidTypes = array_diff( $types ?: [], $validTypes ); |
821 | if ( $invalidTypes !== [] ) { |
822 | $invalidType = array_pop( $invalidTypes ); |
823 | throw new ConstraintParameterException( |
824 | ( new ViolationMessage( 'wbqc-violation-message-invalid-scope' ) ) |
825 | ->withConstraintScope( $invalidType, Role::CONSTRAINT_PARAMETER_VALUE ) |
826 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
827 | ->withConstraintScopeList( $validTypes, Role::CONSTRAINT_PARAMETER_VALUE ) |
828 | ); |
829 | } |
830 | } |
831 | |
832 | /** |
833 | * Turn an item ID into a full unit string (using the concept URI). |
834 | */ |
835 | private function parseUnitParameter( ItemId $unitId ): string { |
836 | return $this->unitItemConceptBaseUri . $unitId->getSerialization(); |
837 | } |
838 | |
839 | /** |
840 | * Turn an ItemIdSnakValue into a single unit parameter. |
841 | * |
842 | * @throws ConstraintParameterException |
843 | */ |
844 | private function parseUnitItem( ItemIdSnakValue $item ): UnitsParameter { |
845 | switch ( true ) { |
846 | case $item->isValue(): |
847 | $unit = $this->parseUnitParameter( $item->getItemId() ); |
848 | return new UnitsParameter( |
849 | [ $item->getItemId() ], |
850 | [ UnboundedQuantityValue::newFromNumber( 1, $unit ) ], |
851 | false |
852 | ); |
853 | case $item->isSomeValue(): |
854 | $qualifierId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
855 | throw new ConstraintParameterException( |
856 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) |
857 | ->withEntityId( new NumericPropertyId( $qualifierId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
858 | ); |
859 | case $item->isNoValue(): |
860 | return new UnitsParameter( [], [], true ); |
861 | } |
862 | } |
863 | |
864 | /** |
865 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
866 | * @param string $constraintTypeItemId used in error messages |
867 | * @throws ConstraintParameterException if the parameter is invalid or missing |
868 | * @return UnitsParameter |
869 | */ |
870 | public function parseUnitsParameter( array $constraintParameters, string $constraintTypeItemId ): UnitsParameter { |
871 | $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true ); |
872 | $unitItems = []; |
873 | $unitQuantities = []; |
874 | $unitlessAllowed = false; |
875 | |
876 | foreach ( $items as $item ) { |
877 | $unit = $this->parseUnitItem( $item ); |
878 | $unitItems = array_merge( $unitItems, $unit->getUnitItemIds() ); |
879 | $unitQuantities = array_merge( $unitQuantities, $unit->getUnitQuantities() ); |
880 | $unitlessAllowed = $unitlessAllowed || $unit->getUnitlessAllowed(); |
881 | } |
882 | |
883 | if ( $unitQuantities === [] && !$unitlessAllowed ) { |
884 | throw new LogicException( |
885 | 'The "units" parameter is required, and yet we seem to be missing any allowed unit' |
886 | ); |
887 | } |
888 | |
889 | return new UnitsParameter( $unitItems, $unitQuantities, $unitlessAllowed ); |
890 | } |
891 | |
892 | private function getEntityTypeMapping(): array { |
893 | return [ |
894 | $this->config->get( 'WBQualityConstraintsWikibaseItemId' ) => 'item', |
895 | $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ) => 'property', |
896 | $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ) => 'lexeme', |
897 | $this->config->get( 'WBQualityConstraintsWikibaseFormId' ) => 'form', |
898 | $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ) => 'sense', |
899 | $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ) => 'mediainfo', |
900 | ]; |
901 | } |
902 | |
903 | /** |
904 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
905 | * @param string $constraintTypeItemId used in error messages |
906 | * @throws ConstraintParameterException if the parameter is invalid or missing |
907 | * @return EntityTypesParameter |
908 | */ |
909 | public function parseEntityTypesParameter( array $constraintParameters, string $constraintTypeItemId ): EntityTypesParameter { |
910 | $entityTypes = []; |
911 | $entityTypeItemIds = []; |
912 | $parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
913 | $itemIds = $this->parseItemIdsParameter( |
914 | $constraintParameters, |
915 | $constraintTypeItemId, |
916 | true, |
917 | $parameterId |
918 | ); |
919 | |
920 | $mapping = $this->getEntityTypeMapping(); |
921 | foreach ( $itemIds as $itemId ) { |
922 | $entityType = $this->mapItemId( $itemId, $mapping, $parameterId ); |
923 | $entityTypes[] = $entityType; |
924 | $entityTypeItemIds[] = $itemId; |
925 | } |
926 | |
927 | if ( $entityTypes === [] ) { |
928 | // @codeCoverageIgnoreStart |
929 | throw new LogicException( |
930 | 'The "entity types" parameter is required, ' . |
931 | 'and yet we seem to be missing any allowed entity type' |
932 | ); |
933 | // @codeCoverageIgnoreEnd |
934 | } |
935 | |
936 | return new EntityTypesParameter( $entityTypes, $entityTypeItemIds ); |
937 | } |
938 | |
939 | /** |
940 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
941 | * @throws ConstraintParameterException if the parameter is invalid |
942 | * @return PropertyId[] |
943 | */ |
944 | public function parseSeparatorsParameter( array $constraintParameters ): array { |
945 | $separatorId = $this->config->get( 'WBQualityConstraintsSeparatorId' ); |
946 | |
947 | if ( !array_key_exists( $separatorId, $constraintParameters ) ) { |
948 | return []; |
949 | } |
950 | |
951 | $parameters = $constraintParameters[$separatorId]; |
952 | $separators = []; |
953 | |
954 | foreach ( $parameters as $parameter ) { |
955 | $separators[] = $this->parsePropertyIdParameter( $parameter, $separatorId ); |
956 | } |
957 | |
958 | return $separators; |
959 | } |
960 | |
961 | private function getConstraintScopeContextTypeMapping(): array { |
962 | return [ |
963 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' ) => Context::TYPE_STATEMENT, |
964 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ) => Context::TYPE_QUALIFIER, |
965 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ) => Context::TYPE_REFERENCE, |
966 | ]; |
967 | } |
968 | |
969 | private function getPropertyScopeContextTypeMapping(): array { |
970 | return [ |
971 | $this->config->get( 'WBQualityConstraintsAsMainValueId' ) => Context::TYPE_STATEMENT, |
972 | $this->config->get( 'WBQualityConstraintsAsQualifiersId' ) => Context::TYPE_QUALIFIER, |
973 | $this->config->get( 'WBQualityConstraintsAsReferencesId' ) => Context::TYPE_REFERENCE, |
974 | ]; |
975 | } |
976 | |
977 | /** |
978 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
979 | * @param string $constraintTypeItemId used in error messages |
980 | * @throws ConstraintParameterException if the parameter is invalid or missing |
981 | * @return string[] list of Context::TYPE_* constants |
982 | */ |
983 | public function parsePropertyScopeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
984 | $contextTypes = []; |
985 | $parameterId = $this->config->get( 'WBQualityConstraintsPropertyScopeId' ); |
986 | $itemIds = $this->parseItemIdsParameter( |
987 | $constraintParameters, |
988 | $constraintTypeItemId, |
989 | true, |
990 | $parameterId |
991 | ); |
992 | |
993 | $mapping = $this->getPropertyScopeContextTypeMapping(); |
994 | foreach ( $itemIds as $itemId ) { |
995 | $contextTypes[] = $this->mapItemId( $itemId, $mapping, $parameterId ); |
996 | } |
997 | |
998 | if ( $contextTypes === [] ) { |
999 | // @codeCoverageIgnoreStart |
1000 | throw new LogicException( |
1001 | 'The "property scope" parameter is required, ' . |
1002 | 'and yet we seem to be missing any allowed scope' |
1003 | ); |
1004 | // @codeCoverageIgnoreEnd |
1005 | } |
1006 | |
1007 | return $contextTypes; |
1008 | } |
1009 | |
1010 | } |