Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.00% covered (success)
92.00%
69 / 75
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
RangeChecker
92.00% covered (success)
92.00%
69 / 75
80.00% covered (warning)
80.00%
4 / 5
27.37
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
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% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
9
 parseRangeParameter
62.50% covered (warning)
62.50%
10 / 16
0.00% covered (danger)
0.00%
0 / 1
3.47
 getViolationMessage
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
9
 checkConstraintParameters
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
4
5use DataValues\DataValue;
6use DataValues\TimeValue;
7use Wikibase\DataModel\Entity\ItemId;
8use Wikibase\DataModel\Entity\PropertyId;
9use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup;
10use Wikibase\DataModel\Snak\PropertyValueSnak;
11use Wikibase\DataModel\Statement\Statement;
12use WikibaseQuality\ConstraintReport\Constraint;
13use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\DependencyMetadata;
14use WikibaseQuality\ConstraintReport\ConstraintCheck\Cache\Metadata;
15use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
16use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
17use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException;
18use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
19use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\NowValue;
20use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper;
21use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
22use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
23use WikibaseQuality\ConstraintReport\Role;
24
25/**
26 * @author BP2014N1
27 * @license GPL-2.0-or-later
28 */
29class 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}