Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 189 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
SpecialBlockList | |
0.00% |
0 / 188 |
|
0.00% |
0 / 6 |
1640 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 67 |
|
0.00% |
0 / 1 |
20 | |||
getBlockListPager | |
0.00% |
0 / 81 |
|
0.00% |
0 / 1 |
756 | |||
showList | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
42 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDB | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Implements Special:BlockList |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup SpecialPage |
22 | */ |
23 | |
24 | namespace MediaWiki\Specials; |
25 | |
26 | use MediaWiki\Block\BlockActionInfo; |
27 | use MediaWiki\Block\BlockRestrictionStore; |
28 | use MediaWiki\Block\BlockUtils; |
29 | use MediaWiki\Block\DatabaseBlock; |
30 | use MediaWiki\Block\DatabaseBlockStore; |
31 | use MediaWiki\Block\HideUserUtils; |
32 | use MediaWiki\Cache\LinkBatchFactory; |
33 | use MediaWiki\CommentFormatter\RowCommentFormatter; |
34 | use MediaWiki\CommentStore\CommentStore; |
35 | use MediaWiki\Config\ConfigException; |
36 | use MediaWiki\Html\Html; |
37 | use MediaWiki\HTMLForm\HTMLForm; |
38 | use MediaWiki\MainConfigNames; |
39 | use MediaWiki\Pager\BlockListPager; |
40 | use MediaWiki\SpecialPage\SpecialPage; |
41 | use Wikimedia\IPUtils; |
42 | use Wikimedia\Rdbms\IConnectionProvider; |
43 | use Wikimedia\Rdbms\IReadableDatabase; |
44 | |
45 | /** |
46 | * A special page that lists existing blocks |
47 | * |
48 | * @ingroup SpecialPage |
49 | */ |
50 | class SpecialBlockList extends SpecialPage { |
51 | protected $target; |
52 | |
53 | protected $options; |
54 | |
55 | protected $blockType; |
56 | |
57 | private LinkBatchFactory $linkBatchFactory; |
58 | private DatabaseBlockStore $blockStore; |
59 | private BlockRestrictionStore $blockRestrictionStore; |
60 | private IConnectionProvider $dbProvider; |
61 | private CommentStore $commentStore; |
62 | private BlockUtils $blockUtils; |
63 | private HideUserUtils $hideUserUtils; |
64 | private BlockActionInfo $blockActionInfo; |
65 | private RowCommentFormatter $rowCommentFormatter; |
66 | |
67 | public function __construct( |
68 | LinkBatchFactory $linkBatchFactory, |
69 | DatabaseBlockStore $blockStore, |
70 | BlockRestrictionStore $blockRestrictionStore, |
71 | IConnectionProvider $dbProvider, |
72 | CommentStore $commentStore, |
73 | BlockUtils $blockUtils, |
74 | HideUserUtils $hideUserUtils, |
75 | BlockActionInfo $blockActionInfo, |
76 | RowCommentFormatter $rowCommentFormatter |
77 | ) { |
78 | parent::__construct( 'BlockList' ); |
79 | |
80 | $this->linkBatchFactory = $linkBatchFactory; |
81 | $this->blockStore = $blockStore; |
82 | $this->blockRestrictionStore = $blockRestrictionStore; |
83 | $this->dbProvider = $dbProvider; |
84 | $this->commentStore = $commentStore; |
85 | $this->blockUtils = $blockUtils; |
86 | $this->hideUserUtils = $hideUserUtils; |
87 | $this->blockActionInfo = $blockActionInfo; |
88 | $this->rowCommentFormatter = $rowCommentFormatter; |
89 | } |
90 | |
91 | /** |
92 | * @param string|null $par Title fragment |
93 | */ |
94 | public function execute( $par ) { |
95 | $this->setHeaders(); |
96 | $this->outputHeader(); |
97 | $this->addHelpLink( 'Help:Blocking_users' ); |
98 | $out = $this->getOutput(); |
99 | $out->setPageTitleMsg( $this->msg( 'ipblocklist' ) ); |
100 | $out->addModuleStyles( [ 'mediawiki.special' ] ); |
101 | |
102 | $request = $this->getRequest(); |
103 | $par = $request->getVal( 'ip', $par ?? '' ); |
104 | $this->target = trim( $request->getVal( 'wpTarget', $par ) ); |
105 | |
106 | $this->options = $request->getArray( 'wpOptions', [] ); |
107 | $this->blockType = $request->getVal( 'blockType' ); |
108 | |
109 | $action = $request->getText( 'action' ); |
110 | |
111 | if ( $action == 'unblock' || ( $action == 'submit' && $request->wasPosted() ) ) { |
112 | // B/C @since 1.18: Unblock interface is now at Special:Unblock |
113 | $title = $this->getSpecialPageFactory()->getTitleForAlias( 'Unblock/' . $this->target ); |
114 | $out->redirect( $title->getFullURL() ); |
115 | |
116 | return; |
117 | } |
118 | |
119 | // Setup BlockListPager here to get the actual default Limit |
120 | $pager = $this->getBlockListPager(); |
121 | |
122 | // Just show the block list |
123 | $fields = [ |
124 | 'Target' => [ |
125 | 'type' => 'user', |
126 | 'label-message' => 'ipaddressorusername', |
127 | 'tabindex' => '1', |
128 | 'size' => '45', |
129 | 'default' => $this->target, |
130 | ], |
131 | 'Options' => [ |
132 | 'type' => 'multiselect', |
133 | 'options-messages' => [ |
134 | 'blocklist-tempblocks' => 'tempblocks', |
135 | 'blocklist-indefblocks' => 'indefblocks', |
136 | 'blocklist-autoblocks' => 'autoblocks', |
137 | 'blocklist-userblocks' => 'userblocks', |
138 | 'blocklist-addressblocks' => 'addressblocks', |
139 | 'blocklist-rangeblocks' => 'rangeblocks', |
140 | ], |
141 | 'flatlist' => true, |
142 | ], |
143 | ]; |
144 | |
145 | $fields['BlockType'] = [ |
146 | 'type' => 'select', |
147 | 'label-message' => 'blocklist-type', |
148 | 'options' => [ |
149 | $this->msg( 'blocklist-type-opt-all' )->escaped() => '', |
150 | $this->msg( 'blocklist-type-opt-sitewide' )->escaped() => 'sitewide', |
151 | $this->msg( 'blocklist-type-opt-partial' )->escaped() => 'partial', |
152 | ], |
153 | 'name' => 'blockType', |
154 | 'cssclass' => 'mw-field-block-type', |
155 | ]; |
156 | |
157 | $fields['Limit'] = [ |
158 | 'type' => 'limitselect', |
159 | 'label-message' => 'table_pager_limit_label', |
160 | 'options' => $pager->getLimitSelectList(), |
161 | 'name' => 'limit', |
162 | 'default' => $pager->getLimit(), |
163 | 'cssclass' => 'mw-field-limit mw-has-field-block-type', |
164 | ]; |
165 | |
166 | $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() ); |
167 | $form |
168 | ->setMethod( 'get' ) |
169 | ->setTitle( $this->getPageTitle() ) // Remove subpage |
170 | ->setFormIdentifier( 'blocklist' ) |
171 | ->setWrapperLegendMsg( 'ipblocklist-legend' ) |
172 | ->setSubmitTextMsg( 'ipblocklist-submit' ) |
173 | ->prepareForm() |
174 | ->displayForm( false ); |
175 | |
176 | $this->showList( $pager ); |
177 | } |
178 | |
179 | /** |
180 | * Setup a new BlockListPager instance. |
181 | * @return BlockListPager |
182 | */ |
183 | protected function getBlockListPager() { |
184 | $readStage = $this->getConfig() |
185 | ->get( MainConfigNames::BlockTargetMigrationStage ) & SCHEMA_COMPAT_READ_MASK; |
186 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
187 | $bl_deleted = 'ipb_deleted'; |
188 | $bl_id = 'ipb_id'; |
189 | $bt_auto = 'ipb_auto'; |
190 | $bt_user = 'ipb_user'; |
191 | $bl_expiry = 'ipb_expiry'; |
192 | $bl_sitewide = 'ipb_sitewide'; |
193 | } elseif ( $readStage === SCHEMA_COMPAT_READ_NEW ) { |
194 | $bl_deleted = 'bl_deleted'; |
195 | $bl_id = 'bl_id'; |
196 | $bt_auto = 'bt_auto'; |
197 | $bt_user = 'bt_user'; |
198 | $bl_expiry = 'bl_expiry'; |
199 | $bl_sitewide = 'bl_sitewide'; |
200 | } else { |
201 | throw new ConfigException( |
202 | '$wgBlockTargetMigrationStage has an invalid read stage' ); |
203 | } |
204 | |
205 | $conds = []; |
206 | $db = $this->getDB(); |
207 | |
208 | if ( $this->target !== '' ) { |
209 | [ $target, $type ] = $this->blockUtils->parseBlockTarget( $this->target ); |
210 | |
211 | switch ( $type ) { |
212 | case DatabaseBlock::TYPE_ID: |
213 | case DatabaseBlock::TYPE_AUTO: |
214 | $conds[$bl_id] = $target; |
215 | break; |
216 | |
217 | case DatabaseBlock::TYPE_IP: |
218 | case DatabaseBlock::TYPE_RANGE: |
219 | [ $start, $end ] = IPUtils::parseRange( $target ); |
220 | $conds[] = $this->blockStore->getRangeCond( $start, $end, |
221 | DatabaseBlockStore::SCHEMA_CURRENT ); |
222 | $conds[$bt_auto] = 0; |
223 | break; |
224 | |
225 | case DatabaseBlock::TYPE_USER: |
226 | if ( $target->getId() ) { |
227 | $conds[$bt_user] = $target->getId(); |
228 | $conds[$bt_auto] = 0; |
229 | } else { |
230 | // No such user |
231 | $conds[] = '1=0'; |
232 | } |
233 | break; |
234 | } |
235 | } |
236 | |
237 | // Apply filters |
238 | if ( in_array( 'userblocks', $this->options ) ) { |
239 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
240 | $conds['ipb_user'] = 0; |
241 | } else { |
242 | $conds['bt_user'] = null; |
243 | } |
244 | } |
245 | if ( in_array( 'autoblocks', $this->options ) ) { |
246 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
247 | // ipb_parent_block_id = 0 because of T282890 |
248 | $conds['ipb_parent_block_id'] = [ null, 0 ]; |
249 | } else { |
250 | $conds['bl_parent_block_id'] = null; |
251 | } |
252 | } |
253 | if ( in_array( 'addressblocks', $this->options ) |
254 | && in_array( 'rangeblocks', $this->options ) |
255 | ) { |
256 | // Simpler conditions for only user blocks (T360864) |
257 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
258 | $conds[] = "ipb_user != 0"; |
259 | } else { |
260 | $conds[] = "bt_user IS NOT NULL"; |
261 | } |
262 | } elseif ( in_array( 'addressblocks', $this->options ) ) { |
263 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
264 | $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; |
265 | } else { |
266 | $conds[] = "bt_user IS NOT NULL OR bt_range_start IS NOT NULL"; |
267 | } |
268 | } elseif ( in_array( 'rangeblocks', $this->options ) ) { |
269 | if ( $readStage === SCHEMA_COMPAT_READ_OLD ) { |
270 | $conds[] = "ipb_range_end = ipb_range_start"; |
271 | } else { |
272 | $conds['bt_range_start'] = null; |
273 | } |
274 | } |
275 | |
276 | $hideTemp = in_array( 'tempblocks', $this->options ); |
277 | $hideIndef = in_array( 'indefblocks', $this->options ); |
278 | if ( $hideTemp && $hideIndef ) { |
279 | // If both types are hidden, ensure query doesn't produce any results |
280 | $conds[] = '1=0'; |
281 | } elseif ( $hideTemp ) { |
282 | $conds[$bl_expiry] = $db->getInfinity(); |
283 | } elseif ( $hideIndef ) { |
284 | $conds[] = $db->expr( $bl_expiry, '!=', $db->getInfinity() ); |
285 | } |
286 | |
287 | if ( $this->blockType === 'sitewide' ) { |
288 | $conds[$bl_sitewide] = 1; |
289 | } elseif ( $this->blockType === 'partial' ) { |
290 | $conds[$bl_sitewide] = 0; |
291 | } |
292 | |
293 | return new BlockListPager( |
294 | $this->getContext(), |
295 | $this->blockActionInfo, |
296 | $this->blockRestrictionStore, |
297 | $this->blockUtils, |
298 | $this->hideUserUtils, |
299 | $this->commentStore, |
300 | $this->linkBatchFactory, |
301 | $this->getLinkRenderer(), |
302 | $this->dbProvider, |
303 | $this->rowCommentFormatter, |
304 | $this->getSpecialPageFactory(), |
305 | $conds |
306 | ); |
307 | } |
308 | |
309 | /** |
310 | * Show the list of blocked accounts matching the actual filter. |
311 | * @param BlockListPager $pager The BlockListPager instance for this page |
312 | */ |
313 | protected function showList( BlockListPager $pager ) { |
314 | $out = $this->getOutput(); |
315 | |
316 | // Check for other blocks, i.e. global/tor blocks |
317 | $otherBlockLink = []; |
318 | $this->getHookRunner()->onOtherBlockLogLink( $otherBlockLink, $this->target ); |
319 | |
320 | // Show additional header for the local block only when other blocks exists. |
321 | // Not necessary in a standard installation without such extensions enabled |
322 | if ( count( $otherBlockLink ) ) { |
323 | $out->addHTML( |
324 | Html::element( 'h2', [], $this->msg( 'ipblocklist-localblock' )->text() ) . "\n" |
325 | ); |
326 | } |
327 | |
328 | if ( $pager->getNumRows() ) { |
329 | $out->addParserOutputContent( $pager->getFullOutput() ); |
330 | } elseif ( $this->target ) { |
331 | $out->addWikiMsg( 'ipblocklist-no-results' ); |
332 | } else { |
333 | $out->addWikiMsg( 'ipblocklist-empty' ); |
334 | } |
335 | |
336 | if ( count( $otherBlockLink ) ) { |
337 | $out->addHTML( |
338 | Html::rawElement( |
339 | 'h2', |
340 | [], |
341 | $this->msg( 'ipblocklist-otherblocks', count( $otherBlockLink ) )->parse() |
342 | ) . "\n" |
343 | ); |
344 | $list = ''; |
345 | foreach ( $otherBlockLink as $link ) { |
346 | $list .= Html::rawElement( 'li', [], $link ) . "\n"; |
347 | } |
348 | $out->addHTML( Html::rawElement( |
349 | 'ul', |
350 | [ 'class' => 'mw-ipblocklist-otherblocks' ], |
351 | $list |
352 | ) . "\n" ); |
353 | } |
354 | } |
355 | |
356 | protected function getGroupName() { |
357 | return 'users'; |
358 | } |
359 | |
360 | /** |
361 | * Return a IDatabase object for reading |
362 | * |
363 | * @return IReadableDatabase |
364 | */ |
365 | protected function getDB() { |
366 | return $this->dbProvider->getReplicaDatabase(); |
367 | } |
368 | } |
369 | |
370 | /** @deprecated class alias since 1.41 */ |
371 | class_alias( SpecialBlockList::class, 'SpecialBlockList' ); |