Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
KeywordsManager
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
11 / 11
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisabledVariables
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDeprecatedVariables
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getBuilderValues
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isVarDisabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isVarDeprecated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isVarInUse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 varExists
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getMessageKeyForVar
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getVarsMappings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCoreVariables
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter;
4
5use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner;
6
7/**
8 * This service can be used to manage the list of keywords recognized by the Parser
9 */
10class KeywordsManager {
11    public const SERVICE_NAME = 'AbuseFilterKeywordsManager';
12
13    /**
14     * Operators and functions that can be used in AbuseFilter code.
15     * They are shown in the dropdown in the filter editor.
16     * Keys of translatable messages with their descriptions are
17     * based on keys of this array.
18     * When editing this list or the messages, keep the order
19     * consistent in both lists.
20     *
21     * @var array
22     */
23    private const BUILDER_VALUES = [
24        'op-arithmetic' => [
25            // Generates abusefilter-edit-builder-op-arithmetic-addition
26            '+' => 'addition',
27            // Generates abusefilter-edit-builder-op-arithmetic-subtraction
28            '-' => 'subtraction',
29            // Generates abusefilter-edit-builder-op-arithmetic-multiplication
30            '*' => 'multiplication',
31            // Generates abusefilter-edit-builder-op-arithmetic-divide
32            '/' => 'divide',
33            // Generates abusefilter-edit-builder-op-arithmetic-modulo
34            '%' => 'modulo',
35            // Generates abusefilter-edit-builder-op-arithmetic-pow
36            '**' => 'pow'
37        ],
38        'op-comparison' => [
39            // Generates abusefilter-edit-builder-op-comparison-equal
40            '==' => 'equal',
41            // Generates abusefilter-edit-builder-op-comparison-equal-strict
42            '===' => 'equal-strict',
43            // Generates abusefilter-edit-builder-op-comparison-notequal
44            '!=' => 'notequal',
45            // Generates abusefilter-edit-builder-op-comparison-notequal-strict
46            '!==' => 'notequal-strict',
47            // Generates abusefilter-edit-builder-op-comparison-lt
48            '<' => 'lt',
49            // Generates abusefilter-edit-builder-op-comparison-gt
50            '>' => 'gt',
51            // Generates abusefilter-edit-builder-op-comparison-lte
52            '<=' => 'lte',
53            // Generates abusefilter-edit-builder-op-comparison-gte
54            '>=' => 'gte'
55        ],
56        'op-bool' => [
57            // Generates abusefilter-edit-builder-op-bool-not
58            '!' => 'not',
59            // Generates abusefilter-edit-builder-op-bool-and
60            '&' => 'and',
61            // Generates abusefilter-edit-builder-op-bool-or
62            '|' => 'or',
63            // Generates abusefilter-edit-builder-op-bool-xor
64            '^' => 'xor'
65        ],
66        'misc' => [
67            // Generates abusefilter-edit-builder-misc-in
68            'in' => 'in',
69            // Generates abusefilter-edit-builder-misc-contains
70            'contains' => 'contains',
71            // Generates abusefilter-edit-builder-misc-like
72            'like' => 'like',
73            // Generates abusefilter-edit-builder-misc-stringlit
74            '""' => 'stringlit',
75            // Generates abusefilter-edit-builder-misc-rlike
76            'rlike' => 'rlike',
77            // Generates abusefilter-edit-builder-misc-irlike
78            'irlike' => 'irlike',
79            // Generates abusefilter-edit-builder-misc-tern
80            'cond ? iftrue : iffalse' => 'tern',
81            // Generates abusefilter-edit-builder-misc-cond
82            'if cond then iftrue else iffalse end' => 'cond',
83            // Generates abusefilter-edit-builder-misc-cond-short
84            'if cond then iftrue end' => 'cond-short',
85        ],
86        'funcs' => [
87            // Generates abusefilter-edit-builder-funcs-length
88            'length(string)' => 'length',
89            // Generates abusefilter-edit-builder-funcs-lcase
90            'lcase(string)' => 'lcase',
91            // Generates abusefilter-edit-builder-funcs-ucase
92            'ucase(string)' => 'ucase',
93            // Generates abusefilter-edit-builder-funcs-ccnorm
94            'ccnorm(string)' => 'ccnorm',
95            // Generates abusefilter-edit-builder-funcs-ccnorm-contains-any
96            'ccnorm_contains_any(haystack,needle1,needle2,..)' => 'ccnorm-contains-any',
97            // Generates abusefilter-edit-builder-funcs-ccnorm-contains-all
98            'ccnorm_contains_all(haystack,needle1,needle2,..)' => 'ccnorm-contains-all',
99            // Generates abusefilter-edit-builder-funcs-rmdoubles
100            'rmdoubles(string)' => 'rmdoubles',
101            // Generates abusefilter-edit-builder-funcs-specialratio
102            'specialratio(string)' => 'specialratio',
103            // Generates abusefilter-edit-builder-funcs-norm
104            'norm(string)' => 'norm',
105            // Generates abusefilter-edit-builder-funcs-count
106            'count(needle,haystack)' => 'count',
107            // Generates abusefilter-edit-builder-funcs-rcount
108            'rcount(needle,haystack)' => 'rcount',
109            // Generates abusefilter-edit-builder-funcs-get_matches
110            'get_matches(needle,haystack)' => 'get_matches',
111            // Generates abusefilter-edit-builder-funcs-rmwhitespace
112            'rmwhitespace(text)' => 'rmwhitespace',
113            // Generates abusefilter-edit-builder-funcs-rmspecials
114            'rmspecials(text)' => 'rmspecials',
115            // Generates abusefilter-edit-builder-funcs-ip_in_range
116            'ip_in_range(ip, range)' => 'ip_in_range',
117            // Generates abusefilter-edit-builder-funcs-ip_in_ranges
118            'ip_in_ranges(ip, range1, range2, ...)' => 'ip_in_ranges',
119            // Generates abusefilter-edit-builder-funcs-contains-any
120            'contains_any(haystack,needle1,needle2,...)' => 'contains-any',
121            // Generates abusefilter-edit-builder-funcs-contains-all
122            'contains_all(haystack,needle1,needle2,...)' => 'contains-all',
123            // Generates abusefilter-edit-builder-funcs-equals-to-any
124            'equals_to_any(haystack,needle1,needle2,...)' => 'equals-to-any',
125            // Generates abusefilter-edit-builder-funcs-substr
126            'substr(subject, offset, length)' => 'substr',
127            // Generates abusefilter-edit-builder-funcs-strpos
128            'strpos(haystack, needle)' => 'strpos',
129            // Generates abusefilter-edit-builder-funcs-str_replace
130            'str_replace(subject, search, replace)' => 'str_replace',
131            // Generates abusefilter-edit-builder-funcs-str_replace_regexp
132            'str_replace_regexp(subject, search, replace)' => 'str_replace_regexp',
133            // Generates abusefilter-edit-builder-funcs-rescape
134            'rescape(string)' => 'rescape',
135            // Generates abusefilter-edit-builder-funcs-set_var
136            'set_var(var,value)' => 'set_var',
137            // Generates abusefilter-edit-builder-funcs-sanitize
138            'sanitize(string)' => 'sanitize',
139        ],
140        'vars' => [
141            // Generates abusefilter-edit-builder-vars-timestamp
142            'timestamp' => 'timestamp',
143            // Generates abusefilter-edit-builder-vars-account-name
144            'account_name' => 'account-name',
145            // Generates abusefilter-edit-builder-vars-account-type
146            'account_type' => 'account-type',
147            // Generates abusefilter-edit-builder-vars-action
148            'action' => 'action',
149            // Generates abusefilter-edit-builder-vars-addedlines
150            'added_lines' => 'addedlines',
151            // Generates abusefilter-edit-builder-vars-delta
152            'edit_delta' => 'delta',
153            // Generates abusefilter-edit-builder-vars-diff
154            'edit_diff' => 'diff',
155            // Generates abusefilter-edit-builder-vars-newsize
156            'new_size' => 'newsize',
157            // Generates abusefilter-edit-builder-vars-oldsize
158            'old_size' => 'oldsize',
159            // Generates abusefilter-edit-builder-vars-new-content-model
160            'new_content_model' => 'new-content-model',
161            // Generates abusefilter-edit-builder-vars-old-content-model
162            'old_content_model' => 'old-content-model',
163            // Generates abusefilter-edit-builder-vars-removedlines
164            'removed_lines' => 'removedlines',
165            // Generates abusefilter-edit-builder-vars-summary
166            'summary' => 'summary',
167            // Generates abusefilter-edit-builder-vars-page-id
168            'page_id' => 'page-id',
169            // Generates abusefilter-edit-builder-vars-page-ns
170            'page_namespace' => 'page-ns',
171            // Generates abusefilter-edit-builder-vars-page-title
172            'page_title' => 'page-title',
173            // Generates abusefilter-edit-builder-vars-page-prefixedtitle
174            'page_prefixedtitle' => 'page-prefixedtitle',
175            // Generates abusefilter-edit-builder-vars-page-age
176            'page_age' => 'page-age',
177            // Generates abusefilter-edit-builder-vars-page-last-edit-age
178            'page_last_edit_age' => 'page-last-edit-age',
179            // Generates abusefilter-edit-builder-vars-movedfrom-id
180            'moved_from_id' => 'movedfrom-id',
181            // Generates abusefilter-edit-builder-vars-movedfrom-ns
182            'moved_from_namespace' => 'movedfrom-ns',
183            // Generates abusefilter-edit-builder-vars-movedfrom-title
184            'moved_from_title' => 'movedfrom-title',
185            // Generates abusefilter-edit-builder-vars-movedfrom-prefixedtitle
186            'moved_from_prefixedtitle' => 'movedfrom-prefixedtitle',
187            // Generates abusefilter-edit-builder-vars-movedfrom-age
188            'moved_from_age' => 'movedfrom-age',
189            // Generates abusefilter-edit-builder-vars-movedfrom-last-edit-age
190            'moved_from_last_edit_age' => 'movedfrom-last-edit-age',
191            // Generates abusefilter-edit-builder-vars-movedto-id
192            'moved_to_id' => 'movedto-id',
193            // Generates abusefilter-edit-builder-vars-movedto-ns
194            'moved_to_namespace' => 'movedto-ns',
195            // Generates abusefilter-edit-builder-vars-movedto-title
196            'moved_to_title' => 'movedto-title',
197            // Generates abusefilter-edit-builder-vars-movedto-prefixedtitle
198            'moved_to_prefixedtitle' => 'movedto-prefixedtitle',
199            // Generates abusefilter-edit-builder-vars-movedto-age
200            'moved_to_age' => 'movedto-age',
201            // Generates abusefilter-edit-builder-vars-movedto-last-edit-age
202            'moved_to_last_edit_age' => 'movedto-last-edit-age',
203            // Generates abusefilter-edit-builder-vars-user-editcount
204            'user_editcount' => 'user-editcount',
205            // Generates abusefilter-edit-builder-vars-user-age
206            'user_age' => 'user-age',
207            // Generates abusefilter-edit-builder-vars-user-unnamed-ip
208            'user_unnamed_ip' => 'user-unnamed-ip',
209            // Generates abusefilter-edit-builder-vars-user-name
210            'user_name' => 'user-name',
211            // Generates abusefilter-edit-builder-vars-user-type
212            'user_type' => 'user-type',
213            // Generates abusefilter-edit-builder-vars-user-groups
214            'user_groups' => 'user-groups',
215            // Generates abusefilter-edit-builder-vars-user-rights
216            'user_rights' => 'user-rights',
217            // Generates abusefilter-edit-builder-vars-user-blocked
218            'user_blocked' => 'user-blocked',
219            // Generates abusefilter-edit-builder-vars-user-emailconfirm
220            'user_emailconfirm' => 'user-emailconfirm',
221            // Generates abusefilter-edit-builder-vars-old-wikitext
222            'old_wikitext' => 'old-wikitext',
223            // Generates abusefilter-edit-builder-vars-new-wikitext
224            'new_wikitext' => 'new-wikitext',
225            // Generates abusefilter-edit-builder-vars-added-links
226            'added_links' => 'added-links',
227            // Generates abusefilter-edit-builder-vars-removed-links
228            'removed_links' => 'removed-links',
229            // Generates abusefilter-edit-builder-vars-old-links
230            'old_links' => 'old-links',
231            // Generates abusefilter-edit-builder-vars-new-links
232            'new_links' => 'new-links',
233            // Generates abusefilter-edit-builder-vars-new-pst
234            'new_pst' => 'new-pst',
235            // Generates abusefilter-edit-builder-vars-diff-pst
236            'edit_diff_pst' => 'diff-pst',
237            // Generates abusefilter-edit-builder-vars-addedlines-pst
238            'added_lines_pst' => 'addedlines-pst',
239            // Generates abusefilter-edit-builder-vars-new-text
240            'new_text' => 'new-text',
241            // Generates abusefilter-edit-builder-vars-new-html
242            'new_html' => 'new-html',
243            // Generates abusefilter-edit-builder-vars-restrictions-edit
244            'page_restrictions_edit' => 'restrictions-edit',
245            // Generates abusefilter-edit-builder-vars-restrictions-move
246            'page_restrictions_move' => 'restrictions-move',
247            // Generates abusefilter-edit-builder-vars-restrictions-create
248            'page_restrictions_create' => 'restrictions-create',
249            // Generates abusefilter-edit-builder-vars-restrictions-upload
250            'page_restrictions_upload' => 'restrictions-upload',
251            // Generates abusefilter-edit-builder-vars-recent-contributors
252            'page_recent_contributors' => 'recent-contributors',
253            // Generates abusefilter-edit-builder-vars-first-contributor
254            'page_first_contributor' => 'first-contributor',
255            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-edit
256            'moved_from_restrictions_edit' => 'movedfrom-restrictions-edit',
257            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-move
258            'moved_from_restrictions_move' => 'movedfrom-restrictions-move',
259            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-create
260            'moved_from_restrictions_create' => 'movedfrom-restrictions-create',
261            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-upload
262            'moved_from_restrictions_upload' => 'movedfrom-restrictions-upload',
263            // Generates abusefilter-edit-builder-vars-movedfrom-recent-contributors
264            'moved_from_recent_contributors' => 'movedfrom-recent-contributors',
265            // Generates abusefilter-edit-builder-vars-movedfrom-first-contributor
266            'moved_from_first_contributor' => 'movedfrom-first-contributor',
267            // Generates abusefilter-edit-builder-vars-movedto-restrictions-edit
268            'moved_to_restrictions_edit' => 'movedto-restrictions-edit',
269            // Generates abusefilter-edit-builder-vars-movedto-restrictions-move
270            'moved_to_restrictions_move' => 'movedto-restrictions-move',
271            // Generates abusefilter-edit-builder-vars-movedto-restrictions-create
272            'moved_to_restrictions_create' => 'movedto-restrictions-create',
273            // Generates abusefilter-edit-builder-vars-movedto-restrictions-upload
274            'moved_to_restrictions_upload' => 'movedto-restrictions-upload',
275            // Generates abusefilter-edit-builder-vars-movedto-recent-contributors
276            'moved_to_recent_contributors' => 'movedto-recent-contributors',
277            // Generates abusefilter-edit-builder-vars-movedto-first-contributor
278            'moved_to_first_contributor' => 'movedto-first-contributor',
279            // Generates abusefilter-edit-builder-vars-file-sha1
280            'file_sha1' => 'file-sha1',
281            // Generates abusefilter-edit-builder-vars-file-size
282            'file_size' => 'file-size',
283            // Generates abusefilter-edit-builder-vars-file-mime
284            'file_mime' => 'file-mime',
285            // Generates abusefilter-edit-builder-vars-file-mediatype
286            'file_mediatype' => 'file-mediatype',
287            // Generates abusefilter-edit-builder-vars-file-width
288            'file_width' => 'file-width',
289            // Generates abusefilter-edit-builder-vars-file-height
290            'file_height' => 'file-height',
291            // Generates abusefilter-edit-builder-vars-file-bits-per-channel
292            'file_bits_per_channel' => 'file-bits-per-channel',
293            // Generates abusefilter-edit-builder-vars-wiki-name
294            'wiki_name' => 'wiki-name',
295            // Generates abusefilter-edit-builder-vars-wiki-language
296            'wiki_language' => 'wiki-language',
297        ],
298    ];
299
300    /**
301     * Old vars which aren't in use anymore.
302     * The translatable messages that are based
303     * on them are not shown in the filter editor,
304     * but may still be shown in the log descriptions of
305     * filter actions that were taken by filters
306     * that used them.
307     *
308     * @var array<string,string>
309     */
310    private const DISABLED_VARS = [
311        // Generates abusefilter-edit-builder-vars-old-text
312        'old_text' => 'old-text',
313        // Generates abusefilter-edit-builder-vars-old-html
314        'old_html' => 'old-html',
315        // Generates abusefilter-edit-builder-vars-minor-edit
316        'minor_edit' => 'minor-edit'
317    ];
318
319    private const DEPRECATED_VARS = [
320        'article_text' => 'page_title',
321        'article_prefixedtext' => 'page_prefixedtitle',
322        'article_namespace' => 'page_namespace',
323        'article_articleid' => 'page_id',
324        'article_restrictions_edit' => 'page_restrictions_edit',
325        'article_restrictions_move' => 'page_restrictions_move',
326        'article_restrictions_create' => 'page_restrictions_create',
327        'article_restrictions_upload' => 'page_restrictions_upload',
328        'article_recent_contributors' => 'page_recent_contributors',
329        'article_first_contributor' => 'page_first_contributor',
330        'moved_from_text' => 'moved_from_title',
331        'moved_from_prefixedtext' => 'moved_from_prefixedtitle',
332        'moved_from_articleid' => 'moved_from_id',
333        'moved_to_text' => 'moved_to_title',
334        'moved_to_prefixedtext' => 'moved_to_prefixedtitle',
335        'moved_to_articleid' => 'moved_to_id',
336        'all_links' => 'new_links',
337        'accountname' => 'account_name',
338    ];
339
340    /** @var string[][] Final list of builder values */
341    private $builderValues;
342
343    /** @var string[] Final list of deprecated vars */
344    private $deprecatedVars;
345
346    public function __construct( private readonly AbuseFilterHookRunner $hookRunner ) {
347    }
348
349    public function getDisabledVariables(): array {
350        return self::DISABLED_VARS;
351    }
352
353    public function getDeprecatedVariables(): array {
354        if ( $this->deprecatedVars === null ) {
355            $this->deprecatedVars = self::DEPRECATED_VARS;
356            $this->hookRunner->onAbuseFilter_deprecatedVariables( $this->deprecatedVars );
357        }
358        return $this->deprecatedVars;
359    }
360
361    public function getBuilderValues(): array {
362        if ( $this->builderValues === null ) {
363            $this->builderValues = self::BUILDER_VALUES;
364            $this->hookRunner->onAbuseFilter_builder( $this->builderValues );
365        }
366        return $this->builderValues;
367    }
368
369    public function isVarDisabled( string $name ): bool {
370        return array_key_exists( $name, self::DISABLED_VARS );
371    }
372
373    public function isVarDeprecated( string $name ): bool {
374        return array_key_exists( $name, $this->getDeprecatedVariables() );
375    }
376
377    public function isVarInUse( string $name ): bool {
378        return array_key_exists( $name, $this->getVarsMappings() );
379    }
380
381    /**
382     * Check whether the given name corresponds to a known variable.
383     * @param string $name
384     * @return bool
385     */
386    public function varExists( string $name ): bool {
387        return $this->isVarInUse( $name ) ||
388            $this->isVarDisabled( $name ) ||
389            $this->isVarDeprecated( $name );
390    }
391
392    /**
393     * Get the message for a builtin variable; takes deprecated variables into account.
394     * Returns null for non-builtin variables.
395     *
396     * @param string $var
397     * @return string|null
398     */
399    public function getMessageKeyForVar( string $var ): ?string {
400        if ( !$this->varExists( $var ) ) {
401            return null;
402        }
403        if ( $this->isVarDeprecated( $var ) ) {
404            $var = $this->getDeprecatedVariables()[$var];
405        }
406
407        $key = self::DISABLED_VARS[$var] ??
408            $this->getVarsMappings()[$var];
409        return "abusefilter-edit-builder-vars-$key";
410    }
411
412    public function getVarsMappings(): array {
413        return $this->getBuilderValues()['vars'];
414    }
415
416    /**
417     * Get a list of core variables, i.e. variables defined in AbuseFilter (ignores hooks).
418     * You usually want to use getVarsMappings(), not this one.
419     * @return string[]
420     */
421    public function getCoreVariables(): array {
422        return array_keys( self::BUILDER_VALUES['vars'] );
423    }
424}