Completed
Push — master ( af1662...d5f822 )
by
unknown
04:16
created

CheckConstraints::validateParameters()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 4
nop 1
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\Api;
4
5
use ApiBase;
6
use ApiMain;
7
use IBufferingStatsdDataFactory;
8
use MediaWiki\Logger\LoggerFactory;
9
use MediaWiki\MediaWikiServices;
10
use RequestContext;
11
use ValueFormatters\FormatterOptions;
12
use Wikibase\DataModel\Entity\EntityId;
13
use Wikibase\DataModel\Entity\EntityIdParser;
14
use Wikibase\DataModel\Entity\EntityIdParsingException;
15
use Wikibase\DataModel\Services\Statement\StatementGuidValidator;
16
use Wikibase\Lib\SnakFormatter;
17
use Wikibase\Lib\Store\Sql\WikiPageEntityMetaDataLookup;
18
use Wikibase\Repo\Api\ApiErrorReporter;
19
use Wikibase\Repo\Api\ApiHelperFactory;
20
use Wikibase\Repo\Api\ResultBuilder;
21
use Wikibase\Repo\EntityIdLabelFormatterFactory;
22
use Wikibase\Repo\WikibaseRepo;
23
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\ContextCursorDeserializer;
24
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\ContextCursorSerializer;
25
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
26
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\LoggingHelper;
27
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\MultilingualTextViolationMessageRenderer;
28
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessageDeserializer;
29
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessageSerializer;
30
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
31
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResultDeserializer;
32
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResultSerializer;
33
use WikibaseQuality\ConstraintReport\ConstraintDeserializer;
34
use WikibaseQuality\ConstraintReport\ConstraintParameterRenderer;
35
use WikibaseQuality\ConstraintReport\ConstraintReportFactory;
36
use WikibaseQuality\ConstraintReport\ConstraintSerializer;
37
38
/**
39
 * API module that performs constraint check of entities, claims and constraint ID
40
 *
41
 * @author Olga Bode
42
 * @license GPL-2.0-or-later
43
 */
