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-accountname
144            'accountname' => 'accountname',
145            // Generates abusefilter-edit-builder-vars-action
146            'action' => 'action',
147            // Generates abusefilter-edit-builder-vars-addedlines
148            'added_lines' => 'addedlines',
149            // Generates abusefilter-edit-builder-vars-delta
150            'edit_delta' => 'delta',
151            // Generates abusefilter-edit-builder-vars-diff
152            'edit_diff' => 'diff',
153            // Generates abusefilter-edit-builder-vars-newsize
154            'new_size' => 'newsize',
155            // Generates abusefilter-edit-builder-vars-oldsize
156            'old_size' => 'oldsize',
157            // Generates abusefilter-edit-builder-vars-new-content-model
158            'new_content_model' => 'new-content-model',
159            // Generates abusefilter-edit-builder-vars-old-content-model
160            'old_content_model' => 'old-content-model',
161            // Generates abusefilter-edit-builder-vars-removedlines
162            'removed_lines' => 'removedlines',
163            // Generates abusefilter-edit-builder-vars-summary
164            'summary' => 'summary',
165            // Generates abusefilter-edit-builder-vars-page-id
166            'page_id' => 'page-id',
167            // Generates abusefilter-edit-builder-vars-page-ns
168            'page_namespace' => 'page-ns',
169            // Generates abusefilter-edit-builder-vars-page-title
170            'page_title' => 'page-title',
171            // Generates abusefilter-edit-builder-vars-page-prefixedtitle
172            'page_prefixedtitle' => 'page-prefixedtitle',
173            // Generates abusefilter-edit-builder-vars-page-age
174            'page_age' => 'page-age',
175            // Generates abusefilter-edit-builder-vars-page-last-edit-age
176            'page_last_edit_age' => 'page-last-edit-age',
177            // Generates abusefilter-edit-builder-vars-movedfrom-id
178            'moved_from_id' => 'movedfrom-id',
179            // Generates abusefilter-edit-builder-vars-movedfrom-ns
180            'moved_from_namespace' => 'movedfrom-ns',
181            // Generates abusefilter-edit-builder-vars-movedfrom-title
182            'moved_from_title' => 'movedfrom-title',
183            // Generates abusefilter-edit-builder-vars-movedfrom-prefixedtitle
184            'moved_from_prefixedtitle' => 'movedfrom-prefixedtitle',
185            // Generates abusefilter-edit-builder-vars-movedfrom-age
186            'moved_from_age' => 'movedfrom-age',
187            // Generates abusefilter-edit-builder-vars-movedfrom-last-edit-age
188            'moved_from_last_edit_age' => 'movedfrom-last-edit-age',
189            // Generates abusefilter-edit-builder-vars-movedto-id
190            'moved_to_id' => 'movedto-id',
191            // Generates abusefilter-edit-builder-vars-movedto-ns
192            'moved_to_namespace' => 'movedto-ns',
193            // Generates abusefilter-edit-builder-vars-movedto-title
194            'moved_to_title' => 'movedto-title',
195            // Generates abusefilter-edit-builder-vars-movedto-prefixedtitle
196            'moved_to_prefixedtitle' => 'movedto-prefixedtitle',
197            // Generates abusefilter-edit-builder-vars-movedto-age
198            'moved_to_age' => 'movedto-age',
199            // Generates abusefilter-edit-builder-vars-movedto-last-edit-age
200            'moved_to_last_edit_age' => 'movedto-last-edit-age',
201            // Generates abusefilter-edit-builder-vars-user-editcount
202            'user_editcount' => 'user-editcount',
203            // Generates abusefilter-edit-builder-vars-user-age
204            'user_age' => 'user-age',
205            // Generates abusefilter-edit-builder-vars-user-name
206            'user_name' => 'user-name',
207            // Generates abusefilter-edit-builder-vars-user-type
208            'user_type' => 'user-type',
209            // Generates abusefilter-edit-builder-vars-user-groups
210            'user_groups' => 'user-groups',
211            // Generates abusefilter-edit-builder-vars-user-rights
212            'user_rights' => 'user-rights',
213            // Generates abusefilter-edit-builder-vars-user-blocked
214            'user_blocked' => 'user-blocked',
215            // Generates abusefilter-edit-builder-vars-user-emailconfirm
216            'user_emailconfirm' => 'user-emailconfirm',
217            // Generates abusefilter-edit-builder-vars-old-wikitext
218            'old_wikitext' => 'old-wikitext',
219            // Generates abusefilter-edit-builder-vars-new-wikitext
220            'new_wikitext' => 'new-wikitext',
221            // Generates abusefilter-edit-builder-vars-added-links
222            'added_links' => 'added-links',
223            // Generates abusefilter-edit-builder-vars-removed-links
224            'removed_links' => 'removed-links',
225            // Generates abusefilter-edit-builder-vars-all-links
226            'all_links' => 'all-links',
227            // Generates abusefilter-edit-builder-vars-new-pst
228            'new_pst' => 'new-pst',
229            // Generates abusefilter-edit-builder-vars-diff-pst
230            'edit_diff_pst' => 'diff-pst',
231            // Generates abusefilter-edit-builder-vars-addedlines-pst
232            'added_lines_pst' => 'addedlines-pst',
233            // Generates abusefilter-edit-builder-vars-new-text
234            'new_text' => 'new-text',
235            // Generates abusefilter-edit-builder-vars-new-html
236            'new_html' => 'new-html',
237            // Generates abusefilter-edit-builder-vars-restrictions-edit
238            'page_restrictions_edit' => 'restrictions-edit',
239            // Generates abusefilter-edit-builder-vars-restrictions-move
240            'page_restrictions_move' => 'restrictions-move',
241            // Generates abusefilter-edit-builder-vars-restrictions-create
242            'page_restrictions_create' => 'restrictions-create',
243            // Generates abusefilter-edit-builder-vars-restrictions-upload
244            'page_restrictions_upload' => 'restrictions-upload',
245            // Generates abusefilter-edit-builder-vars-recent-contributors
246            'page_recent_contributors' => 'recent-contributors',
247            // Generates abusefilter-edit-builder-vars-first-contributor
248            'page_first_contributor' => 'first-contributor',
249            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-edit
250            'moved_from_restrictions_edit' => 'movedfrom-restrictions-edit',
251            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-move
252            'moved_from_restrictions_move' => 'movedfrom-restrictions-move',
253            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-create
254            'moved_from_restrictions_create' => 'movedfrom-restrictions-create',
255            // Generates abusefilter-edit-builder-vars-movedfrom-restrictions-upload
256            'moved_from_restrictions_upload' => 'movedfrom-restrictions-upload',
257            // Generates abusefilter-edit-builder-vars-movedfrom-recent-contributors
258            'moved_from_recent_contributors' => 'movedfrom-recent-contributors',
259            // Generates abusefilter-edit-builder-vars-movedfrom-first-contributor
260            'moved_from_first_contributor' => 'movedfrom-first-contributor',
261            // Generates abusefilter-edit-builder-vars-movedto-restrictions-edit
262            'moved_to_restrictions_edit' => 'movedto-restrictions-edit',
263            // Generates abusefilter-edit-builder-vars-movedto-restrictions-move
264            'moved_to_restrictions_move' => 'movedto-restrictions-move',
265            // Generates abusefilter-edit-builder-vars-movedto-restrictions-create
266            'moved_to_restrictions_create' => 'movedto-restrictions-create',
267            // Generates abusefilter-edit-builder-vars-movedto-restrictions-upload
268            'moved_to_restrictions_upload' => 'movedto-restrictions-upload',
269            // Generates abusefilter-edit-builder-vars-movedto-recent-contributors
270            'moved_to_recent_contributors' => 'movedto-recent-contributors',
271            // Generates abusefilter-edit-builder-vars-movedto-first-contributor
272            'moved_to_first_contributor' => 'movedto-first-contributor',
273            // Generates abusefilter-edit-builder-vars-old-links
274            'old_links' => 'old-links',
275            // Generates abusefilter-edit-builder-vars-file-sha1
276            'file_sha1' => 'file-sha1',
277            // Generates abusefilter-edit-builder-vars-file-size
278            'file_size' => 'file-size',
279            // Generates abusefilter-edit-builder-vars-file-mime
280            'file_mime' => 'file-mime',
281            // Generates abusefilter-edit-builder-vars-file-mediatype
282            'file_mediatype' => 'file-mediatype',
283            // Generates abusefilter-edit-builder-vars-file-width
284            'file_width' => 'file-width',
285            // Generates abusefilter-edit-builder-vars-file-height
286            'file_height' => 'file-height',
287            // Generates abusefilter-edit-builder-vars-file-bits-per-channel
288            'file_bits_per_channel' => 'file-bits-per-channel',
289            // Generates abusefilter-edit-builder-vars-wiki-name
290            'wiki_name' => 'wiki-name',
291            // Generates abusefilter-edit-builder-vars-wiki-language
292            'wiki_language' => 'wiki-language',
293        ],
294    ];
295
296    /**
297     * Old vars which aren't in use anymore.
298     * The translatable messages that are based
299     * on them are not shown in the filter editor,
300     * but may still be shown in the log descriptions of
301     * filter actions that were taken by filters
302     * that used them.
303     *
304     * @var array
305     */
306    private const DISABLED_VARS = [
307        // Generates abusefilter-edit-builder-vars-old-text
308        'old_text' => 'old-text',
309        // Generates abusefilter-edit-builder-vars-old-html
310        'old_html' => 'old-html',
311        // Generates abusefilter-edit-builder-vars-minor-edit
312        'minor_edit' => 'minor-edit'
313    ];
314
315    private const DEPRECATED_VARS = [
316        'article_text' => 'page_title',
317        'article_prefixedtext' => 'page_prefixedtitle',
318        'article_namespace' => 'page_namespace',
319        'article_articleid' => 'page_id',
320        'article_restrictions_edit' => 'page_restrictions_edit',
321        'article_restrictions_move' => 'page_restrictions_move',
322        'article_restrictions_create' => 'page_restrictions_create',
323        'article_restrictions_upload' => 'page_restrictions_upload',
324        'article_recent_contributors' => 'page_recent_contributors',
325        'article_first_contributor' => 'page_first_contributor',
326        'moved_from_text' => 'moved_from_title',
327        'moved_from_prefixedtext' => 'moved_from_prefixedtitle',
328        'moved_from_articleid' => 'moved_from_id',
329        'moved_to_text' => 'moved_to_title',
330        'moved_to_prefixedtext' => 'moved_to_prefixedtitle',
331        'moved_to_articleid' => 'moved_to_id',
332    ];
333
334    /** @var string[][] Final list of builder values */
335    private $builderValues;
336
337    /** @var string[] Final list of deprecated vars */
338    private $deprecatedVars;
339
340    /** @var AbuseFilterHookRunner */
341    private $hookRunner;
342
343    /**
344     * @param AbuseFilterHookRunner $hookRunner
345     */
346    public function __construct( AbuseFilterHookRunner $hookRunner ) {
347        $this->hookRunner = $hookRunner;
348    }
349
350    /**
351     * @return array
352     */
353    public function getDisabledVariables(): array {
354        return self::DISABLED_VARS;
355    }
356
357    /**
358     * @return array
359     */
360    public function getDeprecatedVariables(): array {
361        if ( $this->deprecatedVars === null ) {
362            $this->deprecatedVars = self::DEPRECATED_VARS;
363            $this->hookRunner->onAbuseFilter_deprecatedVariables( $this->deprecatedVars );
364        }
365        return $this->deprecatedVars;
366    }
367
368    /**
369     * @return array
370     */
371    public function getBuilderValues(): array {
372        if ( $this->builderValues === null ) {
373            $this->builderValues = self::BUILDER_VALUES;
374            $this->hookRunner->onAbuseFilter_builder( $this->builderValues );
375        }
376        return $this->builderValues;
377    }
378
379    /**
380     * @param string $name
381     * @return bool
382     */
383    public function isVarDisabled( string $name ): bool {
384        return array_key_exists( $name, self::DISABLED_VARS );
385    }
386
387    /**
388     * @param string $name
389     * @return bool
390     */
391    public function isVarDeprecated( string $name ): bool {
392        return array_key_exists( $name, $this->getDeprecatedVariables() );
393    }
394
395    /**
396     * @param string $name
397     * @return bool
398     */
399    public function isVarInUse( string $name ): bool {
400        return array_key_exists( $name, $this->getVarsMappings() );
401    }
402
403    /**
404     * Check whether the given name corresponds to a known variable.
405     * @param string $name
406     * @return bool
407     */
408    public function varExists( string $name ): bool {
409        return $this->isVarInUse( $name ) ||
410            $this->isVarDisabled( $name ) ||
411            $this->isVarDeprecated( $name );
412    }
413
414    /**
415     * Get the message for a builtin variable; takes deprecated variables into account.
416     * Returns null for non-builtin variables.
417     *
418     * @param string $var
419     * @return string|null
420     */
421    public function getMessageKeyForVar( string $var ): ?string {
422        if ( !$this->varExists( $var ) ) {
423            return null;
424        }
425        if ( $this->isVarDeprecated( $var ) ) {
426            $var = $this->getDeprecatedVariables()[$var];
427        }
428
429        $key = self::DISABLED_VARS[$var] ??
430            $this->getVarsMappings()[$var];
431        return "abusefilter-edit-builder-vars-$key";
432    }
433
434    /**
435     * @return array
436     */
437    public function getVarsMappings(): array {
438        return $this->getBuilderValues()['vars'];
439    }
440
441    /**
442     * Get a list of core variables, i.e. variables defined in AbuseFilter (ignores hooks).
443     * You usually want to use getVarsMappings(), not this one.
444     * @return string[]
445     */
446    public function getCoreVariables(): array {
447        return array_keys( self::BUILDER_VALUES['vars'] );
448    }
449}