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 ) ) { |
|
|
|
|
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 ); |
|
|
|
|
238
|
|
View Code Duplication |
case $value->isSomeValue(): |
|
|
|
|
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(): |
|
|
|
|
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 ) { |
|
|
|
|
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 |
|
|
|
|
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 ) { |
|
|
|
|
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 |
|
|
|
|
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 ) { |
|
|
|
|
404
|
|
|
return [ |
405
|
|
|
Message::plaintextParam( Language::fetchLanguageName( $languageCode ) ), |
406
|
|
|
Message::plaintextParam( $languageCode ), |
407
|
|
|
]; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
} |
411
|
|
|
|
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.