Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
2.94% |
5 / 170 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
AbuseFilterViewTestBatch | |
2.94% |
5 / 170 |
|
25.00% |
1 / 4 |
644.09 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
show | |
0.00% |
0 / 83 |
|
0.00% |
0 / 1 |
6 | |||
doTest | |
0.00% |
0 / 69 |
|
0.00% |
0 / 1 |
342 | |||
loadParameters | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\View; |
4 | |
5 | use HTMLForm; |
6 | use IContextSource; |
7 | use LogEventsList; |
8 | use LogPage; |
9 | use MediaWiki\Extension\AbuseFilter\AbuseFilterChangesList; |
10 | use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager; |
11 | use MediaWiki\Extension\AbuseFilter\EditBox\EditBoxBuilderFactory; |
12 | use MediaWiki\Extension\AbuseFilter\EditBox\EditBoxField; |
13 | use MediaWiki\Extension\AbuseFilter\Parser\RuleCheckerFactory; |
14 | use MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGeneratorFactory; |
15 | use MediaWiki\Html\Html; |
16 | use MediaWiki\Linker\LinkRenderer; |
17 | use MediaWiki\Revision\RevisionRecord; |
18 | use MediaWiki\Title\Title; |
19 | use RecentChange; |
20 | use Wikimedia\Rdbms\LBFactory; |
21 | |
22 | class AbuseFilterViewTestBatch extends AbuseFilterView { |
23 | /** |
24 | * @var int The limit of changes to test, hard coded for now |
25 | */ |
26 | private static $mChangeLimit = 100; |
27 | |
28 | /** |
29 | * @var LBFactory |
30 | */ |
31 | private $lbFactory; |
32 | /** |
33 | * @var string The text of the rule to test changes against |
34 | */ |
35 | private $testPattern; |
36 | /** |
37 | * @var EditBoxBuilderFactory |
38 | */ |
39 | private $boxBuilderFactory; |
40 | /** |
41 | * @var RuleCheckerFactory |
42 | */ |
43 | private $ruleCheckerFactory; |
44 | /** |
45 | * @var VariableGeneratorFactory |
46 | */ |
47 | private $varGeneratorFactory; |
48 | |
49 | /** |
50 | * @param LBFactory $lbFactory |
51 | * @param AbuseFilterPermissionManager $afPermManager |
52 | * @param EditBoxBuilderFactory $boxBuilderFactory |
53 | * @param RuleCheckerFactory $ruleCheckerFactory |
54 | * @param VariableGeneratorFactory $varGeneratorFactory |
55 | * @param IContextSource $context |
56 | * @param LinkRenderer $linkRenderer |
57 | * @param string $basePageName |
58 | * @param array $params |
59 | */ |
60 | public function __construct( |
61 | LBFactory $lbFactory, |
62 | AbuseFilterPermissionManager $afPermManager, |
63 | EditBoxBuilderFactory $boxBuilderFactory, |
64 | RuleCheckerFactory $ruleCheckerFactory, |
65 | VariableGeneratorFactory $varGeneratorFactory, |
66 | IContextSource $context, |
67 | LinkRenderer $linkRenderer, |
68 | string $basePageName, |
69 | array $params |
70 | ) { |
71 | parent::__construct( $afPermManager, $context, $linkRenderer, $basePageName, $params ); |
72 | $this->lbFactory = $lbFactory; |
73 | $this->boxBuilderFactory = $boxBuilderFactory; |
74 | $this->ruleCheckerFactory = $ruleCheckerFactory; |
75 | $this->varGeneratorFactory = $varGeneratorFactory; |
76 | } |
77 | |
78 | /** |
79 | * Shows the page |
80 | */ |
81 | public function show() { |
82 | $out = $this->getOutput(); |
83 | |
84 | if ( !$this->afPermManager->canUseTestTools( $this->getAuthority() ) ) { |
85 | // TODO: the message still refers to the old rights |
86 | $out->addWikiMsg( 'abusefilter-mustviewprivateoredit' ); |
87 | return; |
88 | } |
89 | |
90 | $this->loadParameters(); |
91 | |
92 | $out->setPageTitleMsg( $this->msg( 'abusefilter-test' ) ); |
93 | $out->addHelpLink( 'Extension:AbuseFilter/Rules format' ); |
94 | $out->addWikiMsg( 'abusefilter-test-intro', self::$mChangeLimit ); |
95 | $out->enableOOUI(); |
96 | |
97 | $boxBuilder = $this->boxBuilderFactory->newEditBoxBuilder( $this, $this->getAuthority(), $out ); |
98 | |
99 | $rulesFields = [ |
100 | 'rules' => [ |
101 | 'section' => 'abusefilter-test-rules-section', |
102 | 'class' => EditBoxField::class, |
103 | 'html' => $boxBuilder->buildEditBox( |
104 | $this->testPattern, |
105 | true, |
106 | true, |
107 | false |
108 | ) . $this->buildFilterLoader() |
109 | ] |
110 | ]; |
111 | |
112 | $RCMaxAge = $this->getConfig()->get( 'RCMaxAge' ); |
113 | $min = wfTimestamp( TS_ISO_8601, time() - $RCMaxAge ); |
114 | $max = wfTimestampNow(); |
115 | |
116 | $optionsFields = [ |
117 | 'TestAction' => [ |
118 | 'type' => 'select', |
119 | 'label-message' => 'abusefilter-test-action', |
120 | 'options-messages' => [ |
121 | 'abusefilter-test-search-type-all' => '0', |
122 | 'abusefilter-test-search-type-edit' => 'edit', |
123 | 'abusefilter-test-search-type-move' => 'move', |
124 | 'abusefilter-test-search-type-delete' => 'delete', |
125 | 'abusefilter-test-search-type-createaccount' => 'createaccount', |
126 | 'abusefilter-test-search-type-upload' => 'upload' |
127 | ], |
128 | ], |
129 | 'TestUser' => [ |
130 | 'type' => 'user', |
131 | 'exists' => true, |
132 | 'ipallowed' => true, |
133 | 'required' => false, |
134 | 'label-message' => 'abusefilter-test-user', |
135 | ], |
136 | 'ExcludeBots' => [ |
137 | 'type' => 'check', |
138 | 'label-message' => 'abusefilter-test-nobots', |
139 | ], |
140 | 'TestPeriodStart' => [ |
141 | 'type' => 'datetime', |
142 | 'label-message' => 'abusefilter-test-period-start', |
143 | 'min' => $min, |
144 | 'max' => $max, |
145 | ], |
146 | 'TestPeriodEnd' => [ |
147 | 'type' => 'datetime', |
148 | 'label-message' => 'abusefilter-test-period-end', |
149 | 'min' => $min, |
150 | 'max' => $max, |
151 | ], |
152 | 'TestPage' => [ |
153 | 'type' => 'title', |
154 | 'label-message' => 'abusefilter-test-page', |
155 | 'creatable' => true, |
156 | 'required' => false, |
157 | ], |
158 | 'ShowNegative' => [ |
159 | 'type' => 'check', |
160 | 'label-message' => 'abusefilter-test-shownegative', |
161 | ], |
162 | ]; |
163 | array_walk( $optionsFields, static function ( &$el ) { |
164 | $el['section'] = 'abusefilter-test-options-section'; |
165 | } ); |
166 | $allFields = array_merge( $rulesFields, $optionsFields ); |
167 | |
168 | HTMLForm::factory( 'ooui', $allFields, $this->getContext() ) |
169 | ->setTitle( $this->getTitle( 'test' ) ) |
170 | ->setId( 'wpFilterForm' ) |
171 | ->setWrapperLegendMsg( 'abusefilter-test-legend' ) |
172 | ->setSubmitTextMsg( 'abusefilter-test-submit' ) |
173 | ->setSubmitCallback( [ $this, 'doTest' ] ) |
174 | ->showAlways(); |
175 | } |
176 | |
177 | /** |
178 | * Loads the revisions and checks the given syntax against them |
179 | * @param array $formData |
180 | * @param HTMLForm $form |
181 | * @return bool |
182 | */ |
183 | public function doTest( array $formData, HTMLForm $form ): bool { |
184 | // Quick syntax check. |
185 | $ruleChecker = $this->ruleCheckerFactory->newRuleChecker(); |
186 | |
187 | if ( !$ruleChecker->checkSyntax( $this->testPattern )->isValid() ) { |
188 | $form->addPreHtml( |
189 | Html::errorBox( $this->msg( 'abusefilter-test-syntaxerr' )->parse() ) |
190 | ); |
191 | return true; |
192 | } |
193 | |
194 | $dbr = $this->lbFactory->getReplicaDatabase(); |
195 | $rcQuery = RecentChange::getQueryInfo(); |
196 | $conds = []; |
197 | |
198 | // Normalise username |
199 | $userTitle = Title::newFromText( $formData['TestUser'], NS_USER ); |
200 | $testUser = $userTitle ? $userTitle->getText() : ''; |
201 | if ( $testUser !== '' ) { |
202 | $conds[$rcQuery['fields']['rc_user_text']] = $testUser; |
203 | } |
204 | |
205 | $startTS = strtotime( $formData['TestPeriodStart'] ); |
206 | if ( $startTS ) { |
207 | $conds[] = 'rc_timestamp>=' . $dbr->addQuotes( $dbr->timestamp( $startTS ) ); |
208 | } |
209 | $endTS = strtotime( $formData['TestPeriodEnd'] ); |
210 | if ( $endTS ) { |
211 | $conds[] = 'rc_timestamp<=' . $dbr->addQuotes( $dbr->timestamp( $endTS ) ); |
212 | } |
213 | if ( $formData['TestPage'] !== '' ) { |
214 | // The form validates the input for us, so this shouldn't throw. |
215 | $title = Title::newFromTextThrow( $formData['TestPage'] ); |
216 | $conds['rc_namespace'] = $title->getNamespace(); |
217 | $conds['rc_title'] = $title->getDBkey(); |
218 | } |
219 | |
220 | if ( $formData['ExcludeBots'] ) { |
221 | $conds['rc_bot'] = 0; |
222 | } |
223 | |
224 | $action = $formData['TestAction'] !== '0' ? $formData['TestAction'] : false; |
225 | $conds[] = $this->buildTestConditions( $dbr, $action ); |
226 | $conds = array_merge( $conds, $this->buildVisibilityConditions( $dbr, $this->getAuthority() ) ); |
227 | |
228 | $res = $dbr->select( |
229 | $rcQuery['tables'], |
230 | $rcQuery['fields'], |
231 | $conds, |
232 | __METHOD__, |
233 | [ 'LIMIT' => self::$mChangeLimit, 'ORDER BY' => 'rc_timestamp desc' ], |
234 | $rcQuery['joins'] |
235 | ); |
236 | |
237 | // Get our ChangesList |
238 | $changesList = new AbuseFilterChangesList( $this->getContext(), $this->testPattern ); |
239 | // Note, we're initializing some rows that will later be discarded. Hopefully this won't have any overhead. |
240 | $changesList->initChangesListRows( $res ); |
241 | $output = $changesList->beginRecentChangesList(); |
242 | |
243 | $counter = 1; |
244 | |
245 | $contextUser = $this->getUser(); |
246 | $ruleChecker->toggleConditionLimit( false ); |
247 | foreach ( $res as $row ) { |
248 | $rc = RecentChange::newFromRow( $row ); |
249 | if ( !$formData['ShowNegative'] ) { |
250 | $type = (int)$rc->getAttribute( 'rc_type' ); |
251 | $deletedValue = (int)$rc->getAttribute( 'rc_deleted' ); |
252 | if ( |
253 | ( |
254 | $type === RC_LOG && |
255 | !LogEventsList::userCanBitfield( |
256 | $deletedValue, |
257 | LogPage::SUPPRESSED_ACTION | LogPage::SUPPRESSED_USER, |
258 | $contextUser |
259 | ) |
260 | ) || ( |
261 | $type !== RC_LOG && |
262 | !RevisionRecord::userCanBitfield( $deletedValue, RevisionRecord::SUPPRESSED_ALL, $contextUser ) |
263 | ) |
264 | ) { |
265 | // If the RC is deleted, the user can't see it, and we're only showing matches, |
266 | // always skip this row. If ShowNegative is true, we can still show the row |
267 | // because we won't tell whether it matches the given filter. |
268 | continue; |
269 | } |
270 | } |
271 | |
272 | $varGenerator = $this->varGeneratorFactory->newRCGenerator( $rc, $contextUser ); |
273 | $vars = $varGenerator->getVars(); |
274 | |
275 | if ( !$vars ) { |
276 | continue; |
277 | } |
278 | |
279 | $ruleChecker->setVariables( $vars ); |
280 | $result = $ruleChecker->checkConditions( $this->testPattern )->getResult(); |
281 | |
282 | if ( $result || $formData['ShowNegative'] ) { |
283 | // Stash result in RC item |
284 | $rc->filterResult = $result; |
285 | $rc->counter = $counter++; |
286 | $output .= $changesList->recentChangesLine( $rc, false ); |
287 | } |
288 | } |
289 | |
290 | $output .= $changesList->endRecentChangesList(); |
291 | |
292 | $form->addPostHtml( $output ); |
293 | |
294 | return true; |
295 | } |
296 | |
297 | /** |
298 | * Loads parameters from request |
299 | */ |
300 | public function loadParameters() { |
301 | $request = $this->getRequest(); |
302 | |
303 | $this->testPattern = $request->getText( 'wpFilterRules' ); |
304 | |
305 | if ( $this->testPattern === '' |
306 | && count( $this->mParams ) > 1 |
307 | && is_numeric( $this->mParams[1] ) |
308 | ) { |
309 | $dbr = $this->lbFactory->getReplicaDatabase(); |
310 | $pattern = $dbr->selectField( 'abuse_filter', |
311 | 'af_pattern', |
312 | [ 'af_id' => intval( $this->mParams[1] ) ], |
313 | __METHOD__ |
314 | ); |
315 | if ( $pattern !== false ) { |
316 | $this->testPattern = $pattern; |
317 | } |
318 | } |
319 | } |
320 | } |