Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.74% |
434 / 463 |
|
87.80% |
36 / 41 |
CRAP | |
0.00% |
0 / 1 |
ConstraintParameterParser | |
93.74% |
434 / 463 |
|
87.80% |
36 / 41 |
120.36 | |
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% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
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 | $parameterId ??= $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
262 | if ( !array_key_exists( $parameterId, $constraintParameters ) ) { |
263 | if ( $required ) { |
264 | throw new ConstraintParameterException( |
265 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
266 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
267 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
268 | ); |
269 | } else { |
270 | return []; |
271 | } |
272 | } |
273 | |
274 | $values = []; |
275 | foreach ( $constraintParameters[$parameterId] as $parameter ) { |
276 | $snak = $this->snakDeserializer->deserialize( $parameter ); |
277 | switch ( true ) { |
278 | case $snak instanceof PropertyValueSnak: |
279 | $values[] = $this->parseItemIdParameter( $snak, $parameterId ); |
280 | break; |
281 | case $snak instanceof PropertySomeValueSnak: |
282 | $values[] = ItemIdSnakValue::someValue(); |
283 | break; |
284 | case $snak instanceof PropertyNoValueSnak: |
285 | $values[] = ItemIdSnakValue::noValue(); |
286 | break; |
287 | } |
288 | } |
289 | return $values; |
290 | } |
291 | |
292 | /** |
293 | * Parse a parameter that must contain item IDs. |
294 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
295 | * @param string $constraintTypeItemId used in error messages |
296 | * @param bool $required whether the parameter is required (error if absent) or not ([] if absent) |
297 | * @param string $parameterId the property ID to use |
298 | * @throws ConstraintParameterException |
299 | * @return ItemId[] |
300 | */ |
301 | private function parseItemIdsParameter( |
302 | array $constraintParameters, |
303 | string $constraintTypeItemId, |
304 | bool $required, |
305 | string $parameterId |
306 | ): array { |
307 | return array_map( static function ( ItemIdSnakValue $value ) use ( $parameterId ): ItemId { |
308 | if ( $value->isValue() ) { |
309 | return $value->getItemId(); |
310 | } else { |
311 | throw new ConstraintParameterException( |
312 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value' ) ) |
313 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
314 | ); |
315 | } |
316 | }, $this->parseItemsParameter( |
317 | $constraintParameters, |
318 | $constraintTypeItemId, |
319 | $required, |
320 | $parameterId |
321 | ) ); |
322 | } |
323 | |
324 | /** |
325 | * Map an item ID parameter to a well-known value or throw an appropriate error. |
326 | * @throws ConstraintParameterException |
327 | * @return mixed elements of $mapping |
328 | */ |
329 | private function mapItemId( ItemId $itemId, array $mapping, string $parameterId ) { |
330 | $serialization = $itemId->getSerialization(); |
331 | if ( array_key_exists( $serialization, $mapping ) ) { |
332 | return $mapping[$serialization]; |
333 | } else { |
334 | $allowed = array_map( static function ( $id ) { |
335 | return new ItemId( $id ); |
336 | }, array_keys( $mapping ) ); |
337 | throw new ConstraintParameterException( |
338 | ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) |
339 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
340 | ->withEntityIdList( $allowed, Role::CONSTRAINT_PARAMETER_VALUE ) |
341 | ); |
342 | } |
343 | } |
344 | |
345 | /** |
346 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
347 | * @param string $constraintTypeItemId used in error messages |
348 | * @throws ConstraintParameterException if the parameter is invalid or missing |
349 | * @return PropertyId[] |
350 | */ |
351 | public function parsePropertiesParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
352 | $this->checkError( $constraintParameters ); |
353 | $propertyId = $this->config->get( 'WBQualityConstraintsPropertyId' ); |
354 | if ( !array_key_exists( $propertyId, $constraintParameters ) ) { |
355 | throw new ConstraintParameterException( |
356 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
357 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
358 | ->withEntityId( new NumericPropertyId( $propertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
359 | ); |
360 | } |
361 | |
362 | $parameters = $constraintParameters[$propertyId]; |
363 | if ( count( $parameters ) === 1 && |
364 | $this->snakDeserializer->deserialize( $parameters[0] ) instanceof PropertyNoValueSnak |
365 | ) { |
366 | return []; |
367 | } |
368 | |
369 | $properties = []; |
370 | foreach ( $parameters as $parameter ) { |
371 | $properties[] = $this->parsePropertyIdParameter( $parameter, $propertyId ); |
372 | } |
373 | return $properties; |
374 | } |
375 | |
376 | /** |
377 | * @throws ConstraintParameterException |
378 | */ |
379 | private function parseValueOrNoValueParameter( array $snakSerialization, string $parameterId ): ?DataValue { |
380 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
381 | if ( $snak instanceof PropertyValueSnak ) { |
382 | return $snak->getDataValue(); |
383 | } elseif ( $snak instanceof PropertyNoValueSnak ) { |
384 | return null; |
385 | } else { |
386 | throw new ConstraintParameterException( |
387 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) |
388 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
389 | ); |
390 | } |
391 | } |
392 | |
393 | private function parseValueOrNoValueOrNowParameter( array $snakSerialization, string $parameterId ): ?DataValue { |
394 | try { |
395 | return $this->parseValueOrNoValueParameter( $snakSerialization, $parameterId ); |
396 | } catch ( ConstraintParameterException $e ) { |
397 | // unknown value means “now” |
398 | return new NowValue(); |
399 | } |
400 | } |
401 | |
402 | /** |
403 | * Checks whether there is exactly one non-null quantity with the given unit. |
404 | */ |
405 | private function exactlyOneQuantityWithUnit( ?DataValue $min, ?DataValue $max, string $unit ): bool { |
406 | if ( !( $min instanceof UnboundedQuantityValue ) || |
407 | !( $max instanceof UnboundedQuantityValue ) |
408 | ) { |
409 | return false; |
410 | } |
411 | |
412 | return ( $min->getUnit() === $unit ) !== ( $max->getUnit() === $unit ); |
413 | } |
414 | |
415 | /** |
416 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
417 | * @param string $minimumId |
418 | * @param string $maximumId |
419 | * @param string $constraintTypeItemId used in error messages |
420 | * @param string $type 'quantity' or 'time' (can be data type or data value type) |
421 | * |
422 | * @throws ConstraintParameterException if the parameter is invalid or missing |
423 | * @return DataValue[] if the parameter is invalid or missing |
424 | */ |
425 | private function parseRangeParameter( |
426 | array $constraintParameters, |
427 | string $minimumId, |
428 | string $maximumId, |
429 | string $constraintTypeItemId, |
430 | string $type |
431 | ): array { |
432 | $this->checkError( $constraintParameters ); |
433 | if ( !array_key_exists( $minimumId, $constraintParameters ) || |
434 | !array_key_exists( $maximumId, $constraintParameters ) |
435 | ) { |
436 | throw new ConstraintParameterException( |
437 | ( new ViolationMessage( 'wbqc-violation-message-range-parameters-needed' ) ) |
438 | ->withDataValueType( $type ) |
439 | ->withEntityId( new NumericPropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
440 | ->withEntityId( new NumericPropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
441 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
442 | ); |
443 | } |
444 | |
445 | $this->requireSingleParameter( $constraintParameters, $minimumId ); |
446 | $this->requireSingleParameter( $constraintParameters, $maximumId ); |
447 | $parseFunction = $type === 'time' ? 'parseValueOrNoValueOrNowParameter' : 'parseValueOrNoValueParameter'; |
448 | $min = $this->$parseFunction( $constraintParameters[$minimumId][0], $minimumId ); |
449 | $max = $this->$parseFunction( $constraintParameters[$maximumId][0], $maximumId ); |
450 | |
451 | $yearUnit = $this->config->get( 'WBQualityConstraintsYearUnit' ); |
452 | if ( $this->exactlyOneQuantityWithUnit( $min, $max, $yearUnit ) ) { |
453 | throw new ConstraintParameterException( |
454 | new ViolationMessage( 'wbqc-violation-message-range-parameters-one-year' ) |
455 | ); |
456 | } |
457 | if ( ( $min === null && $max === null ) || |
458 | ( $min !== null && $max !== null && $min->equals( $max ) ) |
459 | ) { |
460 | throw new ConstraintParameterException( |
461 | ( new ViolationMessage( 'wbqc-violation-message-range-parameters-same' ) ) |
462 | ->withEntityId( new NumericPropertyId( $minimumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
463 | ->withEntityId( new NumericPropertyId( $maximumId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
464 | ); |
465 | } |
466 | |
467 | return [ $min, $max ]; |
468 | } |
469 | |
470 | /** |
471 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
472 | * @param string $constraintTypeItemId used in error messages |
473 | * |
474 | * @throws ConstraintParameterException if the parameter is invalid or missing |
475 | * @return DataValue[] a pair of two data values, either of which may be null to signify an open range |
476 | */ |
477 | public function parseQuantityRangeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
478 | return $this->parseRangeParameter( |
479 | $constraintParameters, |
480 | $this->config->get( 'WBQualityConstraintsMinimumQuantityId' ), |
481 | $this->config->get( 'WBQualityConstraintsMaximumQuantityId' ), |
482 | $constraintTypeItemId, |
483 | 'quantity' |
484 | ); |
485 | } |
486 | |
487 | /** |
488 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
489 | * @param string $constraintTypeItemId used in error messages |
490 | * |
491 | * @throws ConstraintParameterException if the parameter is invalid or missing |
492 | * @return DataValue[] a pair of two data values, either of which may be null to signify an open range |
493 | */ |
494 | public function parseTimeRangeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
495 | return $this->parseRangeParameter( |
496 | $constraintParameters, |
497 | $this->config->get( 'WBQualityConstraintsMinimumDateId' ), |
498 | $this->config->get( 'WBQualityConstraintsMaximumDateId' ), |
499 | $constraintTypeItemId, |
500 | 'time' |
501 | ); |
502 | } |
503 | |
504 | /** |
505 | * Parse language parameter. |
506 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
507 | * @param string $constraintTypeItemId used in error messages |
508 | * @throws ConstraintParameterException |
509 | * @return string[] |
510 | */ |
511 | public function parseLanguageParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
512 | $this->checkError( $constraintParameters ); |
513 | $languagePropertyId = $this->config->get( 'WBQualityConstraintsLanguagePropertyId' ); |
514 | if ( !array_key_exists( $languagePropertyId, $constraintParameters ) ) { |
515 | throw new ConstraintParameterException( |
516 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
517 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
518 | ->withEntityId( new NumericPropertyId( $languagePropertyId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
519 | ); |
520 | } |
521 | |
522 | $languages = []; |
523 | foreach ( $constraintParameters[$languagePropertyId] as $snak ) { |
524 | $languages[] = $this->parseStringParameter( $snak, $languagePropertyId ); |
525 | } |
526 | return $languages; |
527 | } |
528 | |
529 | /** |
530 | * Parse a single string parameter. |
531 | * @throws ConstraintParameterException |
532 | */ |
533 | private function parseStringParameter( array $snakSerialization, string $parameterId ): string { |
534 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
535 | $this->requireValueParameter( $snak, $parameterId ); |
536 | $value = $snak->getDataValue(); |
537 | if ( $value instanceof StringValue ) { |
538 | return $value->getValue(); |
539 | } else { |
540 | throw new ConstraintParameterException( |
541 | ( new ViolationMessage( 'wbqc-violation-message-parameter-string' ) ) |
542 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
543 | ->withDataValue( $value, Role::CONSTRAINT_PARAMETER_VALUE ) |
544 | ); |
545 | } |
546 | } |
547 | |
548 | /** |
549 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
550 | * @param string $constraintTypeItemId used in error messages |
551 | * @throws ConstraintParameterException if the parameter is invalid or missing |
552 | * @return string |
553 | */ |
554 | public function parseNamespaceParameter( array $constraintParameters, string $constraintTypeItemId ): string { |
555 | $this->checkError( $constraintParameters ); |
556 | $namespaceId = $this->config->get( 'WBQualityConstraintsNamespaceId' ); |
557 | if ( !array_key_exists( $namespaceId, $constraintParameters ) ) { |
558 | return ''; |
559 | } |
560 | |
561 | $this->requireSingleParameter( $constraintParameters, $namespaceId ); |
562 | return $this->parseStringParameter( $constraintParameters[$namespaceId][0], $namespaceId ); |
563 | } |
564 | |
565 | /** |
566 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
567 | * @param string $constraintTypeItemId used in error messages |
568 | * @throws ConstraintParameterException if the parameter is invalid or missing |
569 | * @return string |
570 | */ |
571 | public function parseFormatParameter( array $constraintParameters, string $constraintTypeItemId ): string { |
572 | $this->checkError( $constraintParameters ); |
573 | $formatId = $this->config->get( 'WBQualityConstraintsFormatAsARegularExpressionId' ); |
574 | if ( !array_key_exists( $formatId, $constraintParameters ) ) { |
575 | throw new ConstraintParameterException( |
576 | ( new ViolationMessage( 'wbqc-violation-message-parameter-needed' ) ) |
577 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
578 | ->withEntityId( new NumericPropertyId( $formatId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
579 | ); |
580 | } |
581 | |
582 | $this->requireSingleParameter( $constraintParameters, $formatId ); |
583 | return $this->parseStringParameter( $constraintParameters[$formatId][0], $formatId ); |
584 | } |
585 | |
586 | /** |
587 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
588 | * @throws ConstraintParameterException if the parameter is invalid |
589 | * @return EntityId[] |
590 | */ |
591 | public function parseExceptionParameter( array $constraintParameters ): array { |
592 | $this->checkError( $constraintParameters ); |
593 | $exceptionId = $this->config->get( 'WBQualityConstraintsExceptionToConstraintId' ); |
594 | if ( !array_key_exists( $exceptionId, $constraintParameters ) ) { |
595 | return []; |
596 | } |
597 | |
598 | return array_map( |
599 | function ( $snakSerialization ) use ( $exceptionId ) { |
600 | return $this->parseEntityIdParameter( $snakSerialization, $exceptionId ); |
601 | }, |
602 | $constraintParameters[$exceptionId] |
603 | ); |
604 | } |
605 | |
606 | /** |
607 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
608 | * @throws ConstraintParameterException if the parameter is invalid |
609 | * @return string|null 'mandatory', 'suggestion' or null |
610 | */ |
611 | public function parseConstraintStatusParameter( array $constraintParameters ): ?string { |
612 | $this->checkError( $constraintParameters ); |
613 | $constraintStatusId = $this->config->get( 'WBQualityConstraintsConstraintStatusId' ); |
614 | if ( !array_key_exists( $constraintStatusId, $constraintParameters ) ) { |
615 | return null; |
616 | } |
617 | |
618 | $mandatoryId = $this->config->get( 'WBQualityConstraintsMandatoryConstraintId' ); |
619 | $supportedStatuses = [ new ItemId( $mandatoryId ) ]; |
620 | if ( $this->config->get( 'WBQualityConstraintsEnableSuggestionConstraintStatus' ) ) { |
621 | $suggestionId = $this->config->get( 'WBQualityConstraintsSuggestionConstraintId' ); |
622 | $supportedStatuses[] = new ItemId( $suggestionId ); |
623 | } else { |
624 | $suggestionId = null; |
625 | } |
626 | |
627 | $this->requireSingleParameter( $constraintParameters, $constraintStatusId ); |
628 | $snak = $this->snakDeserializer->deserialize( $constraintParameters[$constraintStatusId][0] ); |
629 | $this->requireValueParameter( $snak, $constraintStatusId ); |
630 | '@phan-var \Wikibase\DataModel\Snak\PropertyValueSnak $snak'; |
631 | $dataValue = $snak->getDataValue(); |
632 | '@phan-var EntityIdValue $dataValue'; |
633 | $entityId = $dataValue->getEntityId(); |
634 | $statusId = $entityId->getSerialization(); |
635 | |
636 | if ( $statusId === $mandatoryId ) { |
637 | return 'mandatory'; |
638 | } elseif ( $statusId === $suggestionId ) { |
639 | return 'suggestion'; |
640 | } else { |
641 | throw new ConstraintParameterException( |
642 | ( new ViolationMessage( 'wbqc-violation-message-parameter-oneof' ) ) |
643 | ->withEntityId( new NumericPropertyId( $constraintStatusId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
644 | ->withEntityIdList( |
645 | $supportedStatuses, |
646 | Role::CONSTRAINT_PARAMETER_VALUE |
647 | ) |
648 | ); |
649 | } |
650 | } |
651 | |
652 | /** |
653 | * Require that $dataValue is a {@link MonolingualTextValue}. |
654 | * @throws ConstraintParameterException |
655 | */ |
656 | private function requireMonolingualTextParameter( DataValue $dataValue, string $parameterId ): void { |
657 | if ( !( $dataValue instanceof MonolingualTextValue ) ) { |
658 | throw new ConstraintParameterException( |
659 | ( new ViolationMessage( 'wbqc-violation-message-parameter-monolingualtext' ) ) |
660 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
661 | ->withDataValue( $dataValue, Role::CONSTRAINT_PARAMETER_VALUE ) |
662 | ); |
663 | } |
664 | } |
665 | |
666 | /** |
667 | * Parse a series of monolingual text snaks (serialized) into a map from language code to string. |
668 | * |
669 | * @throws ConstraintParameterException if invalid snaks are found or a language has multiple texts |
670 | */ |
671 | private function parseMultilingualTextParameter( array $snakSerializations, string $parameterId ): MultilingualTextValue { |
672 | $result = []; |
673 | |
674 | foreach ( $snakSerializations as $snakSerialization ) { |
675 | $snak = $this->snakDeserializer->deserialize( $snakSerialization ); |
676 | $this->requireValueParameter( $snak, $parameterId ); |
677 | |
678 | $value = $snak->getDataValue(); |
679 | $this->requireMonolingualTextParameter( $value, $parameterId ); |
680 | /** @var MonolingualTextValue $value */ |
681 | '@phan-var MonolingualTextValue $value'; |
682 | |
683 | $code = $value->getLanguageCode(); |
684 | if ( array_key_exists( $code, $result ) ) { |
685 | throw new ConstraintParameterException( |
686 | ( new ViolationMessage( 'wbqc-violation-message-parameter-single-per-language' ) ) |
687 | ->withEntityId( new NumericPropertyId( $parameterId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
688 | ->withLanguage( $code ) |
689 | ); |
690 | } |
691 | |
692 | $result[$code] = $value; |
693 | } |
694 | |
695 | return new MultilingualTextValue( $result ); |
696 | } |
697 | |
698 | /** |
699 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
700 | * @throws ConstraintParameterException if the parameter is invalid |
701 | * @return MultilingualTextValue |
702 | */ |
703 | public function parseSyntaxClarificationParameter( array $constraintParameters ): MultilingualTextValue { |
704 | $syntaxClarificationId = $this->config->get( 'WBQualityConstraintsSyntaxClarificationId' ); |
705 | |
706 | if ( !array_key_exists( $syntaxClarificationId, $constraintParameters ) ) { |
707 | return new MultilingualTextValue( [] ); |
708 | } |
709 | |
710 | $syntaxClarifications = $this->parseMultilingualTextParameter( |
711 | $constraintParameters[$syntaxClarificationId], |
712 | $syntaxClarificationId |
713 | ); |
714 | |
715 | return $syntaxClarifications; |
716 | } |
717 | |
718 | /** |
719 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
720 | * @throws ConstraintParameterException if the parameter is invalid |
721 | * @return MultilingualTextValue |
722 | */ |
723 | public function parseConstraintClarificationParameter( array $constraintParameters ): MultilingualTextValue { |
724 | $constraintClarificationId = $this->config->get( 'WBQualityConstraintsConstraintClarificationId' ); |
725 | |
726 | if ( !array_key_exists( $constraintClarificationId, $constraintParameters ) ) { |
727 | return new MultilingualTextValue( [] ); |
728 | } |
729 | |
730 | $constraintClarifications = $this->parseMultilingualTextParameter( |
731 | $constraintParameters[$constraintClarificationId], |
732 | $constraintClarificationId |
733 | ); |
734 | |
735 | return $constraintClarifications; |
736 | } |
737 | |
738 | /** |
739 | * Parse the constraint scope parameters: |
740 | * the context types and entity types where the constraint should be checked. |
741 | * Depending on configuration, this may be the same property ID or two different ones. |
742 | * |
743 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
744 | * @param string $constraintTypeItemId used in error messages |
745 | * @param string[] $validContextTypes a list of Context::TYPE_* constants which are valid for this constraint type. |
746 | * If one of the specified scopes is not in this list, a ConstraintParameterException is thrown. |
747 | * @param string[] $validEntityTypes a list of entity types which are valid for this constraint type. |
748 | * If one of the specified entity types is not in this list, a ConstraintParameterException is thrown. |
749 | * @throws ConstraintParameterException |
750 | * @return array [ string[]|null $contextTypes, string[]|null $entityTypes ] |
751 | * the context types and entity types in the parameters (each may be null if not specified) |
752 | * @suppress PhanTypeArraySuspicious |
753 | */ |
754 | public function parseConstraintScopeParameters( |
755 | array $constraintParameters, |
756 | string $constraintTypeItemId, |
757 | array $validContextTypes, |
758 | array $validEntityTypes |
759 | ): array { |
760 | $contextTypeParameterId = $this->config->get( 'WBQualityConstraintsConstraintScopeId' ); |
761 | $contextTypeItemIds = $this->parseItemIdsParameter( |
762 | $constraintParameters, |
763 | $constraintTypeItemId, |
764 | false, |
765 | $contextTypeParameterId |
766 | ); |
767 | $entityTypeParameterId = $this->config->get( 'WBQualityConstraintsConstraintEntityTypesId' ); |
768 | $entityTypeItemIds = $this->parseItemIdsParameter( |
769 | $constraintParameters, |
770 | $constraintTypeItemId, |
771 | false, |
772 | $entityTypeParameterId |
773 | ); |
774 | |
775 | $contextTypeMapping = $this->getConstraintScopeContextTypeMapping(); |
776 | $entityTypeMapping = $this->getEntityTypeMapping(); |
777 | |
778 | // these nulls will turn into arrays the first time $contextTypes[] or $entityTypes[] is reached, |
779 | // so they’ll be returned as null iff the parameter was not specified |
780 | $contextTypes = null; |
781 | $entityTypes = null; |
782 | |
783 | if ( $contextTypeParameterId === $entityTypeParameterId ) { |
784 | $itemIds = $contextTypeItemIds; |
785 | $mapping = $contextTypeMapping + $entityTypeMapping; |
786 | foreach ( $itemIds as $itemId ) { |
787 | $mapped = $this->mapItemId( $itemId, $mapping, $contextTypeParameterId ); |
788 | if ( in_array( $mapped, $contextTypeMapping, true ) ) { |
789 | $contextTypes[] = $mapped; |
790 | } else { |
791 | $entityTypes[] = $mapped; |
792 | } |
793 | } |
794 | } else { |
795 | foreach ( $contextTypeItemIds as $contextTypeItemId ) { |
796 | $contextTypes[] = $this->mapItemId( |
797 | $contextTypeItemId, |
798 | $contextTypeMapping, |
799 | $contextTypeParameterId |
800 | ); |
801 | } |
802 | foreach ( $entityTypeItemIds as $entityTypeItemId ) { |
803 | $entityTypes[] = $this->mapItemId( |
804 | $entityTypeItemId, |
805 | $entityTypeMapping, |
806 | $entityTypeParameterId |
807 | ); |
808 | } |
809 | } |
810 | |
811 | $this->checkValidScope( $constraintTypeItemId, $contextTypes, $validContextTypes ); |
812 | $this->checkValidScope( $constraintTypeItemId, $entityTypes, $validEntityTypes ); |
813 | |
814 | return [ $contextTypes, $entityTypes ]; |
815 | } |
816 | |
817 | private function checkValidScope( string $constraintTypeItemId, ?array $types, array $validTypes ): void { |
818 | $invalidTypes = array_diff( $types ?: [], $validTypes ); |
819 | if ( $invalidTypes !== [] ) { |
820 | $invalidType = array_pop( $invalidTypes ); |
821 | throw new ConstraintParameterException( |
822 | ( new ViolationMessage( 'wbqc-violation-message-invalid-scope' ) ) |
823 | ->withConstraintScope( $invalidType, Role::CONSTRAINT_PARAMETER_VALUE ) |
824 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
825 | ->withConstraintScopeList( $validTypes, Role::CONSTRAINT_PARAMETER_VALUE ) |
826 | ); |
827 | } |
828 | } |
829 | |
830 | /** |
831 | * Turn an item ID into a full unit string (using the concept URI). |
832 | */ |
833 | private function parseUnitParameter( ItemId $unitId ): string { |
834 | return $this->unitItemConceptBaseUri . $unitId->getSerialization(); |
835 | } |
836 | |
837 | /** |
838 | * Turn an ItemIdSnakValue into a single unit parameter. |
839 | * |
840 | * @throws ConstraintParameterException |
841 | */ |
842 | private function parseUnitItem( ItemIdSnakValue $item ): UnitsParameter { |
843 | switch ( true ) { |
844 | case $item->isValue(): |
845 | $unit = $this->parseUnitParameter( $item->getItemId() ); |
846 | return new UnitsParameter( |
847 | [ $item->getItemId() ], |
848 | [ UnboundedQuantityValue::newFromNumber( 1, $unit ) ], |
849 | false |
850 | ); |
851 | case $item->isSomeValue(): |
852 | $qualifierId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
853 | throw new ConstraintParameterException( |
854 | ( new ViolationMessage( 'wbqc-violation-message-parameter-value-or-novalue' ) ) |
855 | ->withEntityId( new NumericPropertyId( $qualifierId ), Role::CONSTRAINT_PARAMETER_PROPERTY ) |
856 | ); |
857 | case $item->isNoValue(): |
858 | return new UnitsParameter( [], [], true ); |
859 | } |
860 | } |
861 | |
862 | /** |
863 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
864 | * @param string $constraintTypeItemId used in error messages |
865 | * @throws ConstraintParameterException if the parameter is invalid or missing |
866 | * @return UnitsParameter |
867 | */ |
868 | public function parseUnitsParameter( array $constraintParameters, string $constraintTypeItemId ): UnitsParameter { |
869 | $items = $this->parseItemsParameter( $constraintParameters, $constraintTypeItemId, true ); |
870 | $unitItems = []; |
871 | $unitQuantities = []; |
872 | $unitlessAllowed = false; |
873 | |
874 | foreach ( $items as $item ) { |
875 | $unit = $this->parseUnitItem( $item ); |
876 | $unitItems = array_merge( $unitItems, $unit->getUnitItemIds() ); |
877 | $unitQuantities = array_merge( $unitQuantities, $unit->getUnitQuantities() ); |
878 | $unitlessAllowed = $unitlessAllowed || $unit->getUnitlessAllowed(); |
879 | } |
880 | |
881 | if ( $unitQuantities === [] && !$unitlessAllowed ) { |
882 | throw new LogicException( |
883 | 'The "units" parameter is required, and yet we seem to be missing any allowed unit' |
884 | ); |
885 | } |
886 | |
887 | return new UnitsParameter( $unitItems, $unitQuantities, $unitlessAllowed ); |
888 | } |
889 | |
890 | private function getEntityTypeMapping(): array { |
891 | return [ |
892 | $this->config->get( 'WBQualityConstraintsWikibaseItemId' ) => 'item', |
893 | $this->config->get( 'WBQualityConstraintsWikibasePropertyId' ) => 'property', |
894 | $this->config->get( 'WBQualityConstraintsWikibaseLexemeId' ) => 'lexeme', |
895 | $this->config->get( 'WBQualityConstraintsWikibaseFormId' ) => 'form', |
896 | $this->config->get( 'WBQualityConstraintsWikibaseSenseId' ) => 'sense', |
897 | $this->config->get( 'WBQualityConstraintsWikibaseMediaInfoId' ) => 'mediainfo', |
898 | ]; |
899 | } |
900 | |
901 | /** |
902 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
903 | * @param string $constraintTypeItemId used in error messages |
904 | * @throws ConstraintParameterException if the parameter is invalid or missing |
905 | * @return EntityTypesParameter |
906 | */ |
907 | public function parseEntityTypesParameter( array $constraintParameters, string $constraintTypeItemId ): EntityTypesParameter { |
908 | $entityTypes = []; |
909 | $entityTypeItemIds = []; |
910 | $parameterId = $this->config->get( 'WBQualityConstraintsQualifierOfPropertyConstraintId' ); |
911 | $itemIds = $this->parseItemIdsParameter( |
912 | $constraintParameters, |
913 | $constraintTypeItemId, |
914 | true, |
915 | $parameterId |
916 | ); |
917 | |
918 | $mapping = $this->getEntityTypeMapping(); |
919 | foreach ( $itemIds as $itemId ) { |
920 | $entityType = $this->mapItemId( $itemId, $mapping, $parameterId ); |
921 | $entityTypes[] = $entityType; |
922 | $entityTypeItemIds[] = $itemId; |
923 | } |
924 | |
925 | if ( $entityTypes === [] ) { |
926 | // @codeCoverageIgnoreStart |
927 | throw new LogicException( |
928 | 'The "entity types" parameter is required, ' . |
929 | 'and yet we seem to be missing any allowed entity type' |
930 | ); |
931 | // @codeCoverageIgnoreEnd |
932 | } |
933 | |
934 | return new EntityTypesParameter( $entityTypes, $entityTypeItemIds ); |
935 | } |
936 | |
937 | /** |
938 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
939 | * @throws ConstraintParameterException if the parameter is invalid |
940 | * @return PropertyId[] |
941 | */ |
942 | public function parseSeparatorsParameter( array $constraintParameters ): array { |
943 | $separatorId = $this->config->get( 'WBQualityConstraintsSeparatorId' ); |
944 | |
945 | if ( !array_key_exists( $separatorId, $constraintParameters ) ) { |
946 | return []; |
947 | } |
948 | |
949 | $parameters = $constraintParameters[$separatorId]; |
950 | $separators = []; |
951 | |
952 | foreach ( $parameters as $parameter ) { |
953 | $separators[] = $this->parsePropertyIdParameter( $parameter, $separatorId ); |
954 | } |
955 | |
956 | return $separators; |
957 | } |
958 | |
959 | private function getConstraintScopeContextTypeMapping(): array { |
960 | return [ |
961 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnMainValueId' ) => Context::TYPE_STATEMENT, |
962 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnQualifiersId' ) => Context::TYPE_QUALIFIER, |
963 | $this->config->get( 'WBQualityConstraintsConstraintCheckedOnReferencesId' ) => Context::TYPE_REFERENCE, |
964 | ]; |
965 | } |
966 | |
967 | private function getPropertyScopeContextTypeMapping(): array { |
968 | return [ |
969 | $this->config->get( 'WBQualityConstraintsAsMainValueId' ) => Context::TYPE_STATEMENT, |
970 | $this->config->get( 'WBQualityConstraintsAsQualifiersId' ) => Context::TYPE_QUALIFIER, |
971 | $this->config->get( 'WBQualityConstraintsAsReferencesId' ) => Context::TYPE_REFERENCE, |
972 | ]; |
973 | } |
974 | |
975 | /** |
976 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
977 | * @param string $constraintTypeItemId used in error messages |
978 | * @throws ConstraintParameterException if the parameter is invalid or missing |
979 | * @return string[] list of Context::TYPE_* constants |
980 | */ |
981 | public function parsePropertyScopeParameter( array $constraintParameters, string $constraintTypeItemId ): array { |
982 | $contextTypes = []; |
983 | $parameterId = $this->config->get( 'WBQualityConstraintsPropertyScopeId' ); |
984 | $itemIds = $this->parseItemIdsParameter( |
985 | $constraintParameters, |
986 | $constraintTypeItemId, |
987 | true, |
988 | $parameterId |
989 | ); |
990 | |
991 | $mapping = $this->getPropertyScopeContextTypeMapping(); |
992 | foreach ( $itemIds as $itemId ) { |
993 | $contextTypes[] = $this->mapItemId( $itemId, $mapping, $parameterId ); |
994 | } |
995 | |
996 | if ( $contextTypes === [] ) { |
997 | // @codeCoverageIgnoreStart |
998 | throw new LogicException( |
999 | 'The "property scope" parameter is required, ' . |
1000 | 'and yet we seem to be missing any allowed scope' |
1001 | ); |
1002 | // @codeCoverageIgnoreEnd |
1003 | } |
1004 | |
1005 | return $contextTypes; |
1006 | } |
1007 | |
1008 | } |