Block.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. <?php
  2. /**
  3. * @file
  4. * Blocks and bans object
  5. */
  6. /**
  7. * The block class
  8. * All the functions in this class assume the object is either explicitly
  9. * loaded or filled. It is not load-on-demand. There are no accessors.
  10. *
  11. * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
  12. *
  13. * @todo This could be used everywhere, but it isn't.
  14. */
  15. class Block {
  16. /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
  17. $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock, $mHideName,
  18. $mBlockEmail, $mByName, $mAngryAutoblock, $mAllowUsertalk;
  19. /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster;
  20. const EB_KEEP_EXPIRED = 1;
  21. const EB_FOR_UPDATE = 2;
  22. const EB_RANGE_ONLY = 4;
  23. function __construct( $address = '', $user = 0, $by = 0, $reason = '',
  24. $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
  25. $hideName = 0, $blockEmail = 0, $allowUsertalk = 0 )
  26. {
  27. $this->mId = 0;
  28. # Expand valid IPv6 addresses
  29. $address = IP::sanitizeIP( $address );
  30. $this->mAddress = $address;
  31. $this->mUser = $user;
  32. $this->mBy = $by;
  33. $this->mReason = $reason;
  34. $this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
  35. $this->mAuto = $auto;
  36. $this->mAnonOnly = $anonOnly;
  37. $this->mCreateAccount = $createAccount;
  38. $this->mExpiry = self::decodeExpiry( $expiry );
  39. $this->mEnableAutoblock = $enableAutoblock;
  40. $this->mHideName = $hideName;
  41. $this->mBlockEmail = $blockEmail;
  42. $this->mAllowUsertalk = $allowUsertalk;
  43. $this->mForUpdate = false;
  44. $this->mFromMaster = false;
  45. $this->mByName = false;
  46. $this->mAngryAutoblock = false;
  47. $this->initialiseRange();
  48. }
  49. /**
  50. * Load a block from the database, using either the IP address or
  51. * user ID. Tries the user ID first, and if that doesn't work, tries
  52. * the address.
  53. *
  54. * @param $address String: IP address of user/anon
  55. * @param $user Integer: user id of user
  56. * @param $killExpired Boolean: delete expired blocks on load
  57. * @return Block Object
  58. */
  59. public static function newFromDB( $address, $user = 0, $killExpired = true ) {
  60. $block = new Block;
  61. $block->load( $address, $user, $killExpired );
  62. if ( $block->isValid() ) {
  63. return $block;
  64. } else {
  65. return null;
  66. }
  67. }
  68. /**
  69. * Load a blocked user from their block id.
  70. *
  71. * @param $id Integer: Block id to search for
  72. * @return Block object
  73. */
  74. public static function newFromID( $id ) {
  75. $dbr = wfGetDB( DB_SLAVE );
  76. $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
  77. array( 'ipb_id' => $id ), __METHOD__ ) );
  78. $block = new Block;
  79. if ( $block->loadFromResult( $res ) ) {
  80. return $block;
  81. } else {
  82. return null;
  83. }
  84. }
  85. /**
  86. * Check if two blocks are effectively equal
  87. *
  88. * @return Boolean
  89. */
  90. public function equals( Block $block ) {
  91. return (
  92. $this->mAddress == $block->mAddress
  93. && $this->mUser == $block->mUser
  94. && $this->mAuto == $block->mAuto
  95. && $this->mAnonOnly == $block->mAnonOnly
  96. && $this->mCreateAccount == $block->mCreateAccount
  97. && $this->mExpiry == $block->mExpiry
  98. && $this->mEnableAutoblock == $block->mEnableAutoblock
  99. && $this->mHideName == $block->mHideName
  100. && $this->mBlockEmail == $block->mBlockEmail
  101. && $this->mAllowUsertalk == $block->mAllowUsertalk
  102. && $this->mReason == $block->mReason
  103. );
  104. }
  105. /**
  106. * Clear all member variables in the current object. Does not clear
  107. * the block from the DB.
  108. */
  109. public function clear() {
  110. $this->mAddress = $this->mReason = $this->mTimestamp = '';
  111. $this->mId = $this->mAnonOnly = $this->mCreateAccount =
  112. $this->mEnableAutoblock = $this->mAuto = $this->mUser =
  113. $this->mBy = $this->mHideName = $this->mBlockEmail = $this->mAllowUsertalk = 0;
  114. $this->mByName = false;
  115. }
  116. /**
  117. * Get the DB object and set the reference parameter to the select options.
  118. * The options array will contain FOR UPDATE if appropriate.
  119. *
  120. * @param $options Array
  121. * @return Database
  122. */
  123. protected function &getDBOptions( &$options ) {
  124. global $wgAntiLockFlags;
  125. if ( $this->mForUpdate || $this->mFromMaster ) {
  126. $db = wfGetDB( DB_MASTER );
  127. if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
  128. $options = array();
  129. } else {
  130. $options = array( 'FOR UPDATE' );
  131. }
  132. } else {
  133. $db = wfGetDB( DB_SLAVE );
  134. $options = array();
  135. }
  136. return $db;
  137. }
  138. /**
  139. * Get a block from the DB, with either the given address or the given username
  140. *
  141. * @param $address string The IP address of the user, or blank to skip IP blocks
  142. * @param $user int The user ID, or zero for anonymous users
  143. * @param $killExpired bool Whether to delete expired rows while loading
  144. * @return Boolean: the user is blocked from editing
  145. *
  146. */
  147. public function load( $address = '', $user = 0, $killExpired = true ) {
  148. wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
  149. $options = array();
  150. $db =& $this->getDBOptions( $options );
  151. if ( 0 == $user && $address == '' ) {
  152. # Invalid user specification, not blocked
  153. $this->clear();
  154. return false;
  155. }
  156. # Try user block
  157. if ( $user ) {
  158. $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
  159. __METHOD__, $options ) );
  160. if ( $this->loadFromResult( $res, $killExpired ) ) {
  161. return true;
  162. }
  163. }
  164. # Try IP block
  165. # TODO: improve performance by merging this query with the autoblock one
  166. # Slightly tricky while handling killExpired as well
  167. if ( $address ) {
  168. $conds = array( 'ipb_address' => $address, 'ipb_auto' => 0 );
  169. $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
  170. if ( $this->loadFromResult( $res, $killExpired ) ) {
  171. if ( $user && $this->mAnonOnly ) {
  172. # Block is marked anon-only
  173. # Whitelist this IP address against autoblocks and range blocks
  174. # (but not account creation blocks -- bug 13611)
  175. if( !$this->mCreateAccount ) {
  176. $this->clear();
  177. }
  178. return false;
  179. } else {
  180. return true;
  181. }
  182. }
  183. }
  184. # Try range block
  185. if ( $this->loadRange( $address, $killExpired, $user ) ) {
  186. if ( $user && $this->mAnonOnly ) {
  187. # Respect account creation blocks on logged-in users -- bug 13611
  188. if( !$this->mCreateAccount ) {
  189. $this->clear();
  190. }
  191. return false;
  192. } else {
  193. return true;
  194. }
  195. }
  196. # Try autoblock
  197. if ( $address ) {
  198. $conds = array( 'ipb_address' => $address, 'ipb_auto' => 1 );
  199. if ( $user ) {
  200. $conds['ipb_anon_only'] = 0;
  201. }
  202. $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
  203. if ( $this->loadFromResult( $res, $killExpired ) ) {
  204. return true;
  205. }
  206. }
  207. # Give up
  208. $this->clear();
  209. return false;
  210. }
  211. /**
  212. * Fill in member variables from a result wrapper
  213. *
  214. * @param $res ResultWrapper: row from the ipblocks table
  215. * @param $killExpired Boolean: whether to delete expired rows while loading
  216. * @return Boolean
  217. */
  218. protected function loadFromResult( ResultWrapper $res, $killExpired = true ) {
  219. $ret = false;
  220. if ( 0 != $res->numRows() ) {
  221. # Get first block
  222. $row = $res->fetchObject();
  223. $this->initFromRow( $row );
  224. if ( $killExpired ) {
  225. # If requested, delete expired rows
  226. do {
  227. $killed = $this->deleteIfExpired();
  228. if ( $killed ) {
  229. $row = $res->fetchObject();
  230. if ( $row ) {
  231. $this->initFromRow( $row );
  232. }
  233. }
  234. } while ( $killed && $row );
  235. # If there were any left after the killing finished, return true
  236. if ( $row ) {
  237. $ret = true;
  238. }
  239. } else {
  240. $ret = true;
  241. }
  242. }
  243. $res->free();
  244. return $ret;
  245. }
  246. /**
  247. * Search the database for any range blocks matching the given address, and
  248. * load the row if one is found.
  249. *
  250. * @param $address String: IP address range
  251. * @param $killExpired Boolean: whether to delete expired rows while loading
  252. * @param $userid Integer: if not 0, then sets ipb_anon_only
  253. * @return Boolean
  254. */
  255. public function loadRange( $address, $killExpired = true, $user = 0 ) {
  256. $iaddr = IP::toHex( $address );
  257. if ( $iaddr === false ) {
  258. # Invalid address
  259. return false;
  260. }
  261. # Only scan ranges which start in this /16, this improves search speed
  262. # Blocks should not cross a /16 boundary.
  263. $range = substr( $iaddr, 0, 4 );
  264. $options = array();
  265. $db =& $this->getDBOptions( $options );
  266. $conds = array(
  267. "ipb_range_start LIKE '$range%'",
  268. "ipb_range_start <= '$iaddr'",
  269. "ipb_range_end >= '$iaddr'"
  270. );
  271. if ( $user ) {
  272. $conds['ipb_anon_only'] = 0;
  273. }
  274. $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
  275. $success = $this->loadFromResult( $res, $killExpired );
  276. return $success;
  277. }
  278. /**
  279. * Given a database row from the ipblocks table, initialize
  280. * member variables
  281. *
  282. * @param $row ResultWrapper: a row from the ipblocks table
  283. */
  284. public function initFromRow( $row ) {
  285. $this->mAddress = $row->ipb_address;
  286. $this->mReason = $row->ipb_reason;
  287. $this->mTimestamp = wfTimestamp(TS_MW,$row->ipb_timestamp);
  288. $this->mUser = $row->ipb_user;
  289. $this->mBy = $row->ipb_by;
  290. $this->mAuto = $row->ipb_auto;
  291. $this->mAnonOnly = $row->ipb_anon_only;
  292. $this->mCreateAccount = $row->ipb_create_account;
  293. $this->mEnableAutoblock = $row->ipb_enable_autoblock;
  294. $this->mBlockEmail = $row->ipb_block_email;
  295. $this->mAllowUsertalk = $row->ipb_allow_usertalk;
  296. $this->mHideName = $row->ipb_deleted;
  297. $this->mId = $row->ipb_id;
  298. $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
  299. if ( isset( $row->user_name ) ) {
  300. $this->mByName = $row->user_name;
  301. } else {
  302. $this->mByName = $row->ipb_by_text;
  303. }
  304. $this->mRangeStart = $row->ipb_range_start;
  305. $this->mRangeEnd = $row->ipb_range_end;
  306. }
  307. /**
  308. * Once $mAddress has been set, get the range they came from.
  309. * Wrapper for IP::parseRange
  310. */
  311. protected function initialiseRange() {
  312. $this->mRangeStart = '';
  313. $this->mRangeEnd = '';
  314. if ( $this->mUser == 0 ) {
  315. list( $this->mRangeStart, $this->mRangeEnd ) = IP::parseRange( $this->mAddress );
  316. }
  317. }
  318. /**
  319. * Delete the row from the IP blocks table.
  320. *
  321. * @return Boolean
  322. */
  323. public function delete() {
  324. if ( wfReadOnly() ) {
  325. return false;
  326. }
  327. if ( !$this->mId ) {
  328. throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
  329. }
  330. $dbw = wfGetDB( DB_MASTER );
  331. $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
  332. return $dbw->affectedRows() > 0;
  333. }
  334. /**
  335. * Insert a block into the block table. Will fail if there is a conflicting
  336. * block (same name and options) already in the database.
  337. *
  338. * @return Boolean: whether or not the insertion was successful.
  339. */
  340. public function insert() {
  341. wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
  342. $dbw = wfGetDB( DB_MASTER );
  343. $this->validateBlockParams();
  344. $this->initialiseRange();
  345. # Don't collide with expired blocks
  346. Block::purgeExpired();
  347. $ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
  348. $dbw->insert( 'ipblocks',
  349. array(
  350. 'ipb_id' => $ipb_id,
  351. 'ipb_address' => $this->mAddress,
  352. 'ipb_user' => $this->mUser,
  353. 'ipb_by' => $this->mBy,
  354. 'ipb_by_text' => $this->mByName,
  355. 'ipb_reason' => $this->mReason,
  356. 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
  357. 'ipb_auto' => $this->mAuto,
  358. 'ipb_anon_only' => $this->mAnonOnly,
  359. 'ipb_create_account' => $this->mCreateAccount,
  360. 'ipb_enable_autoblock' => $this->mEnableAutoblock,
  361. 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
  362. 'ipb_range_start' => $this->mRangeStart,
  363. 'ipb_range_end' => $this->mRangeEnd,
  364. 'ipb_deleted' => $this->mHideName,
  365. 'ipb_block_email' => $this->mBlockEmail,
  366. 'ipb_allow_usertalk' => $this->mAllowUsertalk
  367. ), 'Block::insert', array( 'IGNORE' )
  368. );
  369. $affected = $dbw->affectedRows();
  370. if ($affected)
  371. $this->doRetroactiveAutoblock();
  372. return (bool)$affected;
  373. }
  374. /**
  375. * Update a block in the DB with new parameters.
  376. * The ID field needs to be loaded first.
  377. */
  378. public function update() {
  379. wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" );
  380. $dbw = wfGetDB( DB_MASTER );
  381. $this->validateBlockParams();
  382. $dbw->update( 'ipblocks',
  383. array(
  384. 'ipb_user' => $this->mUser,
  385. 'ipb_by' => $this->mBy,
  386. 'ipb_by_text' => $this->mByName,
  387. 'ipb_reason' => $this->mReason,
  388. 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
  389. 'ipb_auto' => $this->mAuto,
  390. 'ipb_anon_only' => $this->mAnonOnly,
  391. 'ipb_create_account' => $this->mCreateAccount,
  392. 'ipb_enable_autoblock' => $this->mEnableAutoblock,
  393. 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
  394. 'ipb_range_start' => $this->mRangeStart,
  395. 'ipb_range_end' => $this->mRangeEnd,
  396. 'ipb_deleted' => $this->mHideName,
  397. 'ipb_block_email' => $this->mBlockEmail,
  398. 'ipb_allow_usertalk' => $this->mAllowUsertalk ),
  399. array( 'ipb_id' => $this->mId ),
  400. 'Block::update' );
  401. return $dbw->affectedRows();
  402. }
  403. /**
  404. * Make sure all the proper members are set to sane values
  405. * before adding/updating a block
  406. */
  407. protected function validateBlockParams() {
  408. # Unset ipb_anon_only for user blocks, makes no sense
  409. if ( $this->mUser ) {
  410. $this->mAnonOnly = 0;
  411. }
  412. # Unset ipb_enable_autoblock for IP blocks, makes no sense
  413. if ( !$this->mUser ) {
  414. $this->mEnableAutoblock = 0;
  415. $this->mBlockEmail = 0; //Same goes for email...
  416. }
  417. if( !$this->mByName ) {
  418. if( $this->mBy ) {
  419. $this->mByName = User::whoIs( $this->mBy );
  420. } else {
  421. global $wgUser;
  422. $this->mByName = $wgUser->getName();
  423. }
  424. }
  425. }
  426. /**
  427. * Retroactively autoblocks the last IP used by the user (if it is a user)
  428. * blocked by this Block.
  429. *
  430. * @return Boolean: whether or not a retroactive autoblock was made.
  431. */
  432. public function doRetroactiveAutoblock() {
  433. $dbr = wfGetDB( DB_SLAVE );
  434. #If autoblock is enabled, autoblock the LAST IP used
  435. # - stolen shamelessly from CheckUser_body.php
  436. if ($this->mEnableAutoblock && $this->mUser) {
  437. wfDebug("Doing retroactive autoblocks for " . $this->mAddress . "\n");
  438. $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
  439. $conds = array( 'rc_user_text' => $this->mAddress );
  440. if ($this->mAngryAutoblock) {
  441. // Block any IP used in the last 7 days. Up to five IPs.
  442. $conds[] = 'rc_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( time() - (7*86400) ) );
  443. $options['LIMIT'] = 5;
  444. } else {
  445. // Just the last IP used.
  446. $options['LIMIT'] = 1;
  447. }
  448. $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds,
  449. __METHOD__ , $options);
  450. if ( !$dbr->numRows( $res ) ) {
  451. #No results, don't autoblock anything
  452. wfDebug("No IP found to retroactively autoblock\n");
  453. } else {
  454. while ( $row = $dbr->fetchObject( $res ) ) {
  455. if ( $row->rc_ip )
  456. $this->doAutoblock( $row->rc_ip );
  457. }
  458. }
  459. }
  460. }
  461. /**
  462. * Checks whether a given IP is on the autoblock whitelist.
  463. *
  464. * @param $ip String: The IP to check
  465. * @return Boolean
  466. */
  467. public static function isWhitelistedFromAutoblocks( $ip ) {
  468. global $wgMemc;
  469. // Try to get the autoblock_whitelist from the cache, as it's faster
  470. // than getting the msg raw and explode()'ing it.
  471. $key = wfMemcKey( 'ipb', 'autoblock', 'whitelist' );
  472. $lines = $wgMemc->get( $key );
  473. if ( !$lines ) {
  474. $lines = explode( "\n", wfMsgForContentNoTrans( 'autoblock_whitelist' ) );
  475. $wgMemc->set( $key, $lines, 3600 * 24 );
  476. }
  477. wfDebug("Checking the autoblock whitelist..\n");
  478. foreach( $lines as $line ) {
  479. # List items only
  480. if ( substr( $line, 0, 1 ) !== '*' ) {
  481. continue;
  482. }
  483. $wlEntry = substr($line, 1);
  484. $wlEntry = trim($wlEntry);
  485. wfDebug("Checking $ip against $wlEntry...");
  486. # Is the IP in this range?
  487. if (IP::isInRange( $ip, $wlEntry )) {
  488. wfDebug(" IP $ip matches $wlEntry, not autoblocking\n");
  489. return true;
  490. } else {
  491. wfDebug( " No match\n" );
  492. }
  493. }
  494. return false;
  495. }
  496. /**
  497. * Autoblocks the given IP, referring to this Block.
  498. *
  499. * @param $autoblockIP String: the IP to autoblock.
  500. * @param $justInserted Boolean: the main block was just inserted
  501. * @return Boolean: whether or not an autoblock was inserted.
  502. */
  503. public function doAutoblock( $autoblockIP, $justInserted = false ) {
  504. # If autoblocks are disabled, go away.
  505. if ( !$this->mEnableAutoblock ) {
  506. return;
  507. }
  508. # Check for presence on the autoblock whitelist
  509. if (Block::isWhitelistedFromAutoblocks($autoblockIP)) {
  510. return;
  511. }
  512. ## Allow hooks to cancel the autoblock.
  513. if (!wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) )) {
  514. wfDebug( "Autoblock aborted by hook.\n" );
  515. return false;
  516. }
  517. # It's okay to autoblock. Go ahead and create/insert the block.
  518. $ipblock = Block::newFromDB( $autoblockIP );
  519. if ( $ipblock ) {
  520. # If the user is already blocked. Then check if the autoblock would
  521. # exceed the user block. If it would exceed, then do nothing, else
  522. # prolong block time
  523. if ($this->mExpiry &&
  524. ($this->mExpiry < Block::getAutoblockExpiry($ipblock->mTimestamp))) {
  525. return;
  526. }
  527. # Just update the timestamp
  528. if ( !$justInserted ) {
  529. $ipblock->updateTimestamp();
  530. }
  531. return;
  532. } else {
  533. $ipblock = new Block;
  534. }
  535. # Make a new block object with the desired properties
  536. wfDebug( "Autoblocking {$this->mAddress}@" . $autoblockIP . "\n" );
  537. $ipblock->mAddress = $autoblockIP;
  538. $ipblock->mUser = 0;
  539. $ipblock->mBy = $this->mBy;
  540. $ipblock->mByName = $this->mByName;
  541. $ipblock->mReason = wfMsgForContent( 'autoblocker', $this->mAddress, $this->mReason );
  542. $ipblock->mTimestamp = wfTimestampNow();
  543. $ipblock->mAuto = 1;
  544. $ipblock->mCreateAccount = $this->mCreateAccount;
  545. # Continue suppressing the name if needed
  546. $ipblock->mHideName = $this->mHideName;
  547. $ipblock->mAllowUsertalk = $this->mAllowUsertalk;
  548. # If the user is already blocked with an expiry date, we don't
  549. # want to pile on top of that!
  550. if($this->mExpiry) {
  551. $ipblock->mExpiry = min ( $this->mExpiry, Block::getAutoblockExpiry( $this->mTimestamp ));
  552. } else {
  553. $ipblock->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
  554. }
  555. # Insert it
  556. return $ipblock->insert();
  557. }
  558. /**
  559. * Check if a block has expired. Delete it if it is.
  560. * @return Boolean
  561. */
  562. public function deleteIfExpired() {
  563. $fname = 'Block::deleteIfExpired';
  564. wfProfileIn( $fname );
  565. if ( $this->isExpired() ) {
  566. wfDebug( "Block::deleteIfExpired() -- deleting\n" );
  567. $this->delete();
  568. $retVal = true;
  569. } else {
  570. wfDebug( "Block::deleteIfExpired() -- not expired\n" );
  571. $retVal = false;
  572. }
  573. wfProfileOut( $fname );
  574. return $retVal;
  575. }
  576. /**
  577. * Has the block expired?
  578. * @return Boolean
  579. */
  580. public function isExpired() {
  581. wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
  582. if ( !$this->mExpiry ) {
  583. return false;
  584. } else {
  585. return wfTimestampNow() > $this->mExpiry;
  586. }
  587. }
  588. /**
  589. * Is the block address valid (i.e. not a null string?)
  590. * @return Boolean
  591. */
  592. public function isValid() {
  593. return $this->mAddress != '';
  594. }
  595. /**
  596. * Update the timestamp on autoblocks.
  597. */
  598. public function updateTimestamp() {
  599. if ( $this->mAuto ) {
  600. $this->mTimestamp = wfTimestamp();
  601. $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
  602. $dbw = wfGetDB( DB_MASTER );
  603. $dbw->update( 'ipblocks',
  604. array( /* SET */
  605. 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
  606. 'ipb_expiry' => $dbw->timestamp($this->mExpiry),
  607. ), array( /* WHERE */
  608. 'ipb_address' => $this->mAddress
  609. ), 'Block::updateTimestamp'
  610. );
  611. }
  612. }
  613. /**
  614. * Get the user id of the blocking sysop
  615. *
  616. * @return Integer
  617. */
  618. public function getBy() {
  619. return $this->mBy;
  620. }
  621. /**
  622. * Get the username of the blocking sysop
  623. *
  624. * @return String
  625. */
  626. public function getByName() {
  627. return $this->mByName;
  628. }
  629. /**
  630. * Get/set the SELECT ... FOR UPDATE flag
  631. */
  632. public function forUpdate( $x = NULL ) {
  633. return wfSetVar( $this->mForUpdate, $x );
  634. }
  635. /**
  636. * Get/set a flag determining whether the master is used for reads
  637. */
  638. public function fromMaster( $x = NULL ) {
  639. return wfSetVar( $this->mFromMaster, $x );
  640. }
  641. /**
  642. * Get the block name, but with autoblocked IPs hidden as per standard privacy policy
  643. * @return String
  644. */
  645. public function getRedactedName() {
  646. if ( $this->mAuto ) {
  647. return '#' . $this->mId;
  648. } else {
  649. return $this->mAddress;
  650. }
  651. }
  652. /**
  653. * Encode expiry for DB
  654. *
  655. * @param $expiry String: timestamp for expiry, or
  656. * @param $db Database object
  657. * @return String
  658. */
  659. public static function encodeExpiry( $expiry, $db ) {
  660. if ( $expiry == '' || $expiry == Block::infinity() ) {
  661. return Block::infinity();
  662. } else {
  663. return $db->timestamp( $expiry );
  664. }
  665. }
  666. /**
  667. * Decode expiry which has come from the DB
  668. *
  669. * @param $expiry String: Database expiry format
  670. * @param $timestampType Requested timestamp format
  671. * @return String
  672. */
  673. public static function decodeExpiry( $expiry, $timestampType = TS_MW ) {
  674. if ( $expiry == '' || $expiry == Block::infinity() ) {
  675. return Block::infinity();
  676. } else {
  677. return wfTimestamp( $timestampType, $expiry );
  678. }
  679. }
  680. /**
  681. * Get a timestamp of the expiry for autoblocks
  682. *
  683. * @return String
  684. */
  685. public static function getAutoblockExpiry( $timestamp ) {
  686. global $wgAutoblockExpiry;
  687. return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
  688. }
  689. /**
  690. * Gets rid of uneeded numbers in quad-dotted/octet IP strings
  691. * For example, 127.111.113.151/24 -> 127.111.113.0/24
  692. * @param $range String: IP address to normalize
  693. * @return string
  694. */
  695. public static function normaliseRange( $range ) {
  696. $parts = explode( '/', $range );
  697. if ( count( $parts ) == 2 ) {
  698. // IPv6
  699. if ( IP::isIPv6($range) && $parts[1] >= 64 && $parts[1] <= 128 ) {
  700. $bits = $parts[1];
  701. $ipint = IP::toUnsigned6( $parts[0] );
  702. # Native 32 bit functions WONT work here!!!
  703. # Convert to a padded binary number
  704. $network = wfBaseConvert( $ipint, 10, 2, 128 );
  705. # Truncate the last (128-$bits) bits and replace them with zeros
  706. $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
  707. # Convert back to an integer
  708. $network = wfBaseConvert( $network, 2, 10 );
  709. # Reform octet address
  710. $newip = IP::toOctet( $network );
  711. $range = "$newip/{$parts[1]}";
  712. } // IPv4
  713. else if ( IP::isIPv4($range) && $parts[1] >= 16 && $parts[1] <= 32 ) {
  714. $shift = 32 - $parts[1];
  715. $ipint = IP::toUnsigned( $parts[0] );
  716. $ipint = $ipint >> $shift << $shift;
  717. $newip = long2ip( $ipint );
  718. $range = "$newip/{$parts[1]}";
  719. }
  720. }
  721. return $range;
  722. }
  723. /**
  724. * Purge expired blocks from the ipblocks table
  725. */
  726. public static function purgeExpired() {
  727. $dbw = wfGetDB( DB_MASTER );
  728. $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
  729. }
  730. /**
  731. * Get a value to insert into expiry field of the database when infinite expiry
  732. * is desired. In principle this could be DBMS-dependant, but currently all
  733. * supported DBMS's support the string "infinity", so we just use that.
  734. *
  735. * @return String
  736. */
  737. public static function infinity() {
  738. # This is a special keyword for timestamps in PostgreSQL, and
  739. # works with CHAR(14) as well because "i" sorts after all numbers.
  740. return 'infinity';
  741. }
  742. /**
  743. * Convert a DB-encoded expiry into a real string that humans can read.
  744. *
  745. * @param $encoded_expiry String: Database encoded expiry time
  746. * @return String
  747. */
  748. public static function formatExpiry( $encoded_expiry ) {
  749. static $msg = null;
  750. if( is_null( $msg ) ) {
  751. $msg = array();
  752. $keys = array( 'infiniteblock', 'expiringblock' );
  753. foreach( $keys as $key ) {
  754. $msg[$key] = wfMsgHtml( $key );
  755. }
  756. }
  757. $expiry = Block::decodeExpiry( $encoded_expiry );
  758. if ($expiry == 'infinity') {
  759. $expirystr = $msg['infiniteblock'];
  760. } else {
  761. global $wgLang;
  762. $expiretimestr = $wgLang->timeanddate( $expiry, true );
  763. $expirystr = wfMsgReplaceArgs( $msg['expiringblock'], array($expiretimestr) );
  764. }
  765. return $expirystr;
  766. }
  767. /**
  768. * Convert a typed-in expiry time into something we can put into the database.
  769. * @param $expiry_input String: whatever was typed into the form
  770. * @return String: more database friendly
  771. */
  772. public static function parseExpiryInput( $expiry_input ) {
  773. if ( $expiry_input == 'infinite' || $expiry_input == 'indefinite' ) {
  774. $expiry = 'infinity';
  775. } else {
  776. $expiry = strtotime( $expiry_input );
  777. if ($expiry < 0 || $expiry === false) {
  778. return false;
  779. }
  780. }
  781. return $expiry;
  782. }
  783. }