AbstractBlock.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @file
  19. */
  20. namespace MediaWiki\Block;
  21. use IContextSource;
  22. use InvalidArgumentException;
  23. use IP;
  24. use MediaWiki\MediaWikiServices;
  25. use RequestContext;
  26. use Title;
  27. use User;
  28. /**
  29. * @since 1.34 Factored out from DatabaseBlock (previously Block).
  30. */
  31. abstract class AbstractBlock {
  32. /**
  33. * @deprecated since 1.34. Use getReason and setReason instead.
  34. * @var string
  35. */
  36. public $mReason;
  37. /**
  38. * @deprecated since 1.34. Use getTimestamp and setTimestamp instead.
  39. * @var string
  40. */
  41. public $mTimestamp;
  42. /**
  43. * @deprecated since 1.34. Use getExpiry and setExpiry instead.
  44. * @var string
  45. */
  46. public $mExpiry = '';
  47. /** @var bool */
  48. protected $mBlockEmail = false;
  49. /** @var bool */
  50. protected $allowUsertalk = false;
  51. /** @var bool */
  52. protected $blockCreateAccount = false;
  53. /**
  54. * @deprecated since 1.34. Use getHideName and setHideName instead.
  55. * @var bool
  56. */
  57. public $mHideName = false;
  58. /** @var User|string */
  59. protected $target;
  60. /**
  61. * @var int AbstractBlock::TYPE_ constant. After the block has been loaded
  62. * from the database, this can only be USER, IP or RANGE.
  63. */
  64. protected $type;
  65. /** @var User */
  66. protected $blocker;
  67. /** @var bool */
  68. protected $isSitewide = true;
  69. # TYPE constants
  70. const TYPE_USER = 1;
  71. const TYPE_IP = 2;
  72. const TYPE_RANGE = 3;
  73. const TYPE_AUTO = 4;
  74. const TYPE_ID = 5;
  75. /**
  76. * Create a new block with specified parameters on a user, IP or IP range.
  77. *
  78. * @param array $options Parameters of the block, with supported options:
  79. * - address: (string|User) Target user name, User object, IP address or IP range
  80. * - by: (int) User ID of the blocker
  81. * - reason: (string) Reason of the block
  82. * - timestamp: (string) The time at which the block comes into effect
  83. * - byText: (string) Username of the blocker (for foreign users)
  84. * - hideName: (bool) Hide the target user name
  85. */
  86. public function __construct( array $options = [] ) {
  87. $defaults = [
  88. 'address' => '',
  89. 'by' => null,
  90. 'reason' => '',
  91. 'timestamp' => '',
  92. 'byText' => '',
  93. 'hideName' => false,
  94. ];
  95. $options += $defaults;
  96. $this->setTarget( $options['address'] );
  97. if ( $options['by'] ) {
  98. # Local user
  99. $this->setBlocker( User::newFromId( $options['by'] ) );
  100. } else {
  101. # Foreign user
  102. $this->setBlocker( $options['byText'] );
  103. }
  104. $this->setReason( $options['reason'] );
  105. $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
  106. $this->setHideName( (bool)$options['hideName'] );
  107. }
  108. /**
  109. * Get the user id of the blocking sysop
  110. *
  111. * @return int (0 for foreign users)
  112. */
  113. public function getBy() {
  114. return $this->getBlocker()->getId();
  115. }
  116. /**
  117. * Get the username of the blocking sysop
  118. *
  119. * @return string
  120. */
  121. public function getByName() {
  122. return $this->getBlocker()->getName();
  123. }
  124. /**
  125. * Get the block ID
  126. * @return int|null
  127. */
  128. public function getId() {
  129. return null;
  130. }
  131. /**
  132. * Get the reason given for creating the block
  133. *
  134. * @since 1.33
  135. * @return string
  136. */
  137. public function getReason() {
  138. return $this->mReason;
  139. }
  140. /**
  141. * Set the reason for creating the block
  142. *
  143. * @since 1.33
  144. * @param string $reason
  145. */
  146. public function setReason( $reason ) {
  147. $this->mReason = $reason;
  148. }
  149. /**
  150. * Get whether the block hides the target's username
  151. *
  152. * @since 1.33
  153. * @return bool The block hides the username
  154. */
  155. public function getHideName() {
  156. return $this->mHideName;
  157. }
  158. /**
  159. * Set whether ths block hides the target's username
  160. *
  161. * @since 1.33
  162. * @param bool $hideName The block hides the username
  163. */
  164. public function setHideName( $hideName ) {
  165. $this->mHideName = $hideName;
  166. }
  167. /**
  168. * Indicates that the block is a sitewide block. This means the user is
  169. * prohibited from editing any page on the site (other than their own talk
  170. * page).
  171. *
  172. * @since 1.33
  173. * @param null|bool $x
  174. * @return bool
  175. */
  176. public function isSitewide( $x = null ) {
  177. return wfSetVar( $this->isSitewide, $x );
  178. }
  179. /**
  180. * Get or set the flag indicating whether this block blocks the target from
  181. * creating an account. (Note that the flag may be overridden depending on
  182. * global configs.)
  183. *
  184. * @since 1.33
  185. * @param null|bool $x Value to set (if null, just get the property value)
  186. * @return bool Value of the property
  187. */
  188. public function isCreateAccountBlocked( $x = null ) {
  189. return wfSetVar( $this->blockCreateAccount, $x );
  190. }
  191. /**
  192. * Get or set the flag indicating whether this block blocks the target from
  193. * sending emails. (Note that the flag may be overridden depending on
  194. * global configs.)
  195. *
  196. * @since 1.33
  197. * @param null|bool $x Value to set (if null, just get the property value)
  198. * @return bool Value of the property
  199. */
  200. public function isEmailBlocked( $x = null ) {
  201. return wfSetVar( $this->mBlockEmail, $x );
  202. }
  203. /**
  204. * Get or set the flag indicating whether this block blocks the target from
  205. * editing their own user talk page. (Note that the flag may be overridden
  206. * depending on global configs.)
  207. *
  208. * @since 1.33
  209. * @param null|bool $x Value to set (if null, just get the property value)
  210. * @return bool Value of the property
  211. */
  212. public function isUsertalkEditAllowed( $x = null ) {
  213. return wfSetVar( $this->allowUsertalk, $x );
  214. }
  215. /**
  216. * Determine whether the block prevents a given right. A right
  217. * may be blacklisted or whitelisted, or determined from a
  218. * property on the block object. For certain rights, the property
  219. * may be overridden according to global configs.
  220. *
  221. * @since 1.33
  222. * @param string $right
  223. * @return bool|null The block applies to the right, or null if
  224. * unsure (e.g. unrecognized right or unset property)
  225. */
  226. public function appliesToRight( $right ) {
  227. $config = RequestContext::getMain()->getConfig();
  228. $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
  229. $res = null;
  230. switch ( $right ) {
  231. case 'edit':
  232. // TODO: fix this case to return proper value
  233. $res = true;
  234. break;
  235. case 'createaccount':
  236. $res = $this->isCreateAccountBlocked();
  237. break;
  238. case 'sendemail':
  239. $res = $this->isEmailBlocked();
  240. break;
  241. case 'upload':
  242. // Until T6995 is completed
  243. $res = $this->isSitewide();
  244. break;
  245. case 'read':
  246. $res = false;
  247. break;
  248. case 'purge':
  249. $res = false;
  250. break;
  251. }
  252. if ( !$res && $blockDisablesLogin ) {
  253. // If a block would disable login, then it should
  254. // prevent any right that all users cannot do
  255. $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
  256. $anon = new User;
  257. $res = $permissionManager->userHasRight( $anon, $right ) ? $res : true;
  258. }
  259. return $res;
  260. }
  261. /**
  262. * Get/set whether the block prevents a given action
  263. *
  264. * @deprecated since 1.33, use appliesToRight to determine block
  265. * behaviour, and specific methods to get/set properties
  266. * @param string $action Action to check
  267. * @param bool|null $x Value for set, or null to just get value
  268. * @return bool|null Null for unrecognized rights.
  269. */
  270. public function prevents( $action, $x = null ) {
  271. $config = RequestContext::getMain()->getConfig();
  272. $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
  273. $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
  274. $res = null;
  275. switch ( $action ) {
  276. case 'edit':
  277. # For now... <evil laugh>
  278. $res = true;
  279. break;
  280. case 'createaccount':
  281. $res = wfSetVar( $this->blockCreateAccount, $x );
  282. break;
  283. case 'sendemail':
  284. $res = wfSetVar( $this->mBlockEmail, $x );
  285. break;
  286. case 'upload':
  287. // Until T6995 is completed
  288. $res = $this->isSitewide();
  289. break;
  290. case 'editownusertalk':
  291. // NOTE: this check is not reliable on partial blocks
  292. // since partially blocked users are always allowed to edit
  293. // their own talk page unless a restriction exists on the
  294. // page or User_talk: namespace
  295. wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
  296. $res = !$this->isUsertalkEditAllowed();
  297. // edit own user talk can be disabled by config
  298. if ( !$blockAllowsUTEdit ) {
  299. $res = true;
  300. }
  301. break;
  302. case 'read':
  303. $res = false;
  304. break;
  305. case 'purge':
  306. $res = false;
  307. break;
  308. }
  309. if ( !$res && $blockDisablesLogin ) {
  310. // If a block would disable login, then it should
  311. // prevent any action that all users cannot do
  312. $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
  313. $anon = new User;
  314. $res = $permissionManager->userHasRight( $anon, $action ) ? $res : true;
  315. }
  316. return $res;
  317. }
  318. /**
  319. * From an existing block, get the target and the type of target.
  320. * Note that, except for null, it is always safe to treat the target
  321. * as a string; for User objects this will return User::__toString()
  322. * which in turn gives User::getName().
  323. *
  324. * @param string|int|User|null $target
  325. * @return array [ User|String|null, AbstractBlock::TYPE_ constant|null ]
  326. */
  327. public static function parseTarget( $target ) {
  328. # We may have been through this before
  329. if ( $target instanceof User ) {
  330. if ( IP::isValid( $target->getName() ) ) {
  331. return [ $target, self::TYPE_IP ];
  332. } else {
  333. return [ $target, self::TYPE_USER ];
  334. }
  335. } elseif ( $target === null ) {
  336. return [ null, null ];
  337. }
  338. $target = trim( $target );
  339. if ( IP::isValid( $target ) ) {
  340. # We can still create a User if it's an IP address, but we need to turn
  341. # off validation checking (which would exclude IP addresses)
  342. return [
  343. User::newFromName( IP::sanitizeIP( $target ), false ),
  344. self::TYPE_IP
  345. ];
  346. } elseif ( IP::isValidRange( $target ) ) {
  347. # Can't create a User from an IP range
  348. return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
  349. }
  350. # Consider the possibility that this is not a username at all
  351. # but actually an old subpage (T31797)
  352. if ( strpos( $target, '/' ) !== false ) {
  353. # An old subpage, drill down to the user behind it
  354. $target = explode( '/', $target )[0];
  355. }
  356. $userObj = User::newFromName( $target );
  357. if ( $userObj instanceof User ) {
  358. # Note that since numbers are valid usernames, a $target of "12345" will be
  359. # considered a User. If you want to pass a block ID, prepend a hash "#12345",
  360. # since hash characters are not valid in usernames or titles generally.
  361. return [ $userObj, self::TYPE_USER ];
  362. } elseif ( preg_match( '/^#\d+$/', $target ) ) {
  363. # Autoblock reference in the form "#12345"
  364. return [ substr( $target, 1 ), self::TYPE_AUTO ];
  365. } else {
  366. return [ null, null ];
  367. }
  368. }
  369. /**
  370. * Get the type of target for this particular block.
  371. * @return int AbstractBlock::TYPE_ constant, will never be TYPE_ID
  372. */
  373. public function getType() {
  374. return $this->type;
  375. }
  376. /**
  377. * Get the target and target type for this particular block. Note that for autoblocks,
  378. * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
  379. * in this situation.
  380. * @return array [ User|String, AbstractBlock::TYPE_ constant ]
  381. * @todo FIXME: This should be an integral part of the block member variables
  382. */
  383. public function getTargetAndType() {
  384. return [ $this->getTarget(), $this->getType() ];
  385. }
  386. /**
  387. * Get the target for this particular block. Note that for autoblocks,
  388. * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
  389. * in this situation.
  390. * @return User|string
  391. */
  392. public function getTarget() {
  393. return $this->target;
  394. }
  395. /**
  396. * Get the block expiry time
  397. *
  398. * @since 1.19
  399. * @return string
  400. */
  401. public function getExpiry() {
  402. return $this->mExpiry;
  403. }
  404. /**
  405. * Set the block expiry time
  406. *
  407. * @since 1.33
  408. * @param string $expiry
  409. */
  410. public function setExpiry( $expiry ) {
  411. $this->mExpiry = $expiry;
  412. }
  413. /**
  414. * Get the timestamp indicating when the block was created
  415. *
  416. * @since 1.33
  417. * @return string
  418. */
  419. public function getTimestamp() {
  420. return $this->mTimestamp;
  421. }
  422. /**
  423. * Set the timestamp indicating when the block was created
  424. *
  425. * @since 1.33
  426. * @param string $timestamp
  427. */
  428. public function setTimestamp( $timestamp ) {
  429. $this->mTimestamp = $timestamp;
  430. }
  431. /**
  432. * Set the target for this block, and update $this->type accordingly
  433. * @param mixed $target
  434. */
  435. public function setTarget( $target ) {
  436. list( $this->target, $this->type ) = static::parseTarget( $target );
  437. }
  438. /**
  439. * Get the user who implemented this block
  440. * @return User User object. May name a foreign user.
  441. */
  442. public function getBlocker() {
  443. return $this->blocker;
  444. }
  445. /**
  446. * Set the user who implemented (or will implement) this block
  447. * @param User|string $user Local User object or username string
  448. */
  449. public function setBlocker( $user ) {
  450. if ( is_string( $user ) ) {
  451. $user = User::newFromName( $user, false );
  452. }
  453. if ( $user->isAnon() && User::isUsableName( $user->getName() ) ) {
  454. throw new InvalidArgumentException(
  455. 'Blocker must be a local user or a name that cannot be a local user'
  456. );
  457. }
  458. $this->blocker = $user;
  459. }
  460. /**
  461. * Get the key and parameters for the corresponding error message.
  462. *
  463. * @since 1.22
  464. * @param IContextSource $context
  465. * @return array
  466. */
  467. abstract public function getPermissionsError( IContextSource $context );
  468. /**
  469. * Get block information used in different block error messages
  470. *
  471. * @since 1.33
  472. * @param IContextSource $context
  473. * @return array
  474. */
  475. public function getBlockErrorParams( IContextSource $context ) {
  476. $lang = $context->getLanguage();
  477. $blocker = $this->getBlocker();
  478. if ( $blocker instanceof User ) { // local user
  479. $blockerUserpage = $blocker->getUserPage();
  480. $blockerText = $lang->embedBidi( $blockerUserpage->getText() );
  481. $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerText}]]";
  482. } else { // foreign user
  483. $link = $blocker;
  484. }
  485. $reason = $this->getReason();
  486. if ( $reason == '' ) {
  487. $reason = $context->msg( 'blockednoreason' )->text();
  488. }
  489. /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
  490. * This could be a username, an IP range, or a single IP. */
  491. $intended = (string)$this->getTarget();
  492. return [
  493. $link,
  494. $reason,
  495. $context->getRequest()->getIP(),
  496. $lang->embedBidi( $this->getByName() ),
  497. // TODO: SystemBlock replaces this with the system block type. Clean up
  498. // error params so that this is not necessary.
  499. $this->getId(),
  500. $lang->formatExpiry( $this->getExpiry() ),
  501. $lang->embedBidi( $intended ),
  502. $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
  503. ];
  504. }
  505. /**
  506. * Determine whether the block allows the user to edit their own
  507. * user talk page. This is done separately from
  508. * AbstractBlock::appliesToRight because there is no right for
  509. * editing one's own user talk page and because the user's talk
  510. * page needs to be passed into the block object, which is unaware
  511. * of the user.
  512. *
  513. * The ipb_allow_usertalk flag (which corresponds to the property
  514. * allowUsertalk) is used on sitewide blocks and partial blocks
  515. * that contain a namespace restriction on the user talk namespace,
  516. * but do not contain a page restriction on the user's talk page.
  517. * For all other (i.e. most) partial blocks, the flag is ignored,
  518. * and the user can always edit their user talk page unless there
  519. * is a page restriction on their user talk page, in which case
  520. * they can never edit it. (Ideally the flag would be stored as
  521. * null in these cases, but the database field isn't nullable.)
  522. *
  523. * This method does not validate that the passed in talk page belongs to the
  524. * block target since the target (an IP) might not be the same as the user's
  525. * talk page (if they are logged in).
  526. *
  527. * @since 1.33
  528. * @param Title|null $usertalk The user's user talk page. If null,
  529. * and if the target is a User, the target's userpage is used
  530. * @return bool The user can edit their talk page
  531. */
  532. public function appliesToUsertalk( Title $usertalk = null ) {
  533. if ( !$usertalk ) {
  534. if ( $this->target instanceof User ) {
  535. $usertalk = $this->target->getTalkPage();
  536. } else {
  537. throw new InvalidArgumentException(
  538. '$usertalk must be provided if block target is not a user/IP'
  539. );
  540. }
  541. }
  542. if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
  543. throw new InvalidArgumentException(
  544. '$usertalk must be a user talk page'
  545. );
  546. }
  547. if ( !$this->isSitewide() ) {
  548. if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
  549. return true;
  550. }
  551. if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
  552. return false;
  553. }
  554. }
  555. // This is a type of block which uses the ipb_allow_usertalk
  556. // flag. The flag can still be overridden by global configs.
  557. $config = RequestContext::getMain()->getConfig();
  558. if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
  559. return true;
  560. }
  561. return !$this->isUsertalkEditAllowed();
  562. }
  563. /**
  564. * Checks if a block applies to a particular title
  565. *
  566. * This check does not consider whether `$this->isUsertalkEditAllowed`
  567. * returns false, as the identity of the user making the hypothetical edit
  568. * isn't known here (particularly in the case of IP hardblocks, range
  569. * blocks, and auto-blocks).
  570. *
  571. * @param Title $title
  572. * @return bool
  573. */
  574. public function appliesToTitle( Title $title ) {
  575. return $this->isSitewide();
  576. }
  577. /**
  578. * Checks if a block applies to a particular namespace
  579. *
  580. * @since 1.33
  581. *
  582. * @param int $ns
  583. * @return bool
  584. */
  585. public function appliesToNamespace( $ns ) {
  586. return $this->isSitewide();
  587. }
  588. /**
  589. * Checks if a block applies to a particular page
  590. *
  591. * This check does not consider whether `$this->isUsertalkEditAllowed`
  592. * returns false, as the identity of the user making the hypothetical edit
  593. * isn't known here (particularly in the case of IP hardblocks, range
  594. * blocks, and auto-blocks).
  595. *
  596. * @since 1.33
  597. *
  598. * @param int $pageId
  599. * @return bool
  600. */
  601. public function appliesToPage( $pageId ) {
  602. return $this->isSitewide();
  603. }
  604. /**
  605. * Check if the block should be tracked with a cookie.
  606. *
  607. * @since 1.33
  608. * @deprecated since 1.34 Use BlockManager::trackBlockWithCookie instead
  609. * of calling this directly.
  610. * @param bool $isAnon The user is logged out
  611. * @return bool The block should be tracked with a cookie
  612. */
  613. public function shouldTrackWithCookie( $isAnon ) {
  614. wfDeprecated( __METHOD__, '1.34' );
  615. return false;
  616. }
  617. /**
  618. * Check if the block prevents a user from resetting their password
  619. *
  620. * @since 1.33
  621. * @return bool The block blocks password reset
  622. */
  623. public function appliesToPasswordReset() {
  624. return $this->isCreateAccountBlocked();
  625. }
  626. }