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 24use MediaWiki\Block\BlockRestrictionStore; 25use MediaWiki\Block\BlockUtils; 26use MediaWiki\Block\DatabaseBlock; 27use MediaWiki\Cache\LinkBatchFactory; 28use Wikimedia\IPUtils; 29use Wikimedia\Rdbms\IDatabase; 30use Wikimedia\Rdbms\ILoadBalancer; 31 32/** 33 * A special page that lists existing blocks 34 * 35 * @ingroup SpecialPage 36 */ 37class SpecialBlockList extends SpecialPage { 38 protected $target; 39 40 protected $options; 41 42 protected $blockType; 43 44 /** @var LinkBatchFactory */ 45 private $linkBatchFactory; 46 47 /** @var BlockRestrictionStore */ 48 private $blockRestrictionStore; 49 50 /** @var ILoadBalancer */ 51 private $loadBalancer; 52 53 /** @var ActorMigration */ 54 private $actorMigration; 55 56 /** @var CommentStore */ 57 private $commentStore; 58 59 /** @var BlockUtils */ 60 private $blockUtils; 61 62 public function __construct( 63 LinkBatchFactory $linkBatchFactory, 64 BlockRestrictionStore $blockRestrictionStore, 65 ILoadBalancer $loadBalancer, 66 ActorMigration $actorMigration, 67 CommentStore $commentStore, 68 BlockUtils $blockUtils 69 ) { 70 parent::__construct( 'BlockList' ); 71 72 $this->linkBatchFactory = $linkBatchFactory; 73 $this->blockRestrictionStore = $blockRestrictionStore; 74 $this->loadBalancer = $loadBalancer; 75 $this->actorMigration = $actorMigration; 76 $this->commentStore = $commentStore; 77 $this->blockUtils = $blockUtils; 78 } 79 80 /** 81 * @param string|null $par Title fragment 82 */ 83 public function execute( $par ) { 84 $this->setHeaders(); 85 $this->outputHeader(); 86 $this->addHelpLink( 'Help:Blocking_users' ); 87 $out = $this->getOutput(); 88 $out->setPageTitle( $this->msg( 'ipblocklist' ) ); 89 $out->addModuleStyles( [ 'mediawiki.special' ] ); 90 91 $request = $this->getRequest(); 92 $par = $request->getVal( 'ip', $par ); 93 $this->target = trim( $request->getVal( 'wpTarget', $par ) ); 94 95 $this->options = $request->getArray( 'wpOptions', [] ); 96 $this->blockType = $request->getVal( 'blockType' ); 97 98 $action = $request->getText( 'action' ); 99 100 if ( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) { 101 # B/C @since 1.18: Unblock interface is now at Special:Unblock 102 $title = $this->getSpecialPageFactory()->getTitleForAlias( 'Unblock/' . $this->target ); 103 $out->redirect( $title->getFullURL() ); 104 105 return; 106 } 107 108 # setup BlockListPager here to get the actual default Limit 109 $pager = $this->getBlockListPager(); 110 111 # Just show the block list 112 $fields = [ 113 'Target' => [ 114 'type' => 'user', 115 'label-message' => 'ipaddressorusername', 116 'tabindex' => '1', 117 'size' => '45', 118 'default' => $this->target, 119 ], 120 'Options' => [ 121 'type' => 'multiselect', 122 'options-messages' => [ 123 'blocklist-tempblocks' => 'tempblocks', 124 'blocklist-indefblocks' => 'indefblocks', 125 'blocklist-userblocks' => 'userblocks', 126 'blocklist-addressblocks' => 'addressblocks', 127 'blocklist-rangeblocks' => 'rangeblocks', 128 ], 129 'flatlist' => true, 130 ], 131 ]; 132 133 $fields['BlockType'] = [ 134 'type' => 'select', 135 'label-message' => 'blocklist-type', 136 'options' => [ 137 $this->msg( 'blocklist-type-opt-all' )->escaped() => '', 138 $this->msg( 'blocklist-type-opt-sitewide' )->escaped() => 'sitewide', 139 $this->msg( 'blocklist-type-opt-partial' )->escaped() => 'partial', 140 ], 141 'name' => 'blockType', 142 'cssclass' => 'mw-field-block-type', 143 ]; 144 145 $fields['Limit'] = [ 146 'type' => 'limitselect', 147 'label-message' => 'table_pager_limit_label', 148 'options' => $pager->getLimitSelectList(), 149 'name' => 'limit', 150 'default' => $pager->getLimit(), 151 'cssclass' => 'mw-field-limit mw-has-field-block-type', 152 ]; 153 154 $context = new DerivativeContext( $this->getContext() ); 155 $context->setTitle( $this->getPageTitle() ); // Remove subpage 156 $form = HTMLForm::factory( 'ooui', $fields, $context ); 157 $form 158 ->setMethod( 'get' ) 159 ->setFormIdentifier( 'blocklist' ) 160 ->setWrapperLegendMsg( 'ipblocklist-legend' ) 161 ->setSubmitTextMsg( 'ipblocklist-submit' ) 162 ->prepareForm() 163 ->displayForm( false ); 164 165 $this->showList( $pager ); 166 } 167 168 /** 169 * Setup a new BlockListPager instance. 170 * @return BlockListPager 171 */ 172 protected function getBlockListPager() { 173 $conds = []; 174 $db = $this->getDB(); 175 # Is the user allowed to see hidden blocks? 176 if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) { 177 $conds['ipb_deleted'] = 0; 178 } 179 180 if ( $this->target !== '' ) { 181 list( $target, $type ) = $this->blockUtils->parseBlockTarget( $this->target ); 182 183 switch ( $type ) { 184 case DatabaseBlock::TYPE_ID: 185 case DatabaseBlock::TYPE_AUTO: 186 $conds['ipb_id'] = $target; 187 break; 188 189 case DatabaseBlock::TYPE_IP: 190 case DatabaseBlock::TYPE_RANGE: 191 list( $start, $end ) = IPUtils::parseRange( $target ); 192 $conds[] = $db->makeList( 193 [ 194 'ipb_address' => $target, 195 DatabaseBlock::getRangeCond( $start, $end ) 196 ], 197 LIST_OR 198 ); 199 $conds['ipb_auto'] = 0; 200 break; 201 202 case DatabaseBlock::TYPE_USER: 203 $conds['ipb_address'] = $target->getName(); 204 $conds['ipb_auto'] = 0; 205 break; 206 } 207 } 208 209 # Apply filters 210 if ( in_array( 'userblocks', $this->options ) ) { 211 $conds['ipb_user'] = 0; 212 } 213 if ( in_array( 'addressblocks', $this->options ) ) { 214 $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start"; 215 } 216 if ( in_array( 'rangeblocks', $this->options ) ) { 217 $conds[] = "ipb_range_end = ipb_range_start"; 218 } 219 220 $hideTemp = in_array( 'tempblocks', $this->options ); 221 $hideIndef = in_array( 'indefblocks', $this->options ); 222 if ( $hideTemp && $hideIndef ) { 223 // If both types are hidden, ensure query doesn't produce any results 224 $conds[] = '1=0'; 225 } elseif ( $hideTemp ) { 226 $conds['ipb_expiry'] = $db->getInfinity(); 227 } elseif ( $hideIndef ) { 228 $conds[] = "ipb_expiry != " . $db->addQuotes( $db->getInfinity() ); 229 } 230 231 if ( $this->blockType === 'sitewide' ) { 232 $conds['ipb_sitewide'] = 1; 233 } elseif ( $this->blockType === 'partial' ) { 234 $conds['ipb_sitewide'] = 0; 235 } 236 237 return new BlockListPager( 238 $this, 239 $conds, 240 $this->linkBatchFactory, 241 $this->blockRestrictionStore, 242 $this->loadBalancer, 243 $this->getSpecialPageFactory(), 244 $this->actorMigration, 245 $this->commentStore, 246 $this->blockUtils 247 ); 248 } 249 250 /** 251 * Show the list of blocked accounts matching the actual filter. 252 * @param BlockListPager $pager The BlockListPager instance for this page 253 */ 254 protected function showList( BlockListPager $pager ) { 255 $out = $this->getOutput(); 256 257 # Check for other blocks, i.e. global/tor blocks 258 $otherBlockLink = []; 259 $this->getHookRunner()->onOtherBlockLogLink( $otherBlockLink, $this->target ); 260 261 # Show additional header for the local block only when other blocks exists. 262 # Not necessary in a standard installation without such extensions enabled 263 if ( count( $otherBlockLink ) ) { 264 $out->addHTML( 265 Html::element( 'h2', [], $this->msg( 'ipblocklist-localblock' )->text() ) . "\n" 266 ); 267 } 268 269 if ( $pager->getNumRows() ) { 270 $out->addParserOutputContent( $pager->getFullOutput() ); 271 } elseif ( $this->target ) { 272 $out->addWikiMsg( 'ipblocklist-no-results' ); 273 } else { 274 $out->addWikiMsg( 'ipblocklist-empty' ); 275 } 276 277 if ( count( $otherBlockLink ) ) { 278 $out->addHTML( 279 Html::rawElement( 280 'h2', 281 [], 282 $this->msg( 'ipblocklist-otherblocks', count( $otherBlockLink ) )->parse() 283 ) . "\n" 284 ); 285 $list = ''; 286 foreach ( $otherBlockLink as $link ) { 287 $list .= Html::rawElement( 'li', [], $link ) . "\n"; 288 } 289 $out->addHTML( Html::rawElement( 290 'ul', 291 [ 'class' => 'mw-ipblocklist-otherblocks' ], 292 $list 293 ) . "\n" ); 294 } 295 } 296 297 protected function getGroupName() { 298 return 'users'; 299 } 300 301 /** 302 * Return a IDatabase object for reading 303 * 304 * @return IDatabase 305 */ 306 protected function getDB() { 307 return $this->loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA ); 308 } 309} 310