1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker; |
4
|
|
|
|
5
|
|
|
use Config; |
6
|
|
|
use DataValues\DataValue; |
7
|
|
|
use Wikibase\DataModel\Entity\EntityId; |
8
|
|
|
use Wikibase\DataModel\Entity\ItemId; |
9
|
|
|
use Wikibase\DataModel\Entity\PropertyId; |
10
|
|
|
use Wikibase\DataModel\Services\Lookup\EntityLookup; |
11
|
|
|
use Wikibase\DataModel\Snak\PropertyValueSnak; |
12
|
|
|
use Wikibase\DataModel\Statement\Statement; |
13
|
|
|
use Wikibase\DataModel\Statement\StatementList; |
14
|
|
|
use WikibaseQuality\ConstraintReport\Constraint; |
15
|
|
|
use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker; |
16
|
|
|
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context; |
17
|
|
|
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper; |
18
|
|
|
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage; |
19
|
|
|
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult; |
20
|
|
|
use WikibaseQuality\ConstraintReport\Role; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @author David Abián |
24
|
|
|
* @license GNU GPL v2+ |
25
|
|
|
*/ |
26
|
|
|
class ContemporaryChecker implements ConstraintChecker { |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var RangeCheckerHelper |
30
|
|
|
*/ |
31
|
|
|
private $rangeCheckerHelper; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var Config |
35
|
|
|
*/ |
36
|
|
|
private $config; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var EntityLookup |
40
|
|
|
*/ |
41
|
|
|
private $entityLookup; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Name of the configuration variable for the array of IDs of the properties that |
45
|
|
|
* state the start time of the entities. |
46
|
|
|
*/ |
47
|
|
|
const CONFIG_VARIABLE_START_PROPERTY_IDS = 'WBQualityConstraintsStartTimePropertyIds'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Name of the configuration variable for the array of IDs of the properties that |
51
|
|
|
* state the end time of the entities. |
52
|
|
|
*/ |
53
|
|
|
const CONFIG_VARIABLE_END_PROPERTY_IDS = 'WBQualityConstraintsEndTimePropertyIds'; |
54
|
|
|
|
55
|
|
|
public function __construct( |
56
|
|
|
EntityLookup $entityLookup, |
57
|
|
|
RangeCheckerHelper $rangeCheckerHelper, |
58
|
|
|
Config $config |
59
|
|
|
) { |
60
|
|
|
$this->entityLookup = $entityLookup; |
61
|
|
|
$this->rangeCheckerHelper = $rangeCheckerHelper; |
62
|
|
|
$this->config = $config; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @codeCoverageIgnore This method is purely declarative. |
67
|
|
|
*/ |
68
|
|
|
public function getSupportedContextTypes() { |
69
|
|
|
return [ |
70
|
|
|
Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE, |
71
|
|
|
Context::TYPE_QUALIFIER => CheckResult::STATUS_NOT_IN_SCOPE, |
72
|
|
|
Context::TYPE_REFERENCE => CheckResult::STATUS_NOT_IN_SCOPE, |
73
|
|
|
]; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @codeCoverageIgnore This method is purely declarative. |
78
|
|
|
*/ |
79
|
|
|
public function getDefaultContextTypes() { |
80
|
|
|
return [ Context::TYPE_STATEMENT ]; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Checks 'Contemporary' constraint. |
85
|
|
|
* |
86
|
|
|
* @param Context $context |
87
|
|
|
* @param Constraint $constraint |
88
|
|
|
* |
89
|
|
|
* @return CheckResult |
90
|
|
|
* @throws \ConfigException |
91
|
|
|
*/ |
92
|
|
|
public function checkConstraint( Context $context, Constraint $constraint ) { |
93
|
|
|
if ( $context->getSnakRank() === Statement::RANK_DEPRECATED ) { |
94
|
|
|
return new CheckResult( $context, $constraint, [], CheckResult::STATUS_DEPRECATED ); |
95
|
|
|
} |
96
|
|
|
$snak = $context->getSnak(); |
97
|
|
|
if ( !$snak instanceof PropertyValueSnak ) { |
98
|
|
|
// nothing to check |
99
|
|
|
return new CheckResult( $context, $constraint, [], CheckResult::STATUS_COMPLIANCE ); |
100
|
|
View Code Duplication |
} elseif ( $snak->getDataValue()->getType() !== 'wikibase-entityid' ) { |
|
|
|
|
101
|
|
|
// wrong data type |
102
|
|
|
$message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-type' ) ) |
103
|
|
|
->withEntityId( new ItemId( $constraint->getConstraintTypeItemId() ), Role::CONSTRAINT_TYPE_ITEM ) |
104
|
|
|
->withDataValueType( 'wikibase-entityid' ); |
105
|
|
|
return new CheckResult( $context, $constraint, [], CheckResult::STATUS_VIOLATION, $message ); |
106
|
|
|
} |
107
|
|
|
/** @var EntityId $subjectId */ |
108
|
|
|
$subjectId = $context->getEntity()->getId(); |
109
|
|
|
/** @var EntityId $objectId */ |
110
|
|
|
$objectId = $snak->getDataValue()->getEntityId(); |
|
|
|
|
111
|
|
|
/** @var Statement[] $subjectStatements */ |
112
|
|
|
$subjectStatements = $context->getEntity()->getStatements()->toArray(); |
113
|
|
|
/** @var Statement[] $objectStatements */ |
114
|
|
|
$objectStatements = $this->entityLookup->getEntity( $objectId )->getStatements()->toArray(); |
115
|
|
|
/** @var String[] $startPropertyIds */ |
116
|
|
|
$startPropertyIds = $this->config->get( self::CONFIG_VARIABLE_START_PROPERTY_IDS ); |
117
|
|
|
/** @var String[] $endPropertyIds */ |
118
|
|
|
$endPropertyIds = $this->config->get( self::CONFIG_VARIABLE_END_PROPERTY_IDS ); |
119
|
|
|
$subjectStartValue = $this->getExtremeValue( |
120
|
|
|
$startPropertyIds, |
121
|
|
|
$subjectStatements, |
122
|
|
|
'start' |
123
|
|
|
); |
124
|
|
|
$objectStartValue = $this->getExtremeValue( |
125
|
|
|
$startPropertyIds, |
126
|
|
|
$objectStatements, |
127
|
|
|
'start' |
128
|
|
|
); |
129
|
|
|
$subjectEndValue = $this->getExtremeValue( |
130
|
|
|
$endPropertyIds, |
131
|
|
|
$subjectStatements, |
132
|
|
|
'end' |
133
|
|
|
); |
134
|
|
|
$objectEndValue = $this->getExtremeValue( |
135
|
|
|
$endPropertyIds, |
136
|
|
|
$objectStatements, |
137
|
|
|
'end' |
138
|
|
|
); |
139
|
|
|
if ( |
140
|
|
|
$this->rangeCheckerHelper->getComparison( $subjectStartValue, $subjectEndValue ) <= 0 && |
141
|
|
|
$this->rangeCheckerHelper->getComparison( $objectStartValue, $objectEndValue ) <= 0 && ( |
142
|
|
|
$this->rangeCheckerHelper->getComparison( $subjectEndValue, $objectStartValue ) < 0 || |
143
|
|
|
$this->rangeCheckerHelper->getComparison( $objectEndValue, $subjectStartValue ) < 0 |
144
|
|
|
) |
145
|
|
|
) { |
146
|
|
|
if ( |
147
|
|
|
$subjectEndValue == null || |
148
|
|
|
$this->rangeCheckerHelper->getComparison( $objectEndValue, $subjectEndValue ) < 0 |
149
|
|
|
) { |
150
|
|
|
$earlierEntityId = $objectId; |
151
|
|
|
$minEndValue = $objectEndValue; |
152
|
|
|
$maxStartValue = $subjectStartValue; |
153
|
|
|
} else { |
154
|
|
|
$earlierEntityId = $subjectId; |
155
|
|
|
$minEndValue = $subjectEndValue; |
156
|
|
|
$maxStartValue = $objectStartValue; |
157
|
|
|
} |
158
|
|
|
$message = $this->getViolationMessage( |
159
|
|
|
$earlierEntityId, |
160
|
|
|
$subjectId, |
161
|
|
|
$context->getSnak()->getPropertyId(), |
162
|
|
|
$objectId, |
163
|
|
|
$minEndValue, |
|
|
|
|
164
|
|
|
$maxStartValue |
|
|
|
|
165
|
|
|
); |
166
|
|
|
$status = CheckResult::STATUS_VIOLATION; |
167
|
|
|
} else { |
168
|
|
|
$message = null; |
169
|
|
|
$status = CheckResult::STATUS_COMPLIANCE; |
170
|
|
|
} |
171
|
|
|
return new CheckResult( $context, $constraint, [], $status, $message ); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @param String[] $extremePropertyIds |
176
|
|
|
* @param Statement[] $statements |
177
|
|
|
* @param string $startOrEnd 'start' or 'end' |
178
|
|
|
* |
179
|
|
|
* @return DataValue|null |
180
|
|
|
*/ |
181
|
|
|
private function getExtremeValue( $extremePropertyIds, $statements, $startOrEnd ) { |
182
|
|
|
if ( $startOrEnd !== 'start' && $startOrEnd !== 'end' ) { |
183
|
|
|
throw new \InvalidArgumentException( '$startOrEnd must be \'start\' or \'end\'.' ); |
184
|
|
|
} |
185
|
|
|
$extremeValue = null; |
186
|
|
|
foreach ( $extremePropertyIds as $extremePropertyId ) { |
187
|
|
|
$statementList = new StatementList( $statements ); |
188
|
|
|
$extremeStatements = $statementList->getByPropertyId( new PropertyId( $extremePropertyId ) ); |
189
|
|
|
/** @var Statement $extremeStatement */ |
190
|
|
|
foreach ( $extremeStatements as $extremeStatement ) { |
191
|
|
|
if ( $extremeStatement->getRank() !== Statement::RANK_DEPRECATED ) { |
192
|
|
|
$snak = $extremeStatement->getMainSnak(); |
193
|
|
|
if ( !$snak instanceof PropertyValueSnak ) { |
194
|
|
|
return null; |
195
|
|
|
} else { |
196
|
|
|
$comparison = $this->rangeCheckerHelper->getComparison( |
197
|
|
|
$snak->getDataValue(), |
198
|
|
|
$extremeValue |
199
|
|
|
); |
200
|
|
|
if ( |
201
|
|
|
$extremeValue === null || |
202
|
|
|
( $startOrEnd === 'start' && $comparison < 0 ) || |
203
|
|
|
( $startOrEnd === 'end' && $comparison > 0 ) |
204
|
|
|
) { |
205
|
|
|
$extremeValue = $snak->getDataValue(); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
return $extremeValue; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @param EntityId $earlierEntityId |
216
|
|
|
* @param EntityId $subjectId |
217
|
|
|
* @param EntityId $propertyId |
218
|
|
|
* @param EntityId $objectId |
219
|
|
|
* @param DataValue $minEndValue |
220
|
|
|
* @param DataValue $maxStartValue |
221
|
|
|
* |
222
|
|
|
* @return ViolationMessage |
223
|
|
|
*/ |
224
|
|
|
private function getViolationMessage( |
225
|
|
|
EntityId $earlierEntityId, |
226
|
|
|
EntityId $subjectId, |
227
|
|
|
EntityId $propertyId, |
228
|
|
|
EntityId $objectId, |
229
|
|
|
DataValue $minEndValue, |
230
|
|
|
DataValue $maxStartValue |
231
|
|
|
) { |
232
|
|
|
$messageKey = $earlierEntityId === $subjectId ? |
233
|
|
|
'wbqc-violation-message-contemporary-subject-earlier' : |
234
|
|
|
'wbqc-violation-message-contemporary-value-earlier'; |
235
|
|
|
return ( new ViolationMessage( $messageKey ) ) |
236
|
|
|
->withEntityId( $subjectId, Role::SUBJECT ) |
237
|
|
|
->withEntityId( $propertyId, Role::PREDICATE ) |
238
|
|
|
->withEntityId( $objectId, Role::OBJECT ) |
239
|
|
|
->withDataValue( $minEndValue, Role::OBJECT ) |
240
|
|
|
->withDataValue( $maxStartValue, Role::OBJECT ); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
public function checkConstraintParameters( Constraint $constraint ) { |
244
|
|
|
// no parameters |
245
|
|
|
return []; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
} |
249
|
|
|
|
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.