Lucas Werkmeister (WMDE) has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/359415 )

Change subject: Add support for importing range parameters
......................................................................

Add support for importing range parameters

ConstraintStatementParameterParser gains a new method to parse
parameters for a quantity or time range. This is used by the Range and
Diff within range constraint types.

A message that a parameter must be a value or no value, but not unknown
value, is added, and the wbqc-violation-message-range-parameters-needed
message is updated to no longer hard-code the constraint type name,
since it will be used for both Range and Diff within range.

Note that the statement version of the time range parameters does not
yet support 'now', since it is not yet clear how that should be modelled
(see discussion at [1]).

[1]: 
https://www.wikidata.org/wiki/Help_talk:Property_constraints_portal/Range#special_parameter_.E2.80.9Cnow.E2.80.9D_in_constraint_statements

Change-Id: I14bb590d3eb4ea421dd9d169cc737b895e857c65
---
M extension.json
M i18n/en.json
M i18n/qqq.json
M includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php
M tests/phpunit/Helper/ConstraintStatementParameterParserTest.php
5 files changed, 297 insertions(+), 2 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikibaseQualityConstraints
 refs/changes/15/359415/1

diff --git a/extension.json b/extension.json
index f4d92e0..3a0e343 100644
--- a/extension.json
+++ b/extension.json
@@ -190,6 +190,26 @@
                        "value": "P2305",
                        "description": "The property ID of the 'qualifier of 
property constraint' property, which specifies the item parameter of an 'item 
requires claim', 'value requires claim', or 'one of' constraint.",
                        "public": true
+               },
+               "WBQualityConstraintsMinimumQuantityId": {
+                       "value": "P2313",
+                       "description": "The property ID of the 'minimum 
quantity' property, which specifies the minimum quantity parameter of a 'range' 
or 'diff within range' constraint.",
+                       "public": true
+               },
+               "WBQualityConstraintsMaximumQuantityId": {
+                       "value": "P2312",
+                       "description": "The property ID of the 'maximum 
quantity' property, which specifies the maximum quantity parameter of a 'range' 
or 'diff within range' constraint.",
+                       "public": true
+               },
+               "WBQualityConstraintsMinimumDateId": {
+                       "value": "P2310",
+                       "description": "The property ID of the 'minimum date' 
property, which specifies the minimum date parameter of a 'range' or 'diff 
within range' constraint.",
+                       "public": true
+               },
+               "WBQualityConstraintsMaximumDateId": {
+                       "value": "P2311",
+                       "description": "The property ID of the 'maximum date' 
property, which specifies the maximum date parameter of a 'range' or 'diff 
within range' constraint.",
+                       "public": true
                }
        },
        "manifest_version": 2
diff --git a/i18n/en.json b/i18n/en.json
index ad5b534..575ab2e 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -56,6 +56,7 @@
        "wbqc-violation-message-target-entity-must-exist": "The target entity 
must exist.",
        "wbqc-violation-message-value-entity-must-exist": "The value entity 
must exist.",
        "wbqc-violation-message-parameter-value": "The parameter \"$1\" must 
have a real value, not \"no value\" or \"unknown value\".",
+       "wbqc-violation-message-parameter-value-or-novalue": "The parameter 
\"$1\" must have a real value or \"no value\", not \"unknown value\".",
        "wbqc-violation-message-parameter-entity": "The value for the parameter 
\"$1\" must be an entity, not \"$2\".",
        "wbqc-violation-message-parameter-item": "The value for the parameter 
\"$1\" must be an item, not \"$2\".",
        "wbqc-violation-message-parameter-property": "The value for the 
parameter \"$1\" must be a property, not \"$2\".",
@@ -81,7 +82,7 @@
        "wbqc-violation-message-qualifier": "The property should only be used 
as a qualifier.",
        "wbqc-violation-message-no-qualifiers": "$1 statements should not have 
any qualifiers.",
        "wbqc-violation-message-qualifiers": "$2 is not a valid qualifier for 
$1 – the only valid {{PLURAL:$3|1=qualifier is $5.|2=qualifiers are $5 and 
$6.|qualifiers are:$4}}",
-       "wbqc-violation-message-range-parameters-needed": "Properties with 
values of type \"$1\" with constraint \"Range\" need the parameters \"$2\" and 
\"$3\".",
+       "wbqc-violation-message-range-parameters-needed": "Properties with 
values of type \"$1\" with constraint \"$4\" need the parameters \"$2\" and 
\"$3\".",
        "wbqc-violation-message-range": "The value for $1 ($2) should be 
