Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
25 / 25 |
|
100.00% |
11 / 11 |
CRAP | |
100.00% |
1 / 1 |
KeywordsManager | |
100.00% |
25 / 25 |
|
100.00% |
11 / 11 |
17 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDisabledVariables | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDeprecatedVariables | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getBuilderValues | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
isVarDisabled | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isVarDeprecated | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isVarInUse | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
varExists | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getMessageKeyForVar | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getVarsMappings | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCoreVariables | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter; |
4 | |
5 | use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner; |
6 | |
7 | /** |
8 | * This service can be used to manage the list of keywords recognized by the Parser |
9 | */ |
10 | class KeywordsManager { |
11 | public const SERVICE_NAME = 'AbuseFilterKeywordsManager'; |
12 | |
13 | private const BUILDER_VALUES = [ |
14 | 'op-arithmetic' => [ |
15 | '+' => 'addition', |
16 | '-' => 'subtraction', |
17 | '*' => 'multiplication', |
18 | '/' => 'divide', |
19 | '%' => 'modulo', |
20 | '**' => 'pow' |
21 | ], |
22 | 'op-comparison' => [ |
23 | '==' => 'equal', |
24 | '===' => 'equal-strict', |
25 | '!=' => 'notequal', |
26 | '!==' => 'notequal-strict', |
27 | '<' => 'lt', |
28 | '>' => 'gt', |
29 | '<=' => 'lte', |
30 | '>=' => 'gte' |
31 | ], |
32 | 'op-bool' => [ |
33 | '!' => 'not', |
34 | '&' => 'and', |
35 | '|' => 'or', |
36 | '^' => 'xor' |
37 | ], |
38 | 'misc' => [ |
39 | 'in' => 'in', |
40 | 'contains' => 'contains', |
41 | 'like' => 'like', |
42 | '""' => 'stringlit', |
43 | 'rlike' => 'rlike', |
44 | 'irlike' => 'irlike', |
45 | 'cond ? iftrue : iffalse' => 'tern', |
46 | 'if cond then iftrue else iffalse end' => 'cond', |
47 | 'if cond then iftrue end' => 'cond-short', |
48 | ], |
49 | 'funcs' => [ |
50 | 'length(string)' => 'length', |
51 | 'lcase(string)' => 'lcase', |
52 | 'ucase(string)' => 'ucase', |
53 | 'ccnorm(string)' => 'ccnorm', |
54 | 'ccnorm_contains_any(haystack,needle1,needle2,..)' => 'ccnorm-contains-any', |
55 | 'ccnorm_contains_all(haystack,needle1,needle2,..)' => 'ccnorm-contains-all', |
56 | 'rmdoubles(string)' => 'rmdoubles', |
57 | 'specialratio(string)' => 'specialratio', |
58 | 'norm(string)' => 'norm', |
59 | 'count(needle,haystack)' => 'count', |
60 | 'rcount(needle,haystack)' => 'rcount', |
61 | 'get_matches(needle,haystack)' => 'get_matches', |
62 | 'rmwhitespace(text)' => 'rmwhitespace', |
63 | 'rmspecials(text)' => 'rmspecials', |
64 | 'ip_in_range(ip, range)' => 'ip_in_range', |
65 | 'ip_in_ranges(ip, range1, range2, ...)' => 'ip_in_ranges', |
66 | 'contains_any(haystack,needle1,needle2,...)' => 'contains-any', |
67 | 'contains_all(haystack,needle1,needle2,...)' => 'contains-all', |
68 | 'equals_to_any(haystack,needle1,needle2,...)' => 'equals-to-any', |
69 | 'substr(subject, offset, length)' => 'substr', |
70 | 'strpos(haystack, needle)' => 'strpos', |
71 | 'str_replace(subject, search, replace)' => 'str_replace', |
72 | 'str_replace_regexp(subject, search, replace)' => 'str_replace_regexp', |
73 | 'rescape(string)' => 'rescape', |
74 | 'set_var(var,value)' => 'set_var', |
75 | 'sanitize(string)' => 'sanitize', |
76 | ], |
77 | 'vars' => [ |
78 | 'timestamp' => 'timestamp', |
79 | 'accountname' => 'accountname', |
80 | 'action' => 'action', |
81 | 'added_lines' => 'addedlines', |
82 | 'edit_delta' => 'delta', |
83 | 'edit_diff' => 'diff', |
84 | 'new_size' => 'newsize', |
85 | 'old_size' => 'oldsize', |
86 | 'new_content_model' => 'new-content-model', |
87 | 'old_content_model' => 'old-content-model', |
88 | 'removed_lines' => 'removedlines', |
89 | 'summary' => 'summary', |
90 | 'page_id' => 'page-id', |
91 | 'page_namespace' => 'page-ns', |
92 | 'page_title' => 'page-title', |
93 | 'page_prefixedtitle' => 'page-prefixedtitle', |
94 | 'page_age' => 'page-age', |
95 | 'moved_from_id' => 'movedfrom-id', |
96 | 'moved_from_namespace' => 'movedfrom-ns', |
97 | 'moved_from_title' => 'movedfrom-title', |
98 | 'moved_from_prefixedtitle' => 'movedfrom-prefixedtitle', |
99 | 'moved_from_age' => 'movedfrom-age', |
100 | 'moved_to_id' => 'movedto-id', |
101 | 'moved_to_namespace' => 'movedto-ns', |
102 | 'moved_to_title' => 'movedto-title', |
103 | 'moved_to_prefixedtitle' => 'movedto-prefixedtitle', |
104 | 'moved_to_age' => 'movedto-age', |
105 | 'user_editcount' => 'user-editcount', |
106 | 'user_age' => 'user-age', |
107 | 'user_name' => 'user-name', |
108 | 'user_groups' => 'user-groups', |
109 | 'user_rights' => 'user-rights', |
110 | 'user_blocked' => 'user-blocked', |
111 | 'user_emailconfirm' => 'user-emailconfirm', |
112 | 'old_wikitext' => 'old-wikitext', |
113 | 'new_wikitext' => 'new-wikitext', |
114 | 'added_links' => 'added-links', |
115 | 'removed_links' => 'removed-links', |
116 | 'all_links' => 'all-links', |
117 | 'new_pst' => 'new-pst', |
118 | 'edit_diff_pst' => 'diff-pst', |
119 | 'added_lines_pst' => 'addedlines-pst', |
120 | 'new_text' => 'new-text', |
121 | 'new_html' => 'new-html', |
122 | 'page_restrictions_edit' => 'restrictions-edit', |
123 | 'page_restrictions_move' => 'restrictions-move', |
124 | 'page_restrictions_create' => 'restrictions-create', |
125 | 'page_restrictions_upload' => 'restrictions-upload', |
126 | 'page_recent_contributors' => 'recent-contributors', |
127 | 'page_first_contributor' => 'first-contributor', |
128 | 'moved_from_restrictions_edit' => 'movedfrom-restrictions-edit', |
129 | 'moved_from_restrictions_move' => 'movedfrom-restrictions-move', |
130 | 'moved_from_restrictions_create' => 'movedfrom-restrictions-create', |
131 | 'moved_from_restrictions_upload' => 'movedfrom-restrictions-upload', |
132 | 'moved_from_recent_contributors' => 'movedfrom-recent-contributors', |
133 | 'moved_from_first_contributor' => 'movedfrom-first-contributor', |
134 | 'moved_to_restrictions_edit' => 'movedto-restrictions-edit', |
135 | 'moved_to_restrictions_move' => 'movedto-restrictions-move', |
136 | 'moved_to_restrictions_create' => 'movedto-restrictions-create', |
137 | 'moved_to_restrictions_upload' => 'movedto-restrictions-upload', |
138 | 'moved_to_recent_contributors' => 'movedto-recent-contributors', |
139 | 'moved_to_first_contributor' => 'movedto-first-contributor', |
140 | 'old_links' => 'old-links', |
141 | 'file_sha1' => 'file-sha1', |
142 | 'file_size' => 'file-size', |
143 | 'file_mime' => 'file-mime', |
144 | 'file_mediatype' => 'file-mediatype', |
145 | 'file_width' => 'file-width', |
146 | 'file_height' => 'file-height', |
147 | 'file_bits_per_channel' => 'file-bits-per-channel', |
148 | 'wiki_name' => 'wiki-name', |
149 | 'wiki_language' => 'wiki-language', |
150 | ], |
151 | ]; |
152 | |
153 | /** @var array Old vars which aren't in use anymore */ |
154 | private const DISABLED_VARS = [ |
155 | 'old_text' => 'old-text', |
156 | 'old_html' => 'old-html', |
157 | 'minor_edit' => 'minor-edit' |
158 | ]; |
159 | |
160 | private const DEPRECATED_VARS = [ |
161 | 'article_text' => 'page_title', |
162 | 'article_prefixedtext' => 'page_prefixedtitle', |
163 | 'article_namespace' => 'page_namespace', |
164 | 'article_articleid' => 'page_id', |
165 | 'article_restrictions_edit' => 'page_restrictions_edit', |
166 | 'article_restrictions_move' => 'page_restrictions_move', |
167 | 'article_restrictions_create' => 'page_restrictions_create', |
168 | 'article_restrictions_upload' => 'page_restrictions_upload', |
169 | 'article_recent_contributors' => 'page_recent_contributors', |
170 | 'article_first_contributor' => 'page_first_contributor', |
171 | 'moved_from_text' => 'moved_from_title', |
172 | 'moved_from_prefixedtext' => 'moved_from_prefixedtitle', |
173 | 'moved_from_articleid' => 'moved_from_id', |
174 | 'moved_to_text' => 'moved_to_title', |
175 | 'moved_to_prefixedtext' => 'moved_to_prefixedtitle', |
176 | 'moved_to_articleid' => 'moved_to_id', |
177 | ]; |
178 | |
179 | /** @var string[][] Final list of builder values */ |
180 | private $builderValues; |
181 | |
182 | /** @var string[] Final list of deprecated vars */ |
183 | private $deprecatedVars; |
184 | |
185 | /** @var AbuseFilterHookRunner */ |
186 | private $hookRunner; |
187 | |
188 | /** |
189 | * @param AbuseFilterHookRunner $hookRunner |
190 | */ |
191 | public function __construct( AbuseFilterHookRunner $hookRunner ) { |
192 | $this->hookRunner = $hookRunner; |
193 | } |
194 | |
195 | /** |
196 | * @return array |
197 | */ |
198 | public function getDisabledVariables(): array { |
199 | return self::DISABLED_VARS; |
200 | } |
201 | |
202 | /** |
203 | * @return array |
204 | */ |
205 | public function getDeprecatedVariables(): array { |
206 | if ( $this->deprecatedVars === null ) { |
207 | $this->deprecatedVars = self::DEPRECATED_VARS; |
208 | $this->hookRunner->onAbuseFilter_deprecatedVariables( $this->deprecatedVars ); |
209 | } |
210 | return $this->deprecatedVars; |
211 | } |
212 | |
213 | /** |
214 | * @return array |
215 | */ |
216 | public function getBuilderValues(): array { |
217 | if ( $this->builderValues === null ) { |
218 | $this->builderValues = self::BUILDER_VALUES; |
219 | $this->hookRunner->onAbuseFilter_builder( $this->builderValues ); |
220 | } |
221 | return $this->builderValues; |
222 | } |
223 | |
224 | /** |
225 | * @param string $name |
226 | * @return bool |
227 | */ |
228 | public function isVarDisabled( string $name ): bool { |
229 | return array_key_exists( $name, self::DISABLED_VARS ); |
230 | } |
231 | |
232 | /** |
233 | * @param string $name |
234 | * @return bool |
235 | */ |
236 | public function isVarDeprecated( string $name ): bool { |
237 | return array_key_exists( $name, $this->getDeprecatedVariables() ); |
238 | } |
239 | |
240 | /** |
241 | * @param string $name |
242 | * @return bool |
243 | */ |
244 | public function isVarInUse( string $name ): bool { |
245 | return array_key_exists( $name, $this->getVarsMappings() ); |
246 | } |
247 | |
248 | /** |
249 | * Check whether the given name corresponds to a known variable. |
250 | * @param string $name |
251 | * @return bool |
252 | */ |
253 | public function varExists( string $name ): bool { |
254 | return $this->isVarInUse( $name ) || |
255 | $this->isVarDisabled( $name ) || |
256 | $this->isVarDeprecated( $name ); |
257 | } |
258 | |
259 | /** |
260 | * Get the message for a builtin variable; takes deprecated variables into account. |
261 | * Returns null for non-builtin variables. |
262 | * |
263 | * @param string $var |
264 | * @return string|null |
265 | */ |
266 | public function getMessageKeyForVar( string $var ): ?string { |
267 | if ( !$this->varExists( $var ) ) { |
268 | return null; |
269 | } |
270 | if ( $this->isVarDeprecated( $var ) ) { |
271 | $var = $this->getDeprecatedVariables()[$var]; |
272 | } |
273 | |
274 | $key = self::DISABLED_VARS[$var] ?? |
275 | $this->getVarsMappings()[$var]; |
276 | return "abusefilter-edit-builder-vars-$key"; |
277 | } |
278 | |
279 | /** |
280 | * @return array |
281 | */ |
282 | public function getVarsMappings(): array { |
283 | return $this->getBuilderValues()['vars']; |
284 | } |
285 | |
286 | /** |
287 | * Get a list of core variables, i.e. variables defined in AbuseFilter (ignores hooks). |
288 | * You usually want to use getVarsMappings(), not this one. |
289 | * @return string[] |
290 | */ |
291 | public function getCoreVariables(): array { |
292 | return array_keys( self::BUILDER_VALUES['vars'] ); |
293 | } |
294 | } |