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