between $3 and $4 ([$3, $4]).",
        "wbqc-violation-message-single-value": "This property should only 
contain a single value. That is, there should only be one statement using this 
property.",
        "wbqc-violation-message-symmetric": "$1 should also have the symmetric 
statement $2 $3.",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 66e0535..1bf5333 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -52,6 +52,7 @@
        "wbqc-violation-message-target-entity-must-exist": "Message for when an 
entity is referenced, but it doesn't exist (any more).",
        "wbqc-violation-message-value-entity-must-exist": "Message for when the 
property has an entity as its value, but it doesn't exist (any more).",
        "wbqc-violation-message-parameter-value": "Message for when \"no 
value\" or \"unknown value\" has been entered as the value of a constraint 
parameter that must be an actual value. $1 contains the parameter.",
+       "wbqc-violation-message-parameter-value-or-novalue": "Message for when 
\"unknown value\" has been entered as the value of a constraint parameter that 
must be an actual value or \"no value\". $1 contains the 
parameter.{{Related|wbqc-violation-message-parameter-value}}",
        "wbqc-violation-message-parameter-entity": "Message for when the value 
of a constraint parameter must be an entity, but is some other kind of data 
value. $1 contains the parameter, $2 the data value type.",
        "wbqc-violation-message-parameter-item": "Message for when the value of 
a constraint parameter must be an item, but is some other kind of entity. $1 
contains the parameter, $2 the data value 
type.{{Related|wbqc-violation-message-parameter-entity}}",
        "wbqc-violation-message-parameter-property": "Message for when the 
value of a constraint parameter must be a property, but is some other kind of 
entity. $1 contains the parameter, $2 the data value 
type.{{Related|wbqc-violation-message-parameter-entity}}",
@@ -76,7 +77,7 @@
        "wbqc-violation-message-qualifier": "Message for violation of Qualifier 
constraint. When the property is used in a claim.",
        "wbqc-violation-message-no-qualifiers": "Message for a violation of the 
“Qualifiers” constraint, when a statement has a qualifier but the property has 
no permitted qualifiers. This is a special case of 
{{msg-mw|wbqc-violation-message-qualifiers}}. Parameters:\n* $1 is the 
qualifier property that is not 
permitted.{{Related|wbqc-violation-message-qualifiers}}",
        "wbqc-violation-message-qualifiers": "Message for a violation of the 
“Qualifiers” constraint, when a statement has a qualifier that is not 
permitted. Parameters:\n* $1 is the property of the statement.\n* $2 is the 
qualifier property that is not permitted.\n* $3 is the number of permitted 
qualifiers. (This number is always greater than zero, since the special case of 
no permitted qualifiers is handled by the separate message 
{{msg-mw|wbqc-violation-message-no-qualifiers}}.)\n* $4 is an HTML list of all 
permitted qualifiers.\n* $5, $6 etc. are the individual permitted 
qualifiers.{{Related|wbqc-violation-message-no-qualifiers}}",
-       "wbqc-violation-message-range-parameters-needed": "Message for 
violation of Range constraint. When min and max parameters of a specific type 
are needed, but missing.",
+       "wbqc-violation-message-range-parameters-needed": "Message for when a 
constraint needs two specific parameters to form a range, but one or both of 
them are missing. Parameters:\n* $1 is the data type (\"quantity\" or 
\"time\").\n* $2 is the parameter needed for the lower boundary.\n* $3 is the 
parameter needed for the upper boundary.\n* $4 is the constraint type name 
(\"Range\" or \"Diff within range\").",
        "wbqc-violation-message-range": "Message for a violation of the “Range” 
constraint, when the value of a statement is smaller or larger than allowed. 
Parameters:\n* $1 is the property of the statement.\n* $2 is the value of the 
statement.\n* $3 is the lower bound of the range (inclusive).\n* $4 is the 
upper bound of the range (inclusive).",
        "wbqc-violation-message-single-value": "Message for violation of Single 
value constraint. When more than one value exists.",
        "wbqc-violation-message-symmetric": "Message for a violation of the 
“Symmetric” constraint, when the symmetric statement of a statement does not 
exist. $1, $2 and $3 contain the expected subject entity, property, and target 
entity of the missing symmetric statement.",
diff --git 
a/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php 
b/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php
index 8b605d6..1c921b3 100644
--- a/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php
+++ b/includes/ConstraintCheck/Helper/ConstraintStatementParameterParser.php
@@ -3,7 +3,9 @@
 namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper;
 
 use Config;
+use DataValues\DataValue;
 use DataValues\StringValue;
+use DataValues\UnboundedQuantityValue;
 use InvalidArgumentException;
 use Wikibase\DataModel\DeserializerFactory;
 use Wikibase\DataModel\Deserializers\SnakDeserializer;
@@ -15,6 +17,7 @@
 use Wikibase\DataModel\Snak\PropertySomeValueSnak;
 use Wikibase\DataModel\Snak\PropertyValueSnak;
 use Wikibase\DataModel\Snak\Snak;
+use Wikibase\Repo\Parsers\TimeParserFactory;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue;
 use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
 
@@ -441,4 +444,108 @@
                }
        }
 
