Index: trunk/extensions/GlobalBlocking/GlobalBlocking.class.php |
— | — | @@ -0,0 +1,265 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class GlobalBlocking { |
| 5 | + static function getUserPermissionsErrors( &$title, &$user, $action, &$result ) { |
| 6 | + global $wgApplyGlobalBlocks; |
| 7 | + if ($action == 'read' || !$wgApplyGlobalBlocks) { |
| 8 | + return true; |
| 9 | + } |
| 10 | + $ip = wfGetIp(); |
| 11 | + $blockError = self::getUserBlockErrors( $user, $ip ); |
| 12 | + if( !empty($blockError) ) { |
| 13 | + $result[] = $blockError; |
| 14 | + return false; |
| 15 | + } |
| 16 | + return true; |
| 17 | + } |
| 18 | + |
| 19 | + static function isBlockedGlobally( &$user, $ip, &$blocked ) { |
| 20 | + $blockError = self::getUserBlockErrors( $user, $ip ); |
| 21 | + if( $blockError ) { |
| 22 | + $blocked = true; |
| 23 | + return false; |
| 24 | + } |
| 25 | + return true; |
| 26 | + } |
| 27 | + |
| 28 | + static function getUserBlockErrors( $user, $ip ) { |
| 29 | + $dbr = GlobalBlocking::getGlobalBlockingSlave(); |
| 30 | + |
| 31 | + $hex_ip = IP::toHex( $ip ); |
| 32 | + $ip_pattern = substr( $hex_ip, 0, 4 ) . '%'; // Don't bother checking blocks out of this /16. |
| 33 | + |
| 34 | + $conds = array( |
| 35 | + 'gb_range_end>='.$dbr->addQuotes($hex_ip), // This block in the given range. |
| 36 | + 'gb_range_start<='.$dbr->addQuotes($hex_ip), |
| 37 | + 'gb_range_start like ' . $dbr->addQuotes( $ip_pattern ), |
| 38 | + 'gb_expiry>'.$dbr->addQuotes($dbr->timestamp(wfTimestampNow())) |
| 39 | + ); |
| 40 | + |
| 41 | + if ( !$user->isAnon() ) |
| 42 | + $conds['gb_anon_only'] = 0; |
| 43 | + |
| 44 | + // Get the block |
| 45 | + if ($block = $dbr->selectRow( 'globalblocks', '*', $conds, __METHOD__ )) { |
| 46 | + |
| 47 | + // Check for local whitelisting |
| 48 | + if (GlobalBlocking::getWhitelistInfo( $block->gb_id ) ) { |
| 49 | + // Block has been whitelisted. |
| 50 | + return array(); |
| 51 | + } |
| 52 | + |
| 53 | + if ( $user->isAllowed( 'ipblock-exempt' ) ) { |
| 54 | + // User is exempt from IP blocks. |
| 55 | + return array(); |
| 56 | + } |
| 57 | + |
| 58 | + $expiry = Block::formatExpiry( $block->gb_expiry ); |
| 59 | + |
| 60 | + wfLoadExtensionMessages( 'GlobalBlocking' ); |
| 61 | + |
| 62 | + $display_wiki = self::getWikiName( $block->gb_by_wiki ); |
| 63 | + $user_display = self::maybeLinkUserpage( $block->gb_by_wiki, $block->gb_by ); |
| 64 | + |
| 65 | + return array('globalblocking-blocked', $user_display, $display_wiki, $block->gb_reason, $expiry); |
| 66 | + } |
| 67 | + return array(); |
| 68 | + } |
| 69 | + |
| 70 | + static function getGlobalBlockingMaster() { |
| 71 | + global $wgGlobalBlockingDatabase; |
| 72 | + return wfGetDB( DB_MASTER, 'globalblocking', $wgGlobalBlockingDatabase ); |
| 73 | + } |
| 74 | + |
| 75 | + static function getGlobalBlockingSlave() { |
| 76 | + global $wgGlobalBlockingDatabase; |
| 77 | + return wfGetDB( DB_SLAVE, 'globalblocking', $wgGlobalBlockingDatabase ); |
| 78 | + } |
| 79 | + |
| 80 | + static function getGlobalBlockId( $ip ) { |
| 81 | + $dbr = GlobalBlocking::getGlobalBlockingSlave(); |
| 82 | + |
| 83 | + if (!($row = $dbr->selectRow( 'globalblocks', 'gb_id', array( 'gb_address' => $ip ), __METHOD__ ))) |
| 84 | + return 0; |
| 85 | + |
| 86 | + return $row->gb_id; |
| 87 | + } |
| 88 | + |
| 89 | + static function purgeExpired() { |
| 90 | + // This is expensive. It involves opening a connection to a new master, |
| 91 | + // and doing a write query. We should only do it when a connection to the master |
| 92 | + // is already open (currently, when a global block is made). |
| 93 | + $dbw = GlobalBlocking::getGlobalBlockingMaster(); |
| 94 | + |
| 95 | + // Stand-alone transaction. |
| 96 | + $dbw->begin(); |
| 97 | + $dbw->delete( 'globalblocks', array('gb_expiry<'.$dbw->addQuotes($dbw->timestamp())), __METHOD__ ); |
| 98 | + $dbw->commit(); |
| 99 | + |
| 100 | + // Purge the global_block_whitelist table. |
| 101 | + // We can't be perfect about this without an expensive check on the master |
| 102 | + // for every single global block. However, we can be clever about it and store |
| 103 | + // the expiry of global blocks in the global_block_whitelist table. |
| 104 | + // That way, most blocks will fall out of the table naturally when they expire. |
| 105 | + $dbw = wfGetDB( DB_MASTER ); |
| 106 | + $dbw->begin(); |
| 107 | + $dbw->delete( 'global_block_whitelist', array( 'gbw_expiry<'.$dbw->addQuotes($dbw->timestamp())), __METHOD__ ); |
| 108 | + $dbw->commit(); |
| 109 | + } |
| 110 | + |
| 111 | + static function getWhitelistInfo( $id = null, $address = null ) { |
| 112 | + $conds = array(); |
| 113 | + if ($id != null) { |
| 114 | + $conds = array( 'gbw_id' => $id ); |
| 115 | + } elseif ($address != null) { |
| 116 | + $conds = array( 'gbw_address' => $address ); |
| 117 | + } else { |
| 118 | + //WTF? |
| 119 | + throw new MWException( "Neither Block IP nor Block ID given for retrieving whitelist status" ); |
| 120 | + } |
| 121 | + |
| 122 | + $dbr = wfGetDB( DB_SLAVE ); |
| 123 | + $row = $dbr->selectRow( 'global_block_whitelist', array( 'gbw_by', 'gbw_reason' ), $conds, __METHOD__ ); |
| 124 | + |
| 125 | + if ($row == false) { |
| 126 | + // Not whitelisted. |
| 127 | + return false; |
| 128 | + } else { |
| 129 | + // Block has been whitelisted |
| 130 | + return array( 'user' => $row->gbw_by, 'reason' => $row->gbw_reason ); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + static function getWhitelistInfoByIP( $block_ip ) { |
| 135 | + return self::getWhitelistInfo( null, $block_ip ); |
| 136 | + } |
| 137 | + |
| 138 | + static function getWikiName( $wiki_id ) { |
| 139 | + if (class_exists('WikiMap')) { |
| 140 | + // We can give more info than just the wiki id! |
| 141 | + $wiki = WikiMap::getWiki( $wiki_id ); |
| 142 | + |
| 143 | + if ($wiki) { |
| 144 | + return $wiki->getDisplayName(); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + return $wiki_id; |
| 149 | + } |
| 150 | + |
| 151 | + static function maybeLinkUserpage( $wiki_id, $user ) { |
| 152 | + if (class_exists( 'WikiMap')) { |
| 153 | + $wiki = WikiMap::getWiki( $wiki_id ); |
| 154 | + |
| 155 | + if ($wiki) { |
| 156 | + return "[".$wiki->getUrl( "User:$user" )." $user]"; |
| 157 | + } |
| 158 | + } |
| 159 | + return $user; |
| 160 | + } |
| 161 | + |
| 162 | + static function insertBlock( $address, $reason, $expiry, $options = array() ) { |
| 163 | + global $wgUser; |
| 164 | + $errors = array(); |
| 165 | + |
| 166 | + ## Purge expired blocks. |
| 167 | + GlobalBlocking::purgeExpired(); |
| 168 | + |
| 169 | + ## Validate input |
| 170 | + $ip = IP::sanitizeIP( $address ); |
| 171 | + |
| 172 | + $anonOnly = in_array( 'anon-only', $options ); |
| 173 | + $modify = in_array( 'modify', $options ); |
| 174 | + |
| 175 | + if (!IP::isIPAddress($ip)) { |
| 176 | + // Invalid IP address. |
| 177 | + $errors[] = array( 'globalblocking-block-ipinvalid', $ip ); |
| 178 | + } |
| 179 | + |
| 180 | + if ( false === $expiry ) { |
| 181 | + $errors[] = array( 'globalblocking-block-expiryinvalid', $expiry ); |
| 182 | + } |
| 183 | + |
| 184 | + $existingBlock = GlobalBlocking::getGlobalBlockId($ip); |
| 185 | + if ( !$modify && $existingBlock ) { |
| 186 | + $errors[] = array( 'globalblocking-block-alreadyblocked', $ip ); |
| 187 | + } |
| 188 | + |
| 189 | + // Check for too-big ranges. |
| 190 | + list( $range_start, $range_end ) = IP::parseRange( $ip ); |
| 191 | + |
| 192 | + if (substr( $range_start, 0, 4 ) != substr( $range_end, 0, 4 )) { |
| 193 | + // Range crosses a /16 boundary. |
| 194 | + $errors[] = array( 'globalblocking-block-bigrange', $ip ); |
| 195 | + } |
| 196 | + |
| 197 | + // Normalise the range |
| 198 | + if ($range_start != $range_end) { |
| 199 | + $ip = Block::normaliseRange( $ip ); |
| 200 | + } |
| 201 | + |
| 202 | + if (count($errors)>0) |
| 203 | + return $errors; |
| 204 | + |
| 205 | + // We're a-ok. |
| 206 | + $dbw = GlobalBlocking::getGlobalBlockingMaster(); |
| 207 | + |
| 208 | + // Delete the old block, if applicable |
| 209 | + |
| 210 | + if ($modify) { |
| 211 | + $dbw->delete( 'globalblocks', array( 'gb_id' => $existingBlock ), __METHOD__ ); |
| 212 | + } |
| 213 | + |
| 214 | + $row = array(); |
| 215 | + $row['gb_address'] = $ip; |
| 216 | + $row['gb_by'] = $wgUser->getName(); |
| 217 | + $row['gb_by_wiki'] = wfWikiId(); |
| 218 | + $row['gb_reason'] = $reason; |
| 219 | + $row['gb_timestamp'] = $dbw->timestamp(wfTimestampNow()); |
| 220 | + $row['gb_anon_only'] = $anonOnly; |
| 221 | + $row['gb_expiry'] = Block::encodeExpiry($expiry, $dbw); |
| 222 | + list( $row['gb_range_start'], $row['gb_range_end'] ) = array( $range_start, $range_end ); |
| 223 | + |
| 224 | + $dbw->insert( 'globalblocks', $row, __METHOD__ ); |
| 225 | + |
| 226 | + return array(); |
| 227 | + } |
| 228 | + |
| 229 | + static function block( $address, $reason, $expiry, $options = array() ) { |
| 230 | + global $wgContLang; |
| 231 | + |
| 232 | + $expiry = Block::parseExpiryInput( $expiry ); |
| 233 | + $errors = self::insertBlock( $address, $reason, $expiry, $options ); |
| 234 | + |
| 235 | + if ( count($errors) > 0 ) |
| 236 | + return $errors; |
| 237 | + |
| 238 | + $anonOnly = in_array( 'anon-only', $options ); |
| 239 | + $modify = in_array( 'modify', $options ); |
| 240 | + |
| 241 | + // Log it. |
| 242 | + $logAction = $modify ? 'modify' : 'gblock2'; |
| 243 | + $flags = array(); |
| 244 | + |
| 245 | + if ($anonOnly) |
| 246 | + $flags[] = wfMsgForContent( 'globalblocking-list-anononly' ); |
| 247 | + |
| 248 | + if ( $expiry != 'infinity' ) { |
| 249 | + $displayExpiry = $wgContLang->timeanddate( $expiry ); |
| 250 | + $flags[] = wfMsgForContent( 'globalblocking-logentry-expiry', $displayExpiry ); |
| 251 | + } else { |
| 252 | + $flags[] = wfMsgForContent( 'globalblocking-logentry-noexpiry' ); |
| 253 | + } |
| 254 | + |
| 255 | + $info = implode( ', ', $flags ); |
| 256 | + |
| 257 | + $page = new LogPage( 'gblblock' ); |
| 258 | + $page->addEntry( $logAction, |
| 259 | + Title::makeTitleSafe( NS_USER, $address ), |
| 260 | + $reason, |
| 261 | + array($info, $address) |
| 262 | + ); |
| 263 | + |
| 264 | + return array(); |
| 265 | + } |
| 266 | +} |
\ No newline at end of file |