Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.74% |
90 / 94 |
|
71.43% |
5 / 7 |
CRAP | |
0.00% |
0 / 1 |
FormatChecker | |
95.74% |
90 / 94 |
|
71.43% |
5 / 7 |
25 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
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 | |
94.29% |
33 / 35 |
|
0.00% |
0 / 1 |
5.00 | |||
formatMessage | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
runRegexCheck | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
runRegexCheckUsingShellbox | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
4 | |||
runRegexCheckUsingSparql | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
checkConstraintParameters | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
3.02 |
1 | <?php |
2 | |
3 | namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker; |
4 | |
5 | use DataValues\MonolingualTextValue; |
6 | use DataValues\MultilingualTextValue; |
7 | use DataValues\StringValue; |
8 | use MediaWiki\Config\Config; |
9 | use MediaWiki\Shell\ShellboxClientFactory; |
10 | use Shellbox\ShellboxError; |
11 | use Wikibase\DataModel\Entity\ItemId; |
12 | use Wikibase\DataModel\Entity\PropertyId; |
13 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
14 | use WikibaseQuality\ConstraintReport\Constraint; |
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\DummySparqlHelper; |
20 | use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper; |
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 FormatChecker implements ConstraintChecker { |
30 | |
31 | /** |
32 | * @var ConstraintParameterParser |
33 | */ |
34 | private $constraintParameterParser; |
35 | |
36 | /** |
37 | * @var SparqlHelper |
38 | */ |
39 | private $sparqlHelper; |
40 | |
41 | /** |
42 | * @var Config |
43 | */ |
44 | private $config; |
45 | |
46 | /** |
47 | * @var ShellboxClientFactory |
48 | */ |
49 | private $shellboxClientFactory; |
50 | |
51 | /** |
52 | * @param ConstraintParameterParser $constraintParameterParser |
53 | * @param Config $config |
54 | * @param SparqlHelper $sparqlHelper |
55 | * @param ShellboxClientFactory $shellboxClientFactory |
56 | */ |
57 | public function __construct( |
58 | ConstraintParameterParser $constraintParameterParser, |
59 | Config $config, |
60 | SparqlHelper $sparqlHelper, |
61 | ShellboxClientFactory $shellboxClientFactory |
62 | ) { |
63 | $this->constraintParameterParser = $constraintParameterParser; |
64 | $this->config = $config; |
65 | $this->sparqlHelper = $sparqlHelper; |
66 | $this->shellboxClientFactory = $shellboxClientFactory; |
67 | } |
68 | |
69 | /** |
70 | * @codeCoverageIgnore This method is purely declarative. |
71 | */ |
72 | public function getSupportedContextTypes() { |
73 | return self::ALL_CONTEXT_TYPES_SUPPORTED; |
74 | } |
75 | |
76 | /** |
77 | * @codeCoverageIgnore This method is purely declarative. |
78 | */ |
79 | public function getDefaultContextTypes() { |
80 | return Context::ALL_CONTEXT_TYPES; |
81 | } |
82 | |
83 | /** @codeCoverageIgnore This method is purely declarative. */ |
84 | public function getSupportedEntityTypes() { |
85 | return self::ALL_ENTITY_TYPES_SUPPORTED; |
86 | } |
87 | |
88 | /** |
89 | * Checks 'Format' constraint. |
90 | * |
91 | * @param Context $context |
92 | * @param Constraint $constraint |
93 | * |
94 | * @throws ConstraintParameterException |
95 | * @return CheckResult |
96 | */ |
97 | public function checkConstraint( Context $context, Constraint $constraint ) { |
98 | $constraintParameters = $constraint->getConstraintParameters(); |
99 | $constraintTypeItemId = $constraint->getConstraintTypeItemId(); |
100 | |
101 | $format = $this->constraintParameterParser->parseFormatParameter( |
102 | $constraintParameters, |
103 | $constraintTypeItemId |
104 | ); |
105 | |
106 | $syntaxClarifications = $this->constraintParameterParser->parseSyntaxClarificationParameter( |
107 | $constraintParameters |
108 | ); |
109 | |
110 | $snak = $context->getSnak(); |
111 | |
112 | if ( !$snak instanceof PropertyValueSnak ) { |
113 | // nothing to check |
114 | return new CheckResult( $context, $constraint, CheckResult::STATUS_COMPLIANCE ); |
115 | } |
116 | |
117 | $dataValue = $snak->getDataValue(); |
118 | |
119 | /* |
120 | * error handling: |
121 | * type of $dataValue for properties with 'Format' constraint has to be 'string' or 'monolingualtext' |
122 | */ |
123 | switch ( $dataValue->getType() ) { |
124 | case 'string': |
125 | $text = $dataValue->getValue(); |
126 | break; |
127 | case 'monolingualtext': |
128 | /** @var MonolingualTextValue $dataValue */ |
129 | '@phan-var MonolingualTextValue $dataValue'; |
130 | $text = $dataValue->getText(); |
131 | break; |
132 | default: |
133 | $message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-types-2' ) ) |
134 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ) |
135 | ->withDataValueType( 'string' ) |
136 | ->withDataValueType( 'monolingualtext' ); |
137 | return new CheckResult( $context, $constraint, CheckResult::STATUS_VIOLATION, $message ); |
138 | } |
139 | $status = $this->runRegexCheck( $text, $format ); |
140 | $message = $this->formatMessage( |
141 | $status, |
142 | $text, |
143 | $format, |
144 | $context->getSnak()->getPropertyId(), |
145 | $syntaxClarifications, |
146 | $constraintTypeItemId |
147 | ); |
148 | return new CheckResult( $context, $constraint, $status, $message ); |
149 | } |
150 | |
151 | private function formatMessage( |
152 | string $status, |
153 | string $text, |
154 | string $format, |
155 | PropertyId $propertyId, |
156 | MultilingualTextValue $syntaxClarifications, |
157 | string $constraintTypeItemId |
158 | ): ?ViolationMessage { |
159 | $message = null; |
160 | if ( $status === CheckResult::STATUS_VIOLATION ) { |
161 | $message = ( new ViolationMessage( 'wbqc-violation-message-format-clarification' ) ) |
162 | ->withEntityId( $propertyId, Role::CONSTRAINT_PROPERTY ) |
163 | ->withDataValue( new StringValue( $text ), Role::OBJECT ) |
164 | ->withInlineCode( $format, Role::CONSTRAINT_PARAMETER_VALUE ) |
165 | ->withMultilingualText( $syntaxClarifications, Role::CONSTRAINT_PARAMETER_VALUE ); |
166 | } elseif ( $status === CheckResult::STATUS_TODO ) { |
167 | $message = ( new ViolationMessage( 'wbqc-violation-message-security-reason' ) ) |
168 | ->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM ); |
169 | } |
170 | |
171 | return $message; |
172 | } |
173 | |
174 | private function runRegexCheck( string $text, string $format ): string { |
175 | if ( !$this->config->get( 'WBQualityConstraintsCheckFormatConstraint' ) ) { |
176 | return CheckResult::STATUS_TODO; |
177 | } |
178 | if ( |
179 | $this->config->get( 'WBQualityConstraintsFormatCheckerShellboxRatio' ) > (float)wfRandom() |
180 | ) { |
181 | return $this->runRegexCheckUsingShellbox( $text, $format ); |
182 | } |
183 | |
184 | return $this->runRegexCheckUsingSparql( $text, $format ); |
185 | } |
186 | |
187 | private function runRegexCheckUsingShellbox( string $text, string $format ): string { |
188 | if ( !$this->shellboxClientFactory->isEnabled( 'constraint-regex-checker' ) ) { |
189 | return CheckResult::STATUS_TODO; |
190 | } |
191 | try { |
192 | $pattern = '/^(?:' . str_replace( '/', '\/', $format ) . ')$/u'; |
193 | $shellboxResponse = $this->shellboxClientFactory->getClient( [ |
194 | 'timeout' => $this->config->get( 'WBQualityConstraintsSparqlMaxMillis' ) / 1000, |
195 | 'service' => 'constraint-regex-checker', |
196 | ] )->call( |
197 | 'constraint-regex-checker', |
198 | 'preg_match', |
199 | [ $pattern, $text ] |
200 | ); |
201 | } catch ( ShellboxError $exception ) { |
202 | throw new ConstraintParameterException( |
203 | ( new ViolationMessage( 'wbqc-violation-message-parameter-regex' ) ) |
204 | ->withInlineCode( $pattern, Role::CONSTRAINT_PARAMETER_VALUE ) |
205 | ); |
206 | } |
207 | |
208 | if ( $shellboxResponse ) { |
209 | return CheckResult::STATUS_COMPLIANCE; |
210 | } else { |
211 | return CheckResult::STATUS_VIOLATION; |
212 | } |
213 | } |
214 | |
215 | private function runRegexCheckUsingSparql( string $text, string $format ): string { |
216 | if ( $this->sparqlHelper instanceof DummySparqlHelper ) { |
217 | return CheckResult::STATUS_TODO; |
218 | } |
219 | |
220 | if ( $this->sparqlHelper->matchesRegularExpression( $text, $format ) ) { |
221 | return CheckResult::STATUS_COMPLIANCE; |
222 | } else { |
223 | return CheckResult::STATUS_VIOLATION; |
224 | } |
225 | } |
226 | |
227 | public function checkConstraintParameters( Constraint $constraint ) { |
228 | $constraintParameters = $constraint->getConstraintParameters(); |
229 | $constraintTypeItemId = $constraint->getConstraintTypeItemId(); |
230 | $exceptions = []; |
231 | try { |
232 | $this->constraintParameterParser->parseFormatParameter( |
233 | $constraintParameters, |
234 | $constraintTypeItemId |
235 | ); |
236 | } catch ( ConstraintParameterException $e ) { |
237 | $exceptions[] = $e; |
238 | } |
239 | try { |
240 | $this->constraintParameterParser->parseSyntaxClarificationParameter( |
241 | $constraintParameters |
242 | ); |
243 | } catch ( ConstraintParameterException $e ) { |
244 | $exceptions[] = $e; |
245 | } |
246 | return $exceptions; |
247 | } |
248 | |
249 | } |