+       /**
+        * @param array $parameter Snak serialization
+        * @param string $parameterId
+        * @return DataValue|null
+        */
+       private function parseValueOrNoValueParameter( array 
$snakSerialization, $parameterId ) {
+               $snak = $this->snakDeserializer->deserialize( 
$snakSerialization );
+               if ( $snak instanceof PropertyValueSnak ) {
+                       return $snak->getDataValue();
+               } elseif ( $snak instanceof PropertyNoValueSnak ) {
+                       return null;
+               } else {
+                       throw new ConstraintParameterException(
+                               wfMessage( 
'wbqc-violation-message-parameter-value-or-novalue' )
+                                       ->rawParams( 
$this->constraintParameterRenderer->formatPropertyId( $parameterId ) )
+                                       ->escaped()
+                       );
+               }
+       }
+
+       private function parseRangeParameterFromStatement( array 
$constraintParameters, $configKey ) {
+               $minimumId = $this->config->get( 'WBQualityConstraintsMinimum' 
. $configKey . 'Id' );
+               $maximumId = $this->config->get( 'WBQualityConstraintsMaximum' 
. $configKey . 'Id' );
+               $this->requireSingleParameter( $constraintParameters, 
$minimumId );
+               $this->requireSingleParameter( $constraintParameters, 
$maximumId );
+               return [
+                       $this->parseValueOrNoValueParameter( 
$constraintParameters[$minimumId][0], $minimumId ),
+                       $this->parseValueOrNoValueParameter( 
$constraintParameters[$maximumId][0], $maximumId )
+               ];
+       }
+
+       private function parseRangeParameterFromTemplate( array 
$constraintParameters, $type ) {
+               // the template parameters are always …_quantity, see T164087
+               $this->requireSingleParameter( $constraintParameters, 
'minimum_quantity' );
+               $this->requireSingleParameter( $constraintParameters, 
'maximum_quantity' );
+               if ( $type === 'quantity' ) {
+                       $min = UnboundedQuantityValue::newFromNumber( 
$constraintParameters['minimum_quantity'] );
+                       $max = UnboundedQuantityValue::newFromNumber( 
$constraintParameters['maximum_quantity'] );
+               } else {
+                       $timeParser = ( new TimeParserFactory() 
)->getTimeParser();
+                       $minStr = $constraintParameters['minimum_quantity'];
+                       $maxStr = $constraintParameters['maximum_quantity'];
+                       $now = gmdate( '+Y-m-d\T00:00:00\Z' );
+                       if ( $minStr === 'now' ) {
+                               $minStr = $now;
+                       }
+                       if ( $maxStr === 'now' ) {
+                               $maxStr = $now;
+                       }
+                       $min = $timeParser->parse( $minStr );
+                       $max = $timeParser->parse( $maxStr );
+               }
+               return [ $min, $max ];
+       }
+
+       /**
+        * @param array $constraintParameters
+        * @param string $constraintTypeName
+        * @param string $type 'quantity' or 'time'
+        * @throws ConstraintParameterException if the parameter is invalid or 
missing
+        * @return DataValue[] a pair of two quantity-type data values, either 
of which may be null to signify an open range
+        */
+       public function parseRangeParameter( array $constraintParameters, 
$constraintTypeName, $type ) {
+               switch ( $type ) {
+                       case 'quantity':
+                               $configKey = 'Quantity';
+                               break;
+                       case 'time':
+                               $configKey = 'Date';
+                               break;
+                       default:
+                               throw new ConstraintParameterException(
+                                       wfMessage( 
'wbqc-violation-message-value-needed-of-types-2' )
+                                               ->rawParams(
+                                                       wfMessage( 
'datatypes-type-quantity' )->escaped(),
+                                                       wfMessage( 
'datatypes-type-time' )->escaped()
+                                               )
+                                               ->escaped()
+                               );
+               }
+               $minimumId = $this->config->get( 'WBQualityConstraintsMinimum' 
. $configKey . 'Id' );
+               $maximumId = $this->config->get( 'WBQualityConstraintsMaximum' 
. $configKey . 'Id' );
+               if ( array_key_exists( $minimumId, $constraintParameters ) &&
+                       array_key_exists( $maximumId, $constraintParameters ) ) 
{
+                       return $this->parseRangeParameterFromStatement( 
$constraintParameters, $configKey );
+               } elseif ( array_key_exists( 'minimum_quantity', 
$constraintParameters ) &&
+                       array_key_exists( 'maximum_quantity', 
$constraintParameters ) ) {
+                       // the template parameters are always …_quantity, see 
T164087
+                       return $this->parseRangeParameterFromTemplate( 
$constraintParameters, $type );
+               } else {
+                       throw new ConstraintParameterException(
+                               wfMessage( 
'wbqc-violation-message-range-parameters-needed' )
+                                       ->params( $constraintTypeName )
+                                       ->rawParams(
+                                               wfMessage( 'datatypes-type-' . 
$type )->escaped(),
+                                               
$this->constraintParameterRenderer->formatPropertyId( $minimumId ),
+                                               
$this->constraintParameterRenderer->formatPropertyId( $maximumId )
+                                       )
+                                       ->params( $constraintTypeName )
+                                       ->escaped()
+                       );
+               }
+       }
+
 }
