Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.00% |
69 / 75 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
RangeChecker | |
92.00% |
69 / 75 |
|
80.00% |
4 / 5 |
27.37 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getSupportedContextTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getDefaultContextTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
getSupportedEntityTypes | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
checkConstraint | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
9 | |||
parseRangeParameter | |
62.50% |
10 / 16 |
|
0.00% |
0 / 1 |
3.47 | |||
getViolationMessage | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
9 | |||
checkConstraintParameters | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker; |
4 | |
5 | use DataValues\DataValue; |
6 | use DataValues\TimeValue; |
7 | use Wikibase\DataModel\Entity\ItemId; |
8 | use Wikibase\DataModel\Entity\PropertyId; |
9 | use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup; |
10 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
11 | use Wikibase\DataModel\Statement\Statement; |
12 | use WikibaseQuality\ConstraintReport\Constraint; |
13 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\DependencyMetadata; |
14 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\Metadata; |
15 | use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker; |
16 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context; |
17 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException; |
18 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser; |
19 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\NowValue; |
20 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper; |
21 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage; |
22 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult; |
23 | use WikibaseQuality\ConstraintReport\Role; |
24 | |
25 | /** |
26 | * @author BP2014N1 |
27 | * @license GPL-2.0-or-later |
28 | */ |
29 | class RangeChecker implements ConstraintChecker { |
30 | |
31 | /** |
32 | * @var PropertyDataTypeLookup |
33 | */ |
34 | private $propertyDataTypeLookup; |
35 | |
36 | /** |
37 | * @var ConstraintParameterParser |
38 | */ |
39 | private $constraintParameterParser; |
40 | |
41 | /** |
42 | * @var RangeCheckerHelper |
43 | */ |
44 | private $rangeCheckerHelper; |
45 | |
46 | public function __construct( |
47 | PropertyDataTypeLookup $propertyDataTypeLookup, |
48 | ConstraintParameterParser $constraintParameterParser, |
49 | RangeCheckerHelper $rangeCheckerHelper |
50 | ) { |
51 | $this->propertyDataTypeLookup = $propertyDataTypeLookup; |
52 | $this->constraintParameterParser = $constraintParameterParser; |
53 | $this->rangeCheckerHelper = $rangeCheckerHelper; |
54 | } |
55 | |
56 | /** |
57 | * @codeCoverageIgnore This method is purely declarative. |
58 | */ |
59 | public function getSupportedContextTypes() { |
60 | return self::ALL_CONTEXT_TYPES_SUPPORTED; |
61 | } |
62 | |
63 | /** |
64 | * @codeCoverageIgnore This method is purely declarative. |
65 | */ |
66 | public function getDefaultContextTypes() { |
67 | return Context::ALL_CONTEXT_TYPES; |
68 | } |
69 | |
70 | /** @codeCoverageIgnore This method is purely declarative. */ |
71 | public function getSupportedEntityTypes() { |
72 | return self::ALL_ENTITY_TYPES_SUPPORTED; |
73 | } |
74 | |
75 | /** |
76 | * Checks 'Range' constraint. |
77 | * |
78 | * @param Context $context |
79 | * @param Constraint $constraint |
80 | * |
81 | * @throws ConstraintParameterException |
82 | * @return CheckResult |
83 | */ |
84 | public function checkConstraint( Context $context, Constraint $constraint ) { |
85 | if ( $context->getSnakRank() === Statement::RANK_DEPRECATED ) { |
86 | return new CheckResult( $context, $constraint, CheckResult::STATUS_DEPRECATED ); |
87 | } |
88 | |
89 | $constraintParameters = $constraint->getConstraintParameters(); |
90 | |
91 | $snak = $context->getSnak(); |
92 | |
93 | if ( !$snak instanceof PropertyValueSnak ) { |
94 | // nothing to check |
95 | return new CheckResult( $context, $constraint, CheckResult::STATUS_COMPLIANCE ); |
96 | } |
97 | |
98 | $dataValue = $snak->getDataValue(); |
99 | |
100 | [ $min, $max ] = $this->parseRangeParameter( |
101 | $constraintParameters, |
102 | $constraint->getConstraintTypeItemId(), |
103 | $dataValue->getType() |
104 | ); |
105 | |
106 | if ( $this->rangeCheckerHelper->getComparison( $min, $dataValue ) > 0 || |
107 | $this->rangeCheckerHelper->getComparison( $dataValue, $max ) > 0 |
108 | ) { |
109 | $message = $this->getViolationMessage( |
110 | $context->getSnak()->getPropertyId(), |
111 | $dataValue, |
112 | $min, |
113 | $max |
114 | ); |
115 | $status = CheckResult::STATUS_VIOLATION; |
116 | } else { |
117 | $message = null; |
118 | $status = CheckResult::STATUS_COMPLIANCE; |
119 | } |
120 | |
121 | if ( |
122 | $dataValue instanceof TimeValue && |
123 | ( $min instanceof NowValue || $max instanceof NowValue ) && |
124 | $this->rangeCheckerHelper->isFutureTime( $dataValue ) |
125 | ) { |
126 | $dependencyMetadata = DependencyMetadata::ofFutureTime( $dataValue ); |
127 | } else { |
128 | $dependencyMetadata = DependencyMetadata::blank(); |
129 | } |
130 | |
131 | return ( new CheckResult( $context, $constraint, $status, $message ) ) |
132 | ->withMetadata( Metadata::ofDependencyMetadata( $dependencyMetadata ) ); |
133 | } |
134 | |
135 | /** |
136 | * @param array $constraintParameters see {@link \WikibaseQuality\ConstraintReport\Constraint::getConstraintParameters()} |
137 | * @param string $constraintTypeItemId used in error messages |
138 | * @param string $type 'quantity' or 'time' (can be data type or data value type) |
139 | * |
140 | * @throws ConstraintParameterException if the parameter is invalid or missing |
141 | * @return DataValue[] a pair of two data values, either of which may be null to signify an open range |
142 | */ |
143 | private function parseRangeParameter( array $constraintParameters, $constraintTypeItemId, $type ) { |
144 | switch ( $type ) { |
145 | case 'quantity': |
146 | return $this->constraintParameterParser->parseQuantityRangeParameter( |
147 | $constraintParameters, |
148 | $constraintTypeItemId |
149 | ); |
150 | case 'time': |
151 | return $this->constraintParameterParser->parseTimeRangeParameter( |
152 | $constraintParameters, |
153 | $constraintTypeItemId |
154 | ); |
155 | } |
156 | |
157 | throw new ConstraintParameterException( |
158 | ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-types-2' ) ) |
159 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
160 | ->withDataValueType( 'quantity' ) |
161 | ->withDataValueType( 'time' ) |
162 | ); |
163 | } |
164 | |
165 | /** |
166 | * @param PropertyId $predicate |
167 | * @param DataValue $value |
168 | * @param DataValue|null $min |
169 | * @param DataValue|null $max |
170 | * |
171 | * @return ViolationMessage |
172 | */ |
173 | private function getViolationMessage( PropertyId $predicate, DataValue $value, $min, $max ) { |
174 | // possible message keys: |
175 | // wbqc-violation-message-range-quantity-closed |
176 | // wbqc-violation-message-range-quantity-leftopen |
177 | // wbqc-violation-message-range-quantity-rightopen |
178 | // wbqc-violation-message-range-time-closed |
179 | // wbqc-violation-message-range-time-closed-leftnow |
180 | // wbqc-violation-message-range-time-closed-rightnow |
181 | // wbqc-violation-message-range-time-leftopen |
182 | // wbqc-violation-message-range-time-leftopen-rightnow |
183 | // wbqc-violation-message-range-time-rightopen |
184 | // wbqc-violation-message-range-time-rightopen-leftnow |
185 | $messageKey = 'wbqc-violation-message-range'; |
186 | $messageKey .= '-' . $value->getType(); |
187 | // at least one of $min, $max is set, otherwise there could be no violation |
188 | $messageKey .= '-' . ( $min !== null ? ( $max !== null ? 'closed' : 'rightopen' ) : 'leftopen' ); |
189 | if ( $min instanceof NowValue ) { |
190 | $messageKey .= '-leftnow'; |
191 | } elseif ( $max instanceof NowValue ) { |
192 | $messageKey .= '-rightnow'; |
193 | } |
194 | $message = ( new ViolationMessage( $messageKey ) ) |
195 | ->withEntityId( $predicate, Role::PREDICATE ) |
196 | ->withDataValue( $value, Role::OBJECT ); |
197 | if ( $min !== null && !( $min instanceof NowValue ) ) { |
198 | $message = $message->withDataValue( $min, Role::OBJECT ); |
199 | } |
200 | if ( $max !== null && !( $max instanceof NowValue ) ) { |
201 | $message = $message->withDataValue( $max, Role::OBJECT ); |
202 | } |
203 | return $message; |
204 | } |
205 | |
206 | public function checkConstraintParameters( Constraint $constraint ) { |
207 | $constraintParameters = $constraint->getConstraintParameters(); |
208 | $exceptions = []; |
209 | try { |
210 | // we don’t have a data value here, so get the type from the property instead |
211 | // (the distinction between data type and data value type is irrelevant for 'quantity' and 'time') |
212 | $type = $this->propertyDataTypeLookup->getDataTypeIdForProperty( $constraint->getPropertyId() ); |
213 | $this->parseRangeParameter( |
214 | $constraintParameters, |
215 | $constraint->getConstraintTypeItemId(), |
216 | $type |
217 | ); |
218 | } catch ( ConstraintParameterException $e ) { |
219 | $exceptions[] = $e; |
220 | } |
221 | return $exceptions; |
222 | } |
223 | |
224 | } |