44
class CheckConstraints extends ApiBase {
45
46
	const PARAM_ID = 'id';
47
	const PARAM_CLAIM_ID = 'claimid';
48
	const PARAM_CONSTRAINT_ID = 'constraintid';
49
	const PARAM_STATUS = 'status';
50
51
	/**
52
	 * @var EntityIdParser
53
	 */
54
	private $entityIdParser;
55
56
	/**
57
	 * @var StatementGuidValidator
58
	 */
59
	private $statementGuidValidator;
60
61
	/**
62
	 * @var ResultBuilder
63
	 */
64
	private $resultBuilder;
65
66
	/**
67
	 * @var ApiErrorReporter
68
	 */
69
	private $errorReporter;
70
71
	/**
72
	 * @var ResultsSource
73
	 */
74
	private $resultsSource;
75
76
	/**
77
	 * @var CheckResultsRenderer
78
	 */
79
	private $checkResultsRenderer;
80
81
	/**
82
	 * @var IBufferingStatsdDataFactory
83
	 */
84
	private $dataFactory;
85
86
	/**
87
	 * Creates new instance from global state.
88
	 *
89
	 * @param ApiMain $main
90
	 * @param string $name
91
	 * @param string $prefix
92
	 *
93
	 * @return self
94
	 */
95
	public static function newFromGlobalState( ApiMain $main, $name, $prefix = '' ) {
96
		$repo = WikibaseRepo::getDefaultInstance();
97
98
		$language = $repo->getUserLanguage();
99
		$formatterOptions = new FormatterOptions();
100
		$formatterOptions->setOption( SnakFormatter::OPT_LANG, $language->getCode() );
101
		$valueFormatterFactory = $repo->getValueFormatterFactory();
102
		$valueFormatter = $valueFormatterFactory->getValueFormatter( SnakFormatter::FORMAT_HTML, $formatterOptions );
103
104
		$languageFallbackLabelDescriptionLookupFactory = $repo->getLanguageFallbackLabelDescriptionLookupFactory();
105
		$labelDescriptionLookup = $languageFallbackLabelDescriptionLookupFactory->newLabelDescriptionLookup( $language );
106
		$entityIdHtmlLinkFormatterFactory = $repo->getEntityIdHtmlLinkFormatterFactory();
107
		$entityIdHtmlLinkFormatter = $entityIdHtmlLinkFormatterFactory->getEntityIdFormatter( $labelDescriptionLookup );
108
		$entityIdLabelFormatterFactory = new EntityIdLabelFormatterFactory();
109
		$entityIdLabelFormatter = $entityIdLabelFormatterFactory->getEntityIdFormatter( $labelDescriptionLookup );
110
		$config = MediaWikiServices::getInstance()->getMainConfig();
111
		$titleParser = MediaWikiServices::getInstance()->getTitleParser();
112
		$unitConverter = $repo->getUnitConverter();
113
		$dataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
114
		$loggingHelper = new LoggingHelper(
115
			$dataFactory,
116
			LoggerFactory::getInstance( 'WikibaseQualityConstraints' ),
117
			$config
118
		);
119
		$constraintParameterRenderer = new ConstraintParameterRenderer(
120
			$entityIdHtmlLinkFormatter,
121
			$valueFormatter,
122
			$config
123
		);
124
		$constraintReportFactory = new ConstraintReportFactory(
125
			$repo->getEntityLookup(),
126
			$repo->getPropertyDataTypeLookup(),
127
			$repo->getStatementGuidParser(),
128
			$config,
129
			$constraintParameterRenderer,
130
			new ConstraintParameterParser(
131
				$config,
132
				$repo->getBaseDataModelDeserializerFactory(),
133
				$constraintParameterRenderer
134
			),
135
			new ViolationMessageSerializer(),
136
			new ViolationMessageDeserializer(
137
				$repo->getEntityIdParser(),
138
				$repo->getDataValueFactory()
139
			),
140
			$repo->getRdfVocabulary(),
141
			$repo->getEntityIdParser(),
142
			$titleParser,
143
			$unitConverter,
144
			$dataFactory
145
		);
146
147
		$checkResultsRenderer = new CheckResultsRenderer(
148
			$repo->getEntityTitleLookup(),
149
			$entityIdLabelFormatter,
150
			new MultilingualTextViolationMessageRenderer( $entityIdHtmlLinkFormatter, $valueFormatter, $config ),
151
			$config
152
		);
153
		$resultsSource = new CheckingResultsSource(
154
			$constraintReportFactory->getConstraintChecker()
155
		);
156
		if ( $config->get( 'WBQualityConstraintsCacheCheckConstraintsResults' ) ) {
157
			$wikiPageEntityMetaDataAccessor = new WikiPageEntityMetaDataLookup(
158
				$repo->getEntityNamespaceLookup()
159
			);
160
			$entityIdParser = $repo->getEntityIdParser();
161
			$checkResultSerializer = new CheckResultSerializer(
162
				new ConstraintSerializer(
163
					false // this API doesn’t expose the constraint parameters
164
				),
165
				new ContextCursorSerializer(),
166
				new ViolationMessageSerializer(),
167
				false // unnecessary to serialize individual result dependencies
168
			);
169
			$checkResultDeserializer = new CheckResultDeserializer(
170
				new ConstraintDeserializer(),
171
				new ContextCursorDeserializer(),
172
				new ViolationMessageDeserializer(
173
					$entityIdParser,
174
					$repo->getDataValueFactory()
175
				),
176
				$entityIdParser
177
			);
178
			$resultsSource = new CachingResultsSource(
179
				$resultsSource,
180
				ResultsCache::getDefaultInstance(),
181
				$checkResultSerializer,
182
				$checkResultDeserializer,
183
				$wikiPageEntityMetaDataAccessor,
184
				$entityIdParser,
185
				$config->get( 'WBQualityConstraintsCacheCheckConstraintsTTLSeconds' ),
186
				[
187
					$config->get( 'WBQualityConstraintsCommonsLinkConstraintId' ),
188
					$config->get( 'WBQualityConstraintsTypeConstraintId' ),
189
					$config->get( 'WBQualityConstraintsValueTypeConstraintId' ),
190
					$config->get( 'WBQualityConstraintsDistinctValuesConstraintId' ),
191
				],
192
				$config->get( 'WBQualityConstraintsCacheCheckConstraintsMaximumRevisionIds' ),
193
				$loggingHelper
194
			);
195
		}
196
197
		return new CheckConstraints(
198
			$main,
199
			$name,
200
			$prefix,
201
			$repo->getEntityIdParser(),
202
			$repo->getStatementGuidValidator(),
203
			$repo->getApiHelperFactory( RequestContext::getMain() ),
204
			$resultsSource,
205
			$checkResultsRenderer,
206
			$dataFactory
207
		);
208
	}
209
210
	/**
211
	 * @param ApiMain $main
212
	 * @param string $name
213
	 * @param string $prefix
214
	 * @param EntityIdParser $entityIdParser
215
	 * @param StatementGuidValidator $statementGuidValidator
216
	 * @param ApiHelperFactory $apiHelperFactory
217
	 * @param ResultsSource $resultsSource
218
	 * @param CheckResultsRenderer $checkResultsRenderer
219
	 * @param IBufferingStatsdDataFactory $dataFactory
220
	 */
221
	public function __construct(
222
		ApiMain $main,
223
		$name,
224
		$prefix,
225
		EntityIdParser $entityIdParser,
226
		StatementGuidValidator $statementGuidValidator,
227
		ApiHelperFactory $apiHelperFactory,
228
		ResultsSource $resultsSource,
229
		CheckResultsRenderer $checkResultsRenderer,
230
		IBufferingStatsdDataFactory $dataFactory
231
	) {
232
		parent::__construct( $main, $name, $prefix );
233
		$this->entityIdParser = $entityIdParser;
234
		$this->statementGuidValidator = $statementGuidValidator;
235
		$this->resultBuilder = $apiHelperFactory->getResultBuilder( $this );
236
		$this->errorReporter = $apiHelperFactory->getErrorReporter( $this );
237
		$this->resultsSource = $resultsSource;
238
		$this->checkResultsRenderer = $checkResultsRenderer;
239
		$this->dataFactory = $dataFactory;
240
	}
241
242
	/**
243
	 * Evaluates the parameters, runs the requested constraint check, and sets up the result
244
	 */
245
	public function execute() {
246
		$this->dataFactory->increment(
247
			'wikibase.quality.constraints.api.checkConstraints.execute'
248
		);
249
250
		$params = $this->extractRequestParams();
251
252
		$this->validateParameters( $params );
253
		$entityIds = $this->parseEntityIds( $params );
254
		$claimIds = $this->parseClaimIds( $params );
255
		$constraintIDs = $params[self::PARAM_CONSTRAINT_ID];
256
		$statuses = $params[self::PARAM_STATUS];
257
258
		$this->getResult()->addValue(
259
			null,
260
			$this->getModuleName(),
261
			$this->checkResultsRenderer->render(
262
				$this->resultsSource->getResults(
263
					$entityIds,
264
					$claimIds,
265
					$constraintIDs,
266
					$statuses
267
				)
268
			)->getArray()
269
		);
270
		$this->resultBuilder->markSuccess( 1 );
271
	}
272
273
	/**
274
	 * @param array $params
275
	 *
276
	 * @return EntityId[]
277
	 */
278
	private function parseEntityIds( array $params ) {
279
		$ids = $params[self::PARAM_ID];
280
281
		if ( $ids === null ) {
282
			return [];
283
		} elseif ( $ids === [] ) {
284
			$this->errorReporter->dieError(
285
				'If ' . self::PARAM_ID . ' is specified, it must be nonempty.', 'no-data' );
286
		}
287
288
		return array_map( function ( $id ) {
289
			try {
290
				return $this->entityIdParser->parse( $id );
291
			} catch ( EntityIdParsingException $e ) {
292
				$this->errorReporter->dieError(
293
					"Invalid id: $id", 'invalid-entity-id', 0, [ self::PARAM_ID => $id ] );
294
			}
295
		}, $ids );
296
	}
297
298
	/**
299
	 * @param array $params
300
	 *
301
	 * @return string[]
302
	 */
303
	private function parseClaimIds( array $params ) {
304
		$ids = $params[self::PARAM_CLAIM_ID];
305
306
		if ( $ids === null ) {
307
			return [];
308
		} elseif ( $ids === [] ) {
309
			$this->errorReporter->dieError(
310
				'If ' . self::PARAM_CLAIM_ID . ' is specified, it must be nonempty.', 'no-data' );
311
		}
312
313
		foreach ( $ids as $id ) {
314
			if ( !$this->statementGuidValidator->validate( $id ) ) {
315
				$this->errorReporter->dieError(
316
					"Invalid claim id: $id", 'invalid-guid', 0, [ self::PARAM_CLAIM_ID => $id ] );
317
			}
318
		}
319
320
		return $ids;
321
	}
322
323
	private function validateParameters( array $params ) {
324
		if ( $params[self::PARAM_CONSTRAINT_ID] !== null
325
			 && empty( $params[self::PARAM_CONSTRAINT_ID] )
326
		) {
327
			$paramConstraintId = self::PARAM_CONSTRAINT_ID;
328
			$this->errorReporter->dieError(
329
				"If $paramConstraintId is specified, it must be nonempty.", 'no-data' );
330
		}
331
		if ( $params[self::PARAM_ID] === null && $params[self::PARAM_CLAIM_ID] === null ) {
332
			$paramId = self::PARAM_ID;
333
			$paramClaimId = self::PARAM_CLAIM_ID;
334
			$this->errorReporter->dieError(
335
				"At least one of $paramId, $paramClaimId must be specified.", 'no-data' );
336
		}
337
		// contents of PARAM_ID and PARAM_CLAIM_ID are validated by parse{Entity,Claim}Ids()
338
	}
339
340
	/**
341
	 * @return array[]
342
	 * @codeCoverageIgnore
343
	 */
344
	public function getAllowedParams() {
345
		return [
346
			self::PARAM_ID => [
347
				ApiBase::PARAM_TYPE => 'string',
348
				ApiBase::PARAM_ISMULTI => true,
349
			],
350
			self::PARAM_CLAIM_ID => [
351
				ApiBase::PARAM_TYPE => 'string',
352
				ApiBase::PARAM_ISMULTI => true,
353
			],
354
			self::PARAM_CONSTRAINT_ID => [
355
				ApiBase::PARAM_TYPE => 'string',
356
				ApiBase::PARAM_ISMULTI => true,
357
			],
358
			self::PARAM_STATUS => [
359
				ApiBase::PARAM_TYPE => [
360
					CheckResult::STATUS_COMPLIANCE,
361
					CheckResult::STATUS_VIOLATION,
362
					CheckResult::STATUS_WARNING,
363
					CheckResult::STATUS_EXCEPTION,
364
					CheckResult::STATUS_NOT_IN_SCOPE,
365
					CheckResult::STATUS_DEPRECATED,
366
					CheckResult::STATUS_BAD_PARAMETERS,
367
					CheckResult::STATUS_TODO,
368
				],
369
				ApiBase::PARAM_ISMULTI => true,
370
				ApiBase::PARAM_ALL => true,
371
				ApiBase::PARAM_DFLT => implode( '|', [
372
					CheckResult::STATUS_VIOLATION,
373
					CheckResult::STATUS_WARNING,
374
					CheckResult::STATUS_BAD_PARAMETERS,
375
				] ),
376
				ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
377
			],
378
		];
379
	}
380
381
	/**
382
	 * Returns usage examples for this module
383
	 *
384
	 * @return string[]
385
	 * @codeCoverageIgnore
386
	 */
387
	public function getExamplesMessages() {
388
		return [
389
			'action=wbcheckconstraints&id=Q5|Q42'
390
				=> 'apihelp-wbcheckconstraints-example-1',
391
			'action=wbcheckconstraints&claimid=q42%248419C20C-8EF8-4EC0-80D6-AF1CA55E7557'
392
				=> 'apihelp-wbcheckconstraints-example-2',
393
			'action=wbcheckconstraints&format=json&id=Q2&constraintid=P1082%24DA39C2DA-47DA-48FB-8A9A-DA80200FB2DB'
394
				=> 'apihelp-wbcheckconstraints-example-3',
395
		];
396
	}
397
398
}
399