diff --git a/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php 
b/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php
index 15b777c..7a789ab 100644
--- a/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php
+++ b/tests/phpunit/Helper/ConstraintStatementParameterParserTest.php
@@ -3,6 +3,8 @@
 namespace WikibaseQuality\ConstraintReport\Test\Helper;
 
 use DataValues\StringValue;
+use DataValues\TimeValue;
+use DataValues\UnboundedQuantityValue;
 use Wikibase\DataModel\Entity\EntityIdValue;
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
@@ -484,4 +486,168 @@
                $this->assertEquals( [], $parsed );
        }
 
+       public function testParseRangeParameterQuantityBounded() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 
'WBQualityConstraintsMinimumQuantityId' );
+               $maximumId = $config->get( 
'WBQualityConstraintsMaximumQuantityId' );
+               $propertyId = new PropertyId( 'P1' );
+               $min = UnboundedQuantityValue::newFromNumber( 13.37 );
+               $max = UnboundedQuantityValue::newFromNumber( 42 );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               $minimumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) 
],
+                               $maximumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ]
+                       ],
+                       '',
+                       'quantity'
+               );
+
+               $this->assertEquals( [ $min, $max ], $parsed );
+       }
+
+       public function testParseRangeParameterQuantityLeftOpen() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 
'WBQualityConstraintsMinimumQuantityId' );
+               $maximumId = $config->get( 
'WBQualityConstraintsMaximumQuantityId' );
+               $propertyId = new PropertyId( 'P1' );
+               $max = UnboundedQuantityValue::newFromNumber( 42 );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               $minimumId => [ 
$this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ],
+                               $maximumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ]
+                       ],
+                       '',
+                       'quantity'
+               );
+
+               $this->assertEquals( [ null, $max ], $parsed );
+       }
+
+       public function testParseRangeParameterQuantityRightOpen() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 
'WBQualityConstraintsMinimumQuantityId' );
+               $maximumId = $config->get( 
'WBQualityConstraintsMaximumQuantityId' );
+               $propertyId = new PropertyId( 'P1' );
+               $min = UnboundedQuantityValue::newFromNumber( 13.37 );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               $minimumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) 
],
+                               $maximumId => [ 
$this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ]
+                       ],
+                       '',
+                       'quantity'
+               );
+
+               $this->assertEquals( [ $min, null ], $parsed );
+       }
+
+       public function testParseRangeParameterQuantityFullyOpen() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 
'WBQualityConstraintsMinimumQuantityId' );
+               $maximumId = $config->get( 
'WBQualityConstraintsMaximumQuantityId' );
+               $propertyId = new PropertyId( 'P1' );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               $minimumId => [ 
$this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ],
+                               $maximumId => [ 
$this->snakSerializer->serialize( new PropertyNoValueSnak( $propertyId ) ) ]
+                       ],
+                       '',
+                       'quantity'
+               );
+
+               $this->assertSame( [ null, null ], $parsed );
+       }
+
+       public function testParseRangeParameterQuantityFromTemplate() {
+               $min = UnboundedQuantityValue::newFromNumber( 13.37 );
+               $max = UnboundedQuantityValue::newFromNumber( 42 );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               'minimum_quantity' => '13.37',
+                               'maximum_quantity' => '42'
+                       ],
+                       '',
+                       'quantity'
+               );
+
+               $this->assertEquals( [ $min, $max ], $parsed );
+       }
+
+       public function testParseRangeParameterQuantitySomeValue() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 
'WBQualityConstraintsMinimumQuantityId' );
+               $maximumId = $config->get( 
'WBQualityConstraintsMaximumQuantityId' );
+               $propertyId = new PropertyId( 'P1' );
+
+               $this->assertThrowsConstraintParameterException(
+                       'parseRangeParameter',
+                       [
+                               [
+                                       $minimumId => [ 
$this->snakSerializer->serialize( new PropertySomeValueSnak( $propertyId ) ) ],
+                                       $maximumId => [ 
$this->snakSerializer->serialize( new PropertySomeValueSnak( $propertyId ) ) ]
+                               ],
+                               'constraint',
+                               'quantity'
+                       ],
+                       'wbqc-violation-message-parameter-value-or-novalue'
+               );
+       }
+
+       public function testParseRangeParameterTimeBounded() {
+               $config = $this->getDefaultConfig();
+               $minimumId = $config->get( 'WBQualityConstraintsMinimumDateId' 
);
+               $maximumId = $config->get( 'WBQualityConstraintsMaximumDateId' 
);
+               $propertyId = new PropertyId( 'P1' );
+               $calendar = 'http://www.wikidata.org/entity/Q1985727';
+               $min = new TimeValue( '+1789-05-08T00:00:00Z', 0, 0, 0, 
TimeValue::PRECISION_YEAR, $calendar );
+               $max = new TimeValue( '+1955-02-05T00:00:00Z', 0, 0, 0, 
TimeValue::PRECISION_YEAR, $calendar );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               $minimumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $min ) ) 
],
+                               $maximumId => [ 
$this->snakSerializer->serialize( new PropertyValueSnak( $propertyId, $max ) ) ]
+                       ],
+                       '',
+                       'time'
+               );
+
+               $this->assertEquals( [ $min, $max ], $parsed );
+       }
+
+       public function testParseRangeParameterTimeFromTemplate() {
+               $calendar = 'http://www.wikidata.org/entity/Q1985727';
+               $min = new TimeValue( '+1753-00-00T00:00:00Z', 0, 0, 0, 
TimeValue::PRECISION_YEAR, $calendar );
+               $max = new TimeValue( gmdate( '+Y-m-d\T00:00:00\Z' ), 0, 0, 0, 
TimeValue::PRECISION_DAY, $calendar );
+
+               $parsed = 
$this->getConstraintParameterParser()->parseRangeParameter(
+                       [
+                               'minimum_quantity' => '1753',
+                               'maximum_quantity' => 'now'
+                       ],
+                       '',
+                       'time'
+               );
+
+               $this->assertEquals( [ $min, $max ], $parsed );
+       }
+
+       public function testParseRangeParameterMissingParameters() {
+               foreach ( [ 'quantity', 'time' ] as $type ) {
+                       $this->assertThrowsConstraintParameterException(
+                               'parseRangeParameter',
+                               [
+                                       [],
+                                       'constraint',
+                                       $type
+                               ],
+                               'wbqc-violation-message-range-parameters-needed'
+                       );
+               }
+       }
+
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/359415
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I14bb590d3eb4ea421dd9d169cc737b895e857c65
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints
Gerrit-Branch: master
Gerrit-Owner: Lucas Werkmeister (WMDE) <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to