Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.55% covered (success)
96.55%
28 / 29
94.44% covered (success)
94.44%
17 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
ViolationMessage
96.55% covered (success)
96.55%
28 / 29
94.44% covered (success)
94.44%
17 / 18
19
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getMessageKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getArguments
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withArgument
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withEntityId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withEntityIdList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withItemIdSnakValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withItemIdSnakValueList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withDataValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withDataValueType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withInlineCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withConstraintScope
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withConstraintScopeList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withPropertyScope
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withPropertyScopeList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withLanguage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withLanguages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 withMultilingualText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Message;
4
5use DataValues\DataValue;
6use DataValues\MultilingualTextValue;
7use InvalidArgumentException;
8use Wikibase\DataModel\Entity\EntityId;
9use WikibaseQuality\ConstraintReport\ConstraintCheck\ItemIdSnakValue;
10
11/**
12 * A violation message of a constraint check.
13 *
14 * A ViolationMessage object is immutable:
15 * operations like {@link withEntityId} return a modified copy.
16 *
17 * @license GPL-2.0-or-later
18 */
19class ViolationMessage {
20
21    /**
22     * @private
23     */
24    public const MESSAGE_KEY_PREFIX = 'wbqc-violation-message-';
25
26    /**
27     * Argument type for a single entity ID.
28     * Value type: {@link EntityId}
29     */
30    public const TYPE_ENTITY_ID = 'e';
31
32    /**
33     * Argument type for a list of entity IDs.
34     * Value type: {@link EntityId}[]
35     */
36    public const TYPE_ENTITY_ID_LIST = 'E';
37
38    /**
39     * Argument type for an item ID, “unknown value”, or “no value”.
40     * Value type: {@link ItemIdSnakValue}
41     */
42    public const TYPE_ITEM_ID_SNAK_VALUE = 'i';
43
44    /**
45     * Argument type for a list of item IDs, “unknown value”s, or “no value”s.
46     * Value type: {@link ItemIdSnakValue}[]
47     */
48    public const TYPE_ITEM_ID_SNAK_VALUE_LIST = 'I';
49
50    /**
51     * Argument type for a single data value.
52     * Value type: {@link DataValue}
53     */
54    public const TYPE_DATA_VALUE = 'v';
55
56    /**
57     * Argument type for a data value type, like "time" or "wikibase-entityid".
58     * (Not to be confused with a data type, like "time" or "wikibase-item".)
59     * Value type: string
60     */
61    public const TYPE_DATA_VALUE_TYPE = 't';
62
63    /**
64     * Argument type for a short fragment of inline computer code.
65     * Value type: string
66     */
67    public const TYPE_INLINE_CODE = 'c';
68
69    /**
70     * Argument type for a single constraint scope.
71     * Value type: string (one of the Context::TYPE_* constants)
72     */
73    public const TYPE_CONSTRAINT_SCOPE = 's';
74
75    /**
76     * Argument type for a list of constraint scopes.
77     * Value type: string[]
78     */
79    public const TYPE_CONSTRAINT_SCOPE_LIST = 'S';
80
81    /**
82     * Argument type for a single property scope.
83     * Value type: string (one of the Context::TYPE_* constants)
84     */
85    public const TYPE_PROPERTY_SCOPE = 'p';
86
87    /**
88     * Argument type for a list of property scopes.
89     * Value type: string[]
90     */
91    public const TYPE_PROPERTY_SCOPE_LIST = 'P';
92
93    /**
94     * Argument type for a language.
95     * Value type: string (language code)
96     */
97    public const TYPE_LANGUAGE = 'l';
98
99    /**
100     * Argument type for list of languages.
101     * Value type: string[] (language codes)
102     */
103    public const TYPE_LANGUAGE_LIST = 'L';
104
105    /**
106     * Argument type for a multilingual text value.
107     * Value type: {@link MultilingualTextValue}
108     */
109    public const TYPE_MULTILINGUAL_TEXT = 'm';
110
111    /**
112     * @var string
113     */
114    private $messageKeySuffix;
115
116    /**
117     * @var array[]
118     */
119    private $arguments;
120
121    /**
122     * @param string $messageKey The full message key. Must start with 'wbqc-violation-message-'.
123     * (We require callers to specify the full message key
124     * so that it’s easy to search for code that produces a given message.)
125     *
126     * @throws InvalidArgumentException If the message key is invalid.
127     */
128    public function __construct(
129        $messageKey
130    ) {
131        if ( strpos( $messageKey, self::MESSAGE_KEY_PREFIX ) !== 0 ) {
132            throw new InvalidArgumentException(
133                'ViolationMessage key ⧼' .
134                $messageKey .
135                '⧽ should start with "' .
136                self::MESSAGE_KEY_PREFIX .
137                '".'
138            );
139        }
140
141        $this->messageKeySuffix = substr( $messageKey, strlen( self::MESSAGE_KEY_PREFIX ) );
142        $this->arguments = [];
143    }
144
145    /**
146     * Get the full message key of this message.
147     * @return string
148     */
149    public function getMessageKey() {
150        return self::MESSAGE_KEY_PREFIX . $this->messageKeySuffix;
151    }
152
153    /**
154     * Get the arguments to this message,
155     * a list of [ 'type' => self::TYPE_*, 'role' => Role::*, 'value' => $value ] elements.
156     * @return array[]
157     */
158    public function getArguments() {
159        return $this->arguments;
160    }
161
162    /**
163     * Note: usually you don’t want to use this function directly.
164     *
165     * @param string $type one of the self::TYPE_* constants
166     * @param string|null $role one of the Role::* constants
167     * @param mixed $value the value, which should match the $type
168     * @return ViolationMessage
169     */
170    public function withArgument( $type, $role, $value ) {
171        $ret = clone $this;
172        $ret->arguments[] = [ 'type' => $type, 'role' => $role, 'value' => $value ];
173        return $ret;
174    }
175
176    /**
177     * Append a single entity ID to the message arguments.
178     * (This operation returns a modified copy, the original object is unchanged.)
179     *
180     * @param EntityId $entityId
181     * @param string|null $role one of the Role::* constants
182     * @return ViolationMessage
183     */
184    public function withEntityId( EntityId $entityId, $role = null ) {
185        return $this->withArgument( self::TYPE_ENTITY_ID, $role, $entityId );
186    }
187
188    /**
189     * Append a list of entity IDs to the message arguments.
190     * (This operation returns a modified copy, the original object is unchanged.)
191     *
192     * This is not the same as appending the list elements individually with {@link withEntityId}!
193     * In the final message, this corresponds to
194     * one parameter for the number of list elements,
195     * one parameter with an HTML list of the list elements,
196     * and then one parameter per entity ID.
197     *
198     * @param EntityId[] $entityIdList
199     * @param string|null $role one of the Role::* constants
200     * @return ViolationMessage
201     */
202    public function withEntityIdList( array $entityIdList, $role = null ) {
203        return $this->withArgument( self::TYPE_ENTITY_ID_LIST, $role, $entityIdList );
204    }
205
206    /**
207     * Append a single item ID, “unknown value”, or “no value” to the message arguments.
208     * (This operation returns a modified copy, the original object is unchanged.)
209     *
210     * @param ItemIdSnakValue $value
211     * @param string|null $role one of the Role::* constants
212     * @return ViolationMessage
213     */
214    public function withItemIdSnakValue( ItemIdSnakValue $value, $role = null ) {
215        return $this->withArgument( self::TYPE_ITEM_ID_SNAK_VALUE, $role, $value );
216    }
217
218    /**
219     * Append a list of item IDs, “unknown value”s, or “no value”s to the message arguments.
220     * (This operation returns a modified copy, the original object is unchanged.)
221     *
222     * This is not the same as appending the list elements individually with {@link withItemIdSnakValue}!
223     * In the final message, this corresponds to
224     * one parameter for the number of list elements,
225     * one parameter with an HTML list of the list elements,
226     * and then one parameter per value.
227     *
228     * @param ItemIdSnakValue[] $valueList
229     * @param string|null $role one of the Role::* constants
230     * @return ViolationMessage
231     */
232    public function withItemIdSnakValueList( array $valueList, $role = null ) {
233        return $this->withArgument( self::TYPE_ITEM_ID_SNAK_VALUE_LIST, $role, $valueList );
234    }
235
236    /**
237     * Append a single data value to the message arguments.
238     * (This operation returns a modified copy, the original object is unchanged.)
239     *
240     * @param DataValue $dataValue
241     * @param string|null $role one of the Role::* constants
242     * @return ViolationMessage
243     */
244    public function withDataValue( DataValue $dataValue, $role = null ) {
245        return $this->withArgument( self::TYPE_DATA_VALUE, $role, $dataValue );
246    }
247
248    /**
249     * Append a single data value type, like "time" or "wikibase-entityid".
250     * (This operation returns a modified copy, the original object is unchanged.)
251     *
252     * Data value types should not be confused with data types, like "time" or "wikibase-item".
253     * For example, "wikibase-entityid" is the data value type
254     * used by the data types "wikibase-item" and "wikibase-property".
255     *
256     * @param string $dataValueType
257     * @param string|null $role one of the Role::* constants
258     * @return ViolationMessage
259     */
260    public function withDataValueType( $dataValueType, $role = null ) {
261        return $this->withArgument( self::TYPE_DATA_VALUE_TYPE, $role, $dataValueType );
262    }
263
264    /**
265     * Append a single short fragment of inline computer code to the message arguments.
266     * (This operation returns a modified copy, the original object is unchanged.)
267     *
268     * @param string $code
269     * @param string|null $role one of the Role::* constants
270     * @return ViolationMessage
271     */
272    public function withInlineCode( $code, $role = null ) {
273        return $this->withArgument( self::TYPE_INLINE_CODE, $role, $code );
274    }
275
276    /**
277     * Append a single constraint scope to the message arguments.
278     * (This operation returns a modified copy, the original object is unchanged.)
279     *
280     * @param string $scope one of the Context::TYPE_* constants
281     * @param string|null $role one of the Role::* constants
282     * @return ViolationMessage
283     */
284    public function withConstraintScope( $scope, $role = null ) {
285        return $this->withArgument( self::TYPE_CONSTRAINT_SCOPE, $role, $scope );
286    }
287
288    /**
289     * Append a list of constraint scopes to the message arguments.
290     * (This operation returns a modified copy, the original object is unchanged.)
291     *
292     * @param string[] $scopeList Role::* constants
293     * @param string|null $role one of the Role::* constants
294     * @return ViolationMessage
295     */
296    public function withConstraintScopeList( array $scopeList, $role = null ) {
297        return $this->withArgument( self::TYPE_CONSTRAINT_SCOPE_LIST, $role, $scopeList );
298    }
299
300    /**
301     * Append a single property scope to the message arguments.
302     * (This operation returns a modified copy, the original object is unchanged.)
303     *
304     * @param string $scope one of the Context::TYPE_* constants
305     * @param string|null $role one of the Role::* constants
306     * @return ViolationMessage
307     */
308    public function withPropertyScope( $scope, $role = null ) {
309        return $this->withArgument( self::TYPE_PROPERTY_SCOPE, $role, $scope );
310    }
311
312    /**
313     * Append a list of property scopes to the message arguments.
314     * (This operation returns a modified copy, the original object is unchanged.)
315     *
316     * @param string[] $scopeList Role::* constants
317     * @param string|null $role one of the Role::* constants
318     * @return ViolationMessage
319     */
320    public function withPropertyScopeList( array $scopeList, $role = null ) {
321        return $this->withArgument( self::TYPE_PROPERTY_SCOPE_LIST, $role, $scopeList );
322    }
323
324    /**
325     * Append a single language to the message arguments.
326     * (This operation returns a modified copy, the original object is unchanged.)
327     *
328     * One language argument corresponds to two params in the final message,
329     * one for the language name (autonym) and one for the language code.
330     *
331     * (Language arguments do not support roles.)
332     *
333     * @param string $languageCode
334     * @return ViolationMessage
335     */
336    public function withLanguage( $languageCode ) {
337        return $this->withArgument( self::TYPE_LANGUAGE, null, $languageCode );
338    }
339
340    /**
341     * Append a single language to the message arguments.
342     * (This operation returns a modified copy, the original object is unchanged.)
343     *
344     * One language argument corresponds to two params in the final message,
345     * one for the language name (autonym) and one for the language code.
346     *
347     * (Language arguments do not support roles.)
348     *
349     * @param string[] $languageCodes
350     * @return ViolationMessage
351     */
352    public function withLanguages( $languageCodes ) {
353        return $this->withArgument( self::TYPE_LANGUAGE_LIST, null, $languageCodes );
354    }
355
356    /**
357     * Append a multilingual text value to the message arguments.
358     * (This operation returns a modified copy, the original object is unchanged.)
359     *
360     * Note that multilingual text arguments can only be rendered for specific message keys
361     * (see {@link MultilingualTextViolationMessageRenderer} for details),
362     * but this method does not verify whether you’re using one of those message keys.
363     *
364     * @param MultilingualTextValue $text
365     * @param string|null $role one of the Role::* constants
366     * @return ViolationMessage
367     */
368    public function withMultilingualText( MultilingualTextValue $text, $role = null ) {
369        return $this->withArgument( self::TYPE_MULTILINGUAL_TEXT, $role, $text );
370    }
371
372}