Completed
Push — master ( db833a...340657 )
by
unknown
04:20
created

ViolationMessageRenderer::renderConstraintScope()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26

Duplication

Lines 26
Ratio 100 %

Importance

Changes 0
Metric Value
dl 26
loc 26
rs 9.504
c 0
b 0
f 0
cc 4
nc 4
nop 2
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Message;
4
5
use Config;
6
use DataValues\DataValue;
7
use InvalidArgumentException;
8
use Language;
9
use LogicException;
10
use Message;
11
use MessageLocalizer;
12
use ValueFormatters\ValueFormatter;
13
use Wikibase\DataModel\Entity\EntityId;
14
use Wikibase\DataModel\Entity\ItemId;
15
use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
16
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
17
use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue;
18
19
/**
20
 * Render a {@link ViolationMessage} into a localized string.
21
 *
22
 * Note: This class does <em>not</em> support multilingual text arguments –
23
 * for that, use {@link MultilingualTextViolationMessageRenderer}.
24
 *
25
 * @license GPL-2.0-or-later
26
 */
27
class ViolationMessageRenderer {
28
29
	/**
30
	 * @var EntityIdFormatter
31
	 */
32
	private $entityIdFormatter;
33
34
	/**
35
	 * @var ValueFormatter
36
	 */
37
	private $dataValueFormatter;
38
39
	/**
40
	 * @var MessageLocalizer
41
	 */
42
	protected $messageLocalizer;
43
44
	/**
45
	 * @var Config
46
	 */
47
	private $config;
48
49
	/**
50
	 * @var int
51
	 */
52
	private $maxListLength;
53
54
	/**
55
	 * @param EntityIdFormatter $entityIdFormatter
56
	 * @param ValueFormatter $dataValueFormatter
57
	 * @param MessageLocalizer $messageLocalizer
58
	 * @param Config $config
59
	 * @param int $maxListLength The maximum number of elements to be rendered in a list parameter.
60
	 * Longer lists are truncated to this length and then rendered with an ellipsis in the HMTL list.
61
	 */
62
	public function __construct(
63
		EntityIdFormatter $entityIdFormatter,
64
		ValueFormatter $dataValueFormatter,
65
		MessageLocalizer $messageLocalizer,
66
		Config $config,
67
		$maxListLength = 10
68
	) {
69
		$this->entityIdFormatter = $entityIdFormatter;
70
		$this->dataValueFormatter = $dataValueFormatter;
71
		$this->messageLocalizer = $messageLocalizer;
72
		$this->config = $config;
73
		$this->maxListLength = $maxListLength;
74
	}
75
76
	/**
77
	 * @param ViolationMessage $violationMessage
78
	 * (temporarily, pre-rendered strings are allowed and returned without changes)
79
	 * @return string
80
	 */
81
	public function render( ViolationMessage $violationMessage ) {
82
		$messageKey = $violationMessage->getMessageKey();
83
		$paramsLists = [ [] ];
84
		foreach ( $violationMessage->getArguments() as $argument ) {
85
			$params = $this->renderArgument( $argument );
86
			$paramsLists[] = $params;
87
		}
88
		$allParams = call_user_func_array( 'array_merge', $paramsLists );
89
		return $this->messageLocalizer
90
			->msg( $messageKey )
91
			->params( $allParams )
92
			->escaped();
93
	}
94
95
	/**
96
	 * @param string $value HTML
97
	 * @param string|null $role one of the Role::* constants
98
	 * @return string HTML
99
	 */
100
	protected function addRole( $value, $role ) {
101
		if ( $role === null ) {
102
			return $value;
103
		}
104
105
		return '<span class="wbqc-role wbqc-role-' . htmlspecialchars( $role ) . '">' .
106
			$value .
107
			'</span>';
108
	}
109
110
	/**
111
	 * @param string $key message key
112
	 * @return string HTML
113
	 */
114
	protected function msgEscaped( $key ) {
115
		return $this->messageLocalizer->msg( $key )->escaped();
116
	}
117
118
	/**
119
	 * @param array $argument
120
	 * @return array[] params (for Message::params)
121
	 */
122
	protected function renderArgument( array $argument ) {
123
		$methods = [
124
			ViolationMessage::TYPE_ENTITY_ID => 'renderEntityId',
125
			ViolationMessage::TYPE_ENTITY_ID_LIST => 'renderEntityIdList',
126
			ViolationMessage::TYPE_ITEM_ID_SNAK_VALUE => 'renderItemIdSnakValue',
127
			ViolationMessage::TYPE_ITEM_ID_SNAK_VALUE_LIST => 'renderItemIdSnakValueList',
128
			ViolationMessage::TYPE_DATA_VALUE => 'renderDataValue',
129
			ViolationMessage::TYPE_DATA_VALUE_TYPE => 'renderDataValueType',
130
			ViolationMessage::TYPE_INLINE_CODE => 'renderInlineCode',
131
			ViolationMessage::TYPE_CONSTRAINT_SCOPE => 'renderConstraintScope',
132
			ViolationMessage::TYPE_CONSTRAINT_SCOPE_LIST => 'renderConstraintScopeList',
133
			ViolationMessage::TYPE_PROPERTY_SCOPE => 'renderPropertyScope',
134
			ViolationMessage::TYPE_PROPERTY_SCOPE_LIST => 'renderPropertyScopeList',
135
			ViolationMessage::TYPE_LANGUAGE => 'renderLanguage',
136
		];
137
138
		$type = $argument['type'];
139
		$value = $argument['value'];
140
		$role = $argument['role'];
141
142 View Code Duplication
		if ( array_key_exists( $type, $methods ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
143
			$method = $methods[$type];
144
			$params = $this->$method( $value, $role );
145
		} else {
146
			throw new InvalidArgumentException(
147
				'Unknown ViolationMessage argument type ' . $type . '!'
148
			);
149
		}
150
151
		return $params;
152
	}
153
154
	/**
155
	 * @param array $list
156
	 * @param string|null $role one of the Role::* constants
157
	 * @param callable $render must accept $list elements and $role as parameters
158
	 * and return a single-element array with a raw message param (i. e. [ Message::rawParam( … ) ])
159
	 * @return array[] list of parameters as accepted by Message::params()
160
	 */
161
	private function renderList( array $list, $role, callable $render ) {
162
		if ( $list === [] ) {
163
			return [
164
				Message::numParam( 0 ),
165
				Message::rawParam( '<ul></ul>' ),
166
			];
167
		}
168
169
		if ( count( $list ) > $this->maxListLength ) {
170
			$list = array_slice( $list, 0, $this->maxListLength );
171
			$truncated = true;
172
		}
173
174
		$renderedParamsLists = array_map(
175
			$render,
176
			$list,
177
			array_fill( 0, count( $list ), $role )
178
		);
179
		$renderedParams = array_map(
180
			function ( $params ) {
181
				return $params[0];
182
			},
183
			$renderedParamsLists
184
		);
185
		$renderedElements = array_map(
186
			function ( $param ) {
187
				return $param['raw'];
188
			},
189
			$renderedParams
190
		);
191
		if ( isset( $truncated ) ) {
192
			$renderedElements[] = $this->msgEscaped( 'ellipsis' );
193
		}
194
195
		return array_merge(
196
			[
197
				Message::numParam( count( $list ) ),
198
				Message::rawParam(
199
					'<ul><li>' .
200
					implode( '</li><li>', $renderedElements ) .
201
					'</li></ul>'
202
				),
203
			],
204
			$renderedParams
205
		);
206
	}
207
208
	/**
209
	 * @param EntityId $entityId
210
	 * @param string|null $role one of the Role::* constants
211
	 * @return array[] list of a single raw message param (i. e. [ Message::rawParam( … ) ])
212
	 */
213
	private function renderEntityId( EntityId $entityId, $role ) {
214
		return [ Message::rawParam( $this->addRole(
215
			$this->entityIdFormatter->formatEntityId( $entityId ),
216
			$role
217
		) ) ];
218
	}
219
220
	/**
221
	 * @param EntityId[] $entityIdList
222
	 * @param string|null $role one of the Role::* constants
223
	 * @return array[] list of parameters as accepted by Message::params()
224
	 */
225
	private function renderEntityIdList( array $entityIdList, $role ) {
226
		return $this->renderList( $entityIdList, $role, [ $this, 'renderEntityId' ] );
227
	}
228
229
	/**
230
	 * @param ItemIdSnakValue $value
231
	 * @param string|null $role one of the Role::* constants
232
	 * @return array[] list of a single raw message param (i. e. [ Message::rawParam( … ) ])
233
	 */
234
	private function renderItemIdSnakValue( ItemIdSnakValue $value, $role ) {
235
		switch ( true ) {
236
			case $value->isValue():
237
				return $this->renderEntityId( $value->getItemId(), $role );
0 ignored issues
show
Bug introduced by
It seems like $value->getItemId() can be null; however, renderEntityId() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
238 View Code Duplication
			case $value->isSomeValue():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
239
				return [ Message::rawParam( $this->addRole(
240
					'<span class="wikibase-snakview-variation-somevaluesnak">' .
241
						$this->msgEscaped( 'wikibase-snakview-snaktypeselector-somevalue' ) .
242
						'</span>',
243
					$role
244
				) ) ];
245 View Code Duplication
			case $value->isNoValue():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
246
				return [ Message::rawParam( $this->addRole(
247
					'<span class="wikibase-snakview-variation-novaluesnak">' .
248
					$this->msgEscaped( 'wikibase-snakview-snaktypeselector-novalue' ) .
249
						'</span>',
250
					$role
251
				) ) ];
252
			default:
253
				// @codeCoverageIgnoreStart
254
				throw new LogicException(
255
					'ItemIdSnakValue should guarantee that one of is{,Some,No}Value() is true'
256
				);
257
				// @codeCoverageIgnoreEnd
258
		}
259
	}
260
261
	/**
262
	 * @param ItemIdSnakValue[] $valueList
263
	 * @param string|null $role one of the Role::* constants
264
	 * @return array[] list of parameters as accepted by Message::params()
265
	 */
266
	private function renderItemIdSnakValueList( array $valueList, $role ) {
267
		return $this->renderList( $valueList, $role, [ $this, 'renderItemIdSnakValue' ] );
268
	}
269
270
	/**
271
	 * @param DataValue $dataValue
272
	 * @param string|null $role one of the Role::* constants
273
	 * @return array[] list of parameters as accepted by Message::params()
274
	 */
275
	private function renderDataValue( DataValue $dataValue, $role ) {
276
		return [ Message::rawParam( $this->addRole(
277
			$this->dataValueFormatter->format( $dataValue ),
278
			$role
279
		) ) ];
280
	}
281
282
	/**
283
	 * @param string $dataValueType
284
	 * @param string|null $role one of the Role::* constants
285
	 * @return array[] list of parameters as accepted by Message::params()
286
	 */
287
	private function renderDataValueType( $dataValueType, $role ) {
288
		$messageKeys = [
289
			'string' => 'datatypes-type-string',
290
			'monolingualtext' => 'datatypes-type-monolingualtext',
291
			'time' => 'datatypes-type-time',
292
			'quantity' => 'datatypes-type-quantity',
293
			'wikibase-entityid' => 'wbqc-dataValueType-wikibase-entityid',
294
		];
295
296
		if ( array_key_exists( $dataValueType, $messageKeys ) ) {
297
			return [ Message::rawParam( $this->addRole(
298
				$this->msgEscaped( $messageKeys[$dataValueType] ),
299
				$role
300
			) ) ];
301
		} else {
302
			// @codeCoverageIgnoreStart
303
			throw new LogicException(
304
				'Unknown data value type ' . $dataValueType
305
			);
306
			// @codeCoverageIgnoreEnd
307
		}
308
	}
309
310
	/**
311
	 * @param string $code (not yet HTML-escaped)
312
	 * @param string|null $role one of the Role::* constants
313
	 * @return array[] list of parameters as accepted by Message::params()
314
	 */
315
	private function renderInlineCode( $code, $role ) {
316
		return [ Message::rawParam( $this->addRole(
317
			'<code>' . htmlspecialchars( $code ) . '</code>',
318
			$role
319
		) ) ];
320
	}
321
322
	/**
323
	 * @param string $scope one of the Context::TYPE_* constants
324
	 * @param string|null $role one of the Role::* constants
325
	 * @return array[] list of a single raw message param (i. e. [ Message::rawParam( … ) ])
326
	 */
327 View Code Duplication
	private function renderConstraintScope( $scope, $role ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
328
		switch ( $scope ) {
329
			case Context::TYPE_STATEMENT:
330
				$itemId = $this->config->get(
331
					'WBQualityConstraintsConstraintCheckedOnMainValueId'
332
				);
333
				break;
334
			case Context::TYPE_QUALIFIER:
335
				$itemId = $this->config->get(
336
					'WBQualityConstraintsConstraintCheckedOnQualifiersId'
337
				);
338
				break;
339
			case Context::TYPE_REFERENCE:
340
				$itemId = $this->config->get(
341
					'WBQualityConstraintsConstraintCheckedOnReferencesId'
342
				);
343
				break;
344
			default:
345
				// callers should never let this happen, but if it does happen,
346
				// showing “unknown value” seems reasonable
347
				// @codeCoverageIgnoreStart
348
				return $this->renderItemIdSnakValue( ItemIdSnakValue::someValue(), $role );
349
				// @codeCoverageIgnoreEnd
350
		}
351
		return $this->renderEntityId( new ItemId( $itemId ), $role );
352
	}
353
354
	/**
355
	 * @param string[] $text Context::TYPE_* constants
0 ignored issues
show
Bug introduced by
There is no parameter named $text. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
356
	 * @param string|null $role one of the Role::* constants
357
	 * @return array[] list of parameters as accepted by Message::params()
358
	 */
359
	private function renderConstraintScopeList( array $scopeList, $role ) {
360
		return $this->renderList( $scopeList, $role, [ $this, 'renderConstraintScope' ] );
361
	}
362
363
	/**
364
	 * @param string $scope one of the Context::TYPE_* constants
365
	 * @param string|null $role one of the Role::* constants
366
	 * @return array[] list of a single raw message param (i. e. [ Message::rawParam( … ) ])
367
	 */
368 View Code Duplication
	private function renderPropertyScope( $scope, $role ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
369
		switch ( $scope ) {
370
			case Context::TYPE_STATEMENT:
371
				$itemId = $this->config->get( 'WBQualityConstraintsAsMainValueId' );
372
				break;
373
			case Context::TYPE_QUALIFIER:
374
				$itemId = $this->config->get( 'WBQualityConstraintsAsQualifiersId' );
375
				break;
376
			case Context::TYPE_REFERENCE:
377
				$itemId = $this->config->get( 'WBQualityConstraintsAsReferencesId' );
378
				break;
379
			default:
380
				// callers should never let this happen, but if it does happen,
381
				// showing “unknown value” seems reasonable
382
				// @codeCoverageIgnoreStart
383
				return $this->renderItemIdSnakValue( ItemIdSnakValue::someValue(), $role );
384
				// @codeCoverageIgnoreEnd
385
		}
386
		return $this->renderEntityId( new ItemId( $itemId ), $role );
387
	}
388
389
	/**
390
	 * @param string[] $text Context::TYPE_* constants
0 ignored issues
show
Bug introduced by
There is no parameter named $text. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
391
	 * @param string|null $role one of the Role::* constants
392
	 * @return array[] list of parameters as accepted by Message::params()
393
	 */
394
	private function renderPropertyScopeList( array $scopeList, $role ) {
395
		return $this->renderList( $scopeList, $role, [ $this, 'renderPropertyScope' ] );
396
	}
397
398
	/**
399
	 * @param string $languageCode MediaWiki language code
400
	 * @param string|null $role one of the Role::* constants
401
	 * @return array[] list of parameters as accepted by Message::params()
402
	 */
403
	private function renderLanguage( $languageCode, $role ) {
0 ignored issues
show
Unused Code introduced by
The parameter $role is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
404
		return [
405
			Message::plaintextParam( Language::fetchLanguageName( $languageCode ) ),
406
			Message::plaintextParam( $languageCode ),
407
		];
408
	}
409
410
}
411