blob: b3f806f874a779880fb776abda82e9a4ecd04f21 [file] [log] [blame]
Jonas Keutel0ff150f2015-04-27 17:24:21 +02001<?php
2
Dominic Sauer630cb1e2015-05-18 10:16:20 +02003namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
Jonas Keutel0ff150f2015-04-27 17:24:21 +02004
Lucas Werkmeisterc2aa62f2017-04-07 19:00:07 +02005use Config;
Thiemo Mättigf3304812016-03-22 17:13:31 +01006use Wikibase\DataModel\Entity\EntityIdValue;
Lucas Werkmeister2d0fe162017-06-07 13:27:38 +02007use Wikibase\DataModel\Entity\ItemId;
Umherirrender1bc588d2019-09-27 19:26:23 +02008use Wikibase\DataModel\Entity\StatementListProvidingEntity;
addshore44a87b92015-08-14 12:32:12 +02009use Wikibase\DataModel\Services\Lookup\EntityLookup;
Marius Hoch33931d82018-11-02 02:54:21 +010010use Wikibase\DataModel\Services\Lookup\UnresolvedEntityRedirectException;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020011use Wikibase\DataModel\Snak\PropertyValueSnak;
addshore2a295b02020-04-30 15:48:03 +010012use Wikibase\DataModel\Statement\Statement;
Dominic Sauer630cb1e2015-05-18 10:16:20 +020013use WikibaseQuality\ConstraintReport\Constraint;
14use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +020015use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
Lucas Werkmeisterb849eec2017-07-07 12:46:59 +020016use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException;
Lucas Werkmeister03b74c62017-06-26 12:47:52 +020017use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
Lucas Werkmeister8f9b7b32017-06-12 15:20:37 +020018use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelperException;
Dominic Sauer630cb1e2015-05-18 10:16:20 +020019use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\TypeCheckerHelper;
addshore2a295b02020-04-30 15:48:03 +010020use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
21use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
Lucas Werkmeister6ec3d2a2017-07-31 18:20:59 +020022use WikibaseQuality\ConstraintReport\Role;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020023
24/**
Jonas Keutel0ff150f2015-04-27 17:24:21 +020025 * @author BP2014N1
Lucas Werkmeister5129b6d2018-03-03 01:36:39 +010026 * @license GPL-2.0-or-later
Jonas Keutel0ff150f2015-04-27 17:24:21 +020027 */
28class ValueTypeChecker implements ConstraintChecker {
29
30 /**
Lucas Werkmeister03b74c62017-06-26 12:47:52 +020031 * @var ConstraintParameterParser
Jonas Keutel0ff150f2015-04-27 17:24:21 +020032 */
Lucas Werkmeister2d0fe162017-06-07 13:27:38 +020033 private $constraintParameterParser;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020034
35 /**
36 * @var EntityLookup
37 */
38 private $entityLookup;
39
40 /**
41 * @var TypeCheckerHelper
42 */
43 private $typeCheckerHelper;
44
Lucas Werkmeisterc2aa62f2017-04-07 19:00:07 +020045 /**
46 * @var Config
47 */
48 private $config;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020049
Lucas Werkmeister2d0fe162017-06-07 13:27:38 +020050 public function __construct(
51 EntityLookup $lookup,
Lucas Werkmeister03b74c62017-06-26 12:47:52 +020052 ConstraintParameterParser $constraintParameterParser,
Lucas Werkmeister2d0fe162017-06-07 13:27:38 +020053 TypeCheckerHelper $typeCheckerHelper,
54 Config $config
55 ) {
Jonas Keutel0ff150f2015-04-27 17:24:21 +020056 $this->entityLookup = $lookup;
Lucas Werkmeister2d0fe162017-06-07 13:27:38 +020057 $this->constraintParameterParser = $constraintParameterParser;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020058 $this->typeCheckerHelper = $typeCheckerHelper;
Lucas Werkmeisterc2aa62f2017-04-07 19:00:07 +020059 $this->config = $config;
Jonas Keutel0ff150f2015-04-27 17:24:21 +020060 }
61
62 /**
Lucas Werkmeistercccebd12018-01-08 16:36:55 +010063 * @codeCoverageIgnore This method is purely declarative.
64 */
65 public function getSupportedContextTypes() {
Lucas Werkmeister537b5272021-08-25 10:49:00 +020066 return self::ALL_CONTEXT_TYPES_SUPPORTED;
Lucas Werkmeistercccebd12018-01-08 16:36:55 +010067 }
68
69 /**
70 * @codeCoverageIgnore This method is purely declarative.
71 */
72 public function getDefaultContextTypes() {
Lucas Werkmeister537b5272021-08-25 10:49:00 +020073 return Context::ALL_CONTEXT_TYPES;
Lucas Werkmeistercccebd12018-01-08 16:36:55 +010074 }
75
Lucas Werkmeister44eedce2021-08-19 16:56:16 +020076 /** @codeCoverageIgnore This method is purely declarative. */
77 public function getSupportedEntityTypes() {
78 return self::ALL_ENTITY_TYPES_SUPPORTED;
79 }
80
Lucas Werkmeistercccebd12018-01-08 16:36:55 +010081 /**
Jonas Keutel0ff150f2015-04-27 17:24:21 +020082 * Checks 'Value type' constraint.
83 *
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +020084 * @param Context $context
Jonas Keutel5cd8d882015-05-05 16:44:32 +020085 * @param Constraint $constraint
Jonas Keutel0ff150f2015-04-27 17:24:21 +020086 *
Lucas Werkmeisterd9377e82018-01-10 17:06:56 +010087 * @throws ConstraintParameterException
Lucas Werkmeister8f9b7b32017-06-12 15:20:37 +020088 * @throws SparqlHelperException if the checker uses SPARQL and the query times out or some other error occurs
Lucas Werkmeisterd9377e82018-01-10 17:06:56 +010089 * @return CheckResult
Jonas Keutel0ff150f2015-04-27 17:24:21 +020090 */
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +020091 public function checkConstraint( Context $context, Constraint $constraint ) {
92 if ( $context->getSnakRank() === Statement::RANK_DEPRECATED ) {
Lucas Werkmeister82bde5c2023-07-26 12:44:56 +020093 return new CheckResult( $context, $constraint, CheckResult::STATUS_DEPRECATED );
Lucas Werkmeister98925812017-07-12 13:34:15 +020094 }
95
Jonas Keutelde83ac42015-05-13 16:10:20 +020096 $constraintParameters = $constraint->getConstraintParameters();
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +020097 $constraintTypeItemId = $constraint->getConstraintTypeItemId();
Jonas Keutel0ff150f2015-04-27 17:24:21 +020098
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +020099 $classes = $this->constraintParameterParser->parseClassParameter(
100 $constraintParameters,
101 $constraintTypeItemId
102 );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200103
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +0200104 $relation = $this->constraintParameterParser->parseRelationParameter(
105 $constraintParameters,
106 $constraintTypeItemId
107 );
Lucas Werkmeister153883b2018-01-17 15:17:44 +0100108 $relationIds = [];
109 if ( $relation === 'instance' || $relation === 'instanceOrSubclass' ) {
110 $relationIds[] = $this->config->get( 'WBQualityConstraintsInstanceOfId' );
111 }
112 if ( $relation === 'subclass' || $relation === 'instanceOrSubclass' ) {
113 $relationIds[] = $this->config->get( 'WBQualityConstraintsSubclassOfId' );
Jonas Keutelde83ac42015-05-13 16:10:20 +0200114 }
tamslo495f7a82015-06-17 16:54:21 +0200115
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +0200116 $snak = $context->getSnak();
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200117
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +0200118 if ( !$snak instanceof PropertyValueSnak ) {
Lucas Werkmeister09008e12017-08-02 11:26:18 +0200119 // nothing to check
Lucas Werkmeister82bde5c2023-07-26 12:44:56 +0200120 return new CheckResult( $context, $constraint, CheckResult::STATUS_COMPLIANCE );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200121 }
122
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +0200123 $dataValue = $snak->getDataValue();
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200124
125 /*
126 * error handling:
127 * type of $dataValue for properties with 'Value type' constraint has to be 'wikibase-entityid'
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200128 */
Umherirrender99120982019-09-23 21:41:10 +0200129 if ( !$dataValue instanceof EntityIdValue ) {
Lucas Werkmeisterbed86582018-01-31 15:51:03 +0100130 $message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-type' ) )
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +0200131 ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
Lucas Werkmeisterbed86582018-01-31 15:51:03 +0100132 ->withDataValueType( 'wikibase-entityid' );
Lucas Werkmeister82bde5c2023-07-26 12:44:56 +0200133 return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, $message );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200134 }
Thiemo Mättigf3304812016-03-22 17:13:31 +0100135
Marius Hoch33931d82018-11-02 02:54:21 +0100136 try {
137 $item = $this->entityLookup->getEntity( $dataValue->getEntityId() );
138 } catch ( UnresolvedEntityRedirectException $e ) {
139 // Edge case (double redirect): Pretend the entity doesn't exist
140 $item = null;
141 }
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200142
Umherirrender1bc588d2019-09-27 19:26:23 +0200143 if ( !( $item instanceof StatementListProvidingEntity ) ) {
Lucas Werkmeister61c01042018-01-26 17:10:56 +0100144 $message = new ViolationMessage( 'wbqc-violation-message-value-entity-must-exist' );
Lucas Werkmeister82bde5c2023-07-26 12:44:56 +0200145 return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, $message );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200146 }
147
148 $statements = $item->getStatements();
149
Lucas Werkmeister57077232017-11-14 18:57:42 +0100150 $result = $this->typeCheckerHelper->hasClassInRelation(
151 $statements,
Lucas Werkmeister153883b2018-01-17 15:17:44 +0100152 $relationIds,
Lucas Werkmeister57077232017-11-14 18:57:42 +0100153 $classes
154 );
155
156 if ( $result->getBool() ) {
Lucas Werkmeisterf91f8832018-01-26 16:19:22 +0100157 $message = null;
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200158 $status = CheckResult::STATUS_COMPLIANCE;
159 } else {
Lucas Werkmeister58e313e2017-05-02 16:46:37 +0200160 $message = $this->typeCheckerHelper->getViolationMessage(
Lucas Werkmeisterf50361f2017-08-01 17:24:27 +0200161 $context->getSnak()->getPropertyId(),
Lucas Werkmeister58e313e2017-05-02 16:46:37 +0200162 $item->getId(),
163 $classes,
164 'valueType',
165 $relation
166 );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200167 $status = CheckResult::STATUS_VIOLATION;
168 }
169
Lucas Werkmeister82bde5c2023-07-26 12:44:56 +0200170 return ( new CheckResult( $context, $constraint, $status, $message ) )
Lucas Werkmeister8f81f952017-12-18 18:10:45 +0100171 ->withMetadata( $result->getMetadata() );
Jonas Keutel0ff150f2015-04-27 17:24:21 +0200172 }
tamslo495f7a82015-06-17 16:54:21 +0200173
Lucas Werkmeisterb849eec2017-07-07 12:46:59 +0200174 public function checkConstraintParameters( Constraint $constraint ) {
175 $constraintParameters = $constraint->getConstraintParameters();
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +0200176 $constraintTypeItemId = $constraint->getConstraintTypeItemId();
Lucas Werkmeisterb849eec2017-07-07 12:46:59 +0200177 $exceptions = [];
178 try {
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +0200179 $this->constraintParameterParser->parseClassParameter(
180 $constraintParameters,
181 $constraintTypeItemId
182 );
Lucas Werkmeisterb849eec2017-07-07 12:46:59 +0200183 } catch ( ConstraintParameterException $e ) {
184 $exceptions[] = $e;
185 }
186 try {
Lucas Werkmeistercb8298e2019-04-02 20:25:02 +0200187 $this->constraintParameterParser->parseRelationParameter(
188 $constraintParameters,
189 $constraintTypeItemId
190 );
Lucas Werkmeisterb849eec2017-07-07 12:46:59 +0200191 } catch ( ConstraintParameterException $e ) {
192 $exceptions[] = $e;
193 }
194 return $exceptions;
195 }
196
Thiemo Mättigf0260522016-03-03 15:32:33 +0100197}