Completed
Push — master ( f6c6d9...a2958c )
by
unknown
02:14 queued 11s
created

FormatChecker::runRegexCheckUsingShellbox()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
cc 4
nc 5
nop 2
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
4
5
use Config;
6
use DataValues\MonolingualTextValue;
7
use DataValues\MultilingualTextValue;
8
use DataValues\StringValue;
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 [
74
			Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE,
75
			Context::TYPE_QUALIFIER => CheckResult::STATUS_COMPLIANCE,
76
			Context::TYPE_REFERENCE => CheckResult::STATUS_COMPLIANCE,
77
		];
78
	}
79
80
	/**
81
	 * @codeCoverageIgnore This method is purely declarative.
82
	 */
83
	public function getDefaultContextTypes() {
84
		return [
85
			Context::TYPE_STATEMENT,
86
			Context::TYPE_QUALIFIER,
87
			Context::TYPE_REFERENCE,
88
		];
89
	}
90
91
	/**
92
	 * Checks 'Format' constraint.
93
	 *
94
	 * @param Context $context
95
	 * @param Constraint $constraint
96
	 *
97
	 * @throws ConstraintParameterException
98
	 * @return CheckResult
99
	 */
100
	public function checkConstraint( Context $context, Constraint $constraint ) {
101
		$parameters = [];
102
		$constraintParameters = $constraint->getConstraintParameters();
103
		$constraintTypeItemId = $constraint->getConstraintTypeItemId();
104
105
		$format = $this->constraintParameterParser->parseFormatParameter(
106
			$constraintParameters,
107
			$constraintTypeItemId
108
		);
109
		$parameters['pattern'] = [ $format ];
110
111
		$syntaxClarifications = $this->constraintParameterParser->parseSyntaxClarificationParameter(
112
			$constraintParameters
113
		);
114
115
		$snak = $context->getSnak();
116
117
		if ( !$snak instanceof PropertyValueSnak ) {
118
			// nothing to check
119
			return new CheckResult( $context, $constraint, $parameters, CheckResult::STATUS_COMPLIANCE );
120
		}
121
122
		$dataValue = $snak->getDataValue();
123
124
		/*
125
		 * error handling:
126
		 *   type of $dataValue for properties with 'Format' constraint has to be 'string' or 'monolingualtext'
127
		 */
128
		switch ( $dataValue->getType() ) {
129
			case 'string':
130
				$text = $dataValue->getValue();
131
				break;
132
			case 'monolingualtext':
133
				/** @var MonolingualTextValue $dataValue */
134
				'@phan-var MonolingualTextValue $dataValue';
135
				$text = $dataValue->getText();
136
				break;
137
			default:
138
				$message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-types-2' ) )
139
					->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM )
140
					->withDataValueType( 'string' )
141
					->withDataValueType( 'monolingualtext' );
142
				return new CheckResult( $context, $constraint, $parameters, CheckResult::STATUS_VIOLATION, $message );
143
		}
144
		$status = $this->runRegexCheck( $text, $format );
145
		$message = $this->formatMessage(
146
			$status,
147
			$text,
148
			$format,
149
			$context->getSnak()->getPropertyId(),
150
			$syntaxClarifications,
151
			$constraintTypeItemId
152
		);
153
		return new CheckResult( $context, $constraint, $parameters, $status, $message );
154
	}
155
156
	private function formatMessage(
157
		string $status,
158
		string $text,
159
		string $format,
160
		PropertyId $propertyId,
161
		MultilingualTextValue $syntaxClarifications,
162
		string $constraintTypeItemId
163
	): ?ViolationMessage {
164
		$message = null;
165
		if ( $status === CheckResult::STATUS_VIOLATION ) {
166
			$message = ( new ViolationMessage( 'wbqc-violation-message-format-clarification' ) )
167
				->withEntityId( $propertyId, Role::CONSTRAINT_PROPERTY )
168
				->withDataValue( new StringValue( $text ), Role::OBJECT )
169
				->withInlineCode( $format, Role::CONSTRAINT_PARAMETER_VALUE )
170
				->withMultilingualText( $syntaxClarifications, Role::CONSTRAINT_PARAMETER_VALUE );
171
		} elseif ( $status === CheckResult::STATUS_TODO ) {
172
			$message = ( new ViolationMessage( 'wbqc-violation-message-security-reason' ) )
173
				->withEntityId( new ItemId( $constraintTypeItemId ), Role::CONSTRAINT_TYPE_ITEM );
174
		}
175
176
		return $message;
177
	}
178
179
	private function runRegexCheck( string $text, string $format ): string {
180
		if ( !$this->config->get( 'WBQualityConstraintsCheckFormatConstraint' ) ) {
181
			return CheckResult::STATUS_TODO;
182
		}
183
		if (
184
			$this->config->get( 'WBQualityConstraintsFormatCheckerShellboxRatio' ) > (float)wfRandom()
185
		) {
186
			return $this->runRegexCheckUsingShellbox( $text, $format );
187
		}
188
189
		return $this->runRegexCheckUsingSparql( $text, $format );
190
	}
191
192
	private function runRegexCheckUsingShellbox( string $text, string $format ): string {
193
		if ( !$this->shellboxClientFactory->isEnabled() ) {
194
			return CheckResult::STATUS_TODO;
195
		}
196
		try {
197
			$pattern = '/^' . str_replace( '/', '\/', $format ) . '$/';
198
			$shellboxResponse = $this->shellboxClientFactory->getClient(
199
				[ 'timeout' => $this->config->get( 'WBQualityConstraintsSparqlMaxMillis' ) / 1000 ]
200
			)->call(
201
				'constraint-regex-checker',
202
				'preg_match',
203
				[ $pattern, $text ]
204
			);
205
		} catch ( ShellboxError $exception ) {
0 ignored issues
show
Bug introduced by
The class Shellbox\ShellboxError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
206
			throw new ConstraintParameterException(
207
				( new ViolationMessage( 'wbqc-violation-message-parameter-regex' ) )
208
					->withInlineCode( $pattern, Role::CONSTRAINT_PARAMETER_VALUE )
209
			);
210
		}
211
212
		if ( $shellboxResponse ) {
213
			return CheckResult::STATUS_COMPLIANCE;
214
		} else {
215
			return CheckResult::STATUS_VIOLATION;
216
		}
217
	}
218
219
	private function runRegexCheckUsingSparql( string $text, string $format ): string {
220
		if ( $this->sparqlHelper instanceof DummySparqlHelper ) {
221
			return CheckResult::STATUS_TODO;
222
		}
223
224
		if ( $this->sparqlHelper->matchesRegularExpression( $text, $format ) ) {
225
			return CheckResult::STATUS_COMPLIANCE;
226
		} else {
227
			return CheckResult::STATUS_VIOLATION;
228
		}
229
	}
230
231
	public function checkConstraintParameters( Constraint $constraint ) {
232
		$constraintParameters = $constraint->getConstraintParameters();
233
		$constraintTypeItemId = $constraint->getConstraintTypeItemId();
234
		$exceptions = [];
235
		try {
236
			$this->constraintParameterParser->parseFormatParameter(
237
				$constraintParameters,
238
				$constraintTypeItemId
239
			);
240
		} catch ( ConstraintParameterException $e ) {
241
			$exceptions[] = $e;
242
		}
243
		try {
244
			$this->constraintParameterParser->parseSyntaxClarificationParameter(
245
				$constraintParameters
246
			);
247
		} catch ( ConstraintParameterException $e ) {
248
			$exceptions[] = $e;
249
		}
250
		return $exceptions;
251
	}
252
253
}
254