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