Update git submodules
[mediawiki.git] / includes / block / AbstractBlock.php
blob8607ef449bcad5845e586f01851ef62ef71f5a64
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.
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.
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
18 * @file
21 namespace MediaWiki\Block;
23 use IContextSource;
24 use InvalidArgumentException;
25 use MediaWiki\CommentStore\CommentStoreComment;
26 use MediaWiki\DAO\WikiAwareEntityTrait;
27 use MediaWiki\MainConfigNames;
28 use MediaWiki\MediaWikiServices;
29 use MediaWiki\Title\Title;
30 use MediaWiki\User\User;
31 use MediaWiki\User\UserIdentity;
32 use Message;
33 use RequestContext;
35 /**
36 * @note Extensions should not subclass this, as MediaWiki currently does not
37 * support custom block types.
38 * @since 1.34 Factored out from DatabaseBlock (previously Block).
40 abstract class AbstractBlock implements Block {
41 use WikiAwareEntityTrait;
43 /** @var CommentStoreComment */
44 protected $reason;
46 /** @var string */
47 protected $timestamp = '';
49 /** @var string */
50 protected $expiry = '';
52 /** @var bool */
53 protected $blockEmail = false;
55 /** @var bool */
56 protected $allowUsertalk = false;
58 /** @var bool */
59 protected $blockCreateAccount = false;
61 /** @var bool */
62 protected $hideName = false;
64 /** @var bool */
65 protected $isHardblock;
67 /** @var UserIdentity|string|null */
68 protected $target;
70 /**
71 * @var int|null AbstractBlock::TYPE_ constant. After the block has been loaded
72 * from the database, this can only be USER, IP or RANGE.
74 protected $type;
76 /** @var bool */
77 protected $isSitewide = true;
79 /** @var string|false */
80 protected $wikiId;
82 /**
83 * Create a new block with specified parameters on a user, IP or IP range.
85 * @param array $options Parameters of the block, with supported options:
86 * - address: (string|UserIdentity) Target user name, user identity object,
87 * IP address or IP range
88 * - wiki: (string|false) The wiki the block has been issued in,
89 * self::LOCAL for the local wiki (since 1.38)
90 * - reason: (string|Message|CommentStoreComment) Reason for the block
91 * - timestamp: (string) The time at which the block comes into effect
92 * - hideName: (bool) Hide the target user name
93 * - anonOnly: (bool) Used if the target is an IP address. The block only
94 * applies to anon and temporary users using this IP address, and not to
95 * logged-in users.
97 public function __construct( array $options = [] ) {
98 $defaults = [
99 'address' => '',
100 'wiki' => self::LOCAL,
101 'reason' => '',
102 'timestamp' => '',
103 'hideName' => false,
104 'anonOnly' => false,
107 $options += $defaults;
109 $this->wikiId = $options['wiki'];
110 $this->setTarget( $options['address'] );
111 $this->setReason( $options['reason'] );
112 $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
113 $this->setHideName( (bool)$options['hideName'] );
114 $this->isHardblock( !$options['anonOnly'] );
118 * Get the user id of the blocking sysop
120 * @param string|false $wikiId (since 1.38)
121 * @return int (0 for foreign users)
123 abstract public function getBy( $wikiId = self::LOCAL ): int;
126 * Get the username of the blocking sysop
128 * @return string
130 abstract public function getByName();
133 * @inheritDoc
135 public function getId( $wikiId = self::LOCAL ): ?int {
136 $this->assertWiki( $wikiId );
137 return null;
141 * Get the reason given for creating the block, as a string.
143 * Deprecated, since this gives the caller no control over the language
144 * or format, and no access to the comment's data.
146 * @deprecated since 1.35. Use getReasonComment instead.
147 * @since 1.33
148 * @return string
150 public function getReason() {
151 wfDeprecated( __METHOD__, '1.35' );
152 $language = RequestContext::getMain()->getLanguage();
153 return $this->reason->message->inLanguage( $language )->plain();
157 * Get the reason for creating the block.
159 * @since 1.35
160 * @return CommentStoreComment
162 public function getReasonComment(): CommentStoreComment {
163 return $this->reason;
167 * Set the reason for creating the block.
169 * @since 1.33
170 * @param string|Message|CommentStoreComment $reason
172 public function setReason( $reason ) {
173 $this->reason = CommentStoreComment::newUnsavedComment( $reason );
177 * Get whether the block hides the target's username
179 * @since 1.33
180 * @return bool The block hides the username
182 public function getHideName() {
183 return $this->hideName;
187 * Set whether the block hides the target's username
189 * @since 1.33
190 * @param bool $hideName The block hides the username
192 public function setHideName( $hideName ) {
193 $this->hideName = $hideName;
197 * Indicates that the block is a sitewide block. This means the user is
198 * prohibited from editing any page on the site (other than their own talk
199 * page).
201 * @since 1.33
202 * @param null|bool $x
203 * @return bool
205 public function isSitewide( $x = null ): bool {
206 return wfSetVar( $this->isSitewide, $x );
210 * Get or set the flag indicating whether this block blocks the target from
211 * creating an account. (Note that the flag may be overridden depending on
212 * global configs.)
214 * @since 1.33
215 * @param null|bool $x Value to set (if null, just get the property value)
216 * @return bool Value of the property
218 public function isCreateAccountBlocked( $x = null ): bool {
219 return wfSetVar( $this->blockCreateAccount, $x );
223 * Get or set the flag indicating whether this block blocks the target from
224 * sending emails. (Note that the flag may be overridden depending on
225 * global configs.)
227 * @since 1.33
228 * @param null|bool $x Value to set (if null, just get the property value)
229 * @return bool Value of the property
231 public function isEmailBlocked( $x = null ) {
232 return wfSetVar( $this->blockEmail, $x );
236 * Get or set the flag indicating whether this block blocks the target from
237 * editing their own user talk page. (Note that the flag may be overridden
238 * depending on global configs.)
240 * @since 1.33
241 * @param null|bool $x Value to set (if null, just get the property value)
242 * @return bool Value of the property
244 public function isUsertalkEditAllowed( $x = null ) {
245 return wfSetVar( $this->allowUsertalk, $x );
249 * Get/set whether the block is a hard block (affects logged-in users on a
250 * given IP/range).
252 * Note that temporary users are not considered logged-in here - they are
253 * always blocked by IP-address blocks.
255 * Note that user blocks are always hard blocks, since the target is logged
256 * in by definition.
258 * @since 1.36 Moved up from DatabaseBlock
259 * @param bool|null $x
260 * @return bool
262 public function isHardblock( $x = null ): bool {
263 wfSetVar( $this->isHardblock, $x );
265 return $this->getType() == self::TYPE_USER
266 ? true
267 : $this->isHardblock;
271 * Determine whether the block prevents a given right. A right may be
272 * allowed or disallowed by default, or determined from a property on the
273 * block object. For certain rights, the property may be overridden
274 * according to global configs.
276 * @since 1.33
277 * @param string $right
278 * @return bool|null The block applies to the right, or null if
279 * unsure (e.g. unrecognized right or unset property)
281 public function appliesToRight( $right ) {
282 $blockDisablesLogin = MediaWikiServices::getInstance()->getMainConfig()
283 ->get( MainConfigNames::BlockDisablesLogin );
285 $res = null;
286 switch ( $right ) {
287 case 'createaccount':
288 $res = $this->isCreateAccountBlocked();
289 break;
290 case 'sendemail':
291 $res = $this->isEmailBlocked();
292 break;
293 case 'upload':
294 // Sitewide blocks always block upload. This may be overridden in a subclass.
295 $res = $this->isSitewide();
296 break;
297 case 'read':
298 $res = false;
299 break;
301 if ( !$res && $blockDisablesLogin ) {
302 // If a block would disable login, then it should
303 // prevent any right that all users cannot do
304 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
305 $anon = new User;
306 $res = $permissionManager->userHasRight( $anon, $right ) ? $res : true;
309 return $res;
313 * Get the type of target for this particular block.
314 * @return int|null AbstractBlock::TYPE_ constant, will never be TYPE_ID
316 public function getType(): ?int {
317 return $this->type;
321 * @since 1.37
322 * @return ?UserIdentity
324 public function getTargetUserIdentity(): ?UserIdentity {
325 return $this->target instanceof UserIdentity ? $this->target : null;
329 * @since 1.37
330 * @return string
332 public function getTargetName(): string {
333 return $this->target instanceof UserIdentity
334 ? $this->target->getName()
335 : (string)$this->target;
339 * @param UserIdentity|string $target
341 * @return bool
342 * @since 1.37
344 public function isBlocking( $target ): bool {
345 $targetName = $target instanceof UserIdentity
346 ? $target->getName()
347 : (string)$target;
349 return $targetName === $this->getTargetName();
353 * Get the block expiry time
355 * @since 1.19
356 * @return string
358 public function getExpiry(): string {
359 return $this->expiry;
363 * Set the block expiry time
365 * @since 1.33
366 * @param string $expiry
368 public function setExpiry( $expiry ) {
369 // Force string so getExpiry() return typehint doesn't break things
370 $this->expiry = (string)$expiry;
374 * Get the timestamp indicating when the block was created
376 * @since 1.33
377 * @return string
379 public function getTimestamp(): string {
380 return $this->timestamp;
384 * Set the timestamp indicating when the block was created
386 * @since 1.33
387 * @param string $timestamp
389 public function setTimestamp( $timestamp ) {
390 // Force string so getTimestamp() return typehint doesn't break things
391 $this->timestamp = (string)$timestamp;
395 * Set the target for this block, and update $this->type accordingly
396 * @param string|UserIdentity|null $target
398 public function setTarget( $target ) {
399 // Small optimization to make this code testable, this is what would happen anyway
400 if ( $target === '' ) {
401 $this->target = null;
402 $this->type = null;
403 } else {
404 [ $parsedTarget, $this->type ] = MediaWikiServices::getInstance()
405 ->getBlockUtils()
406 ->parseBlockTarget( $target );
407 if ( $parsedTarget !== null ) {
408 $this->assertWiki( is_string( $parsedTarget ) ? self::LOCAL : $parsedTarget->getWikiId() );
410 $this->target = $parsedTarget;
415 * @since 1.38
416 * @return string|false
418 public function getWikiId() {
419 return $this->wikiId;
423 * Get the key and parameters for the corresponding error message.
425 * @deprecated since 1.35 Use BlockErrorFormatter::getMessage instead, and
426 * build the array using Message::getKey and Message::getParams. Hard
427 * deprecated since 1.40.
428 * @since 1.22
429 * @param IContextSource $context
430 * @return array A message array: either a list of strings, the first of which
431 * is the message key and the remaining ones the parameters, or an array with
432 * a single MessageSpecifier object.
433 * @phan-return non-empty-array
435 public function getPermissionsError( IContextSource $context ) {
436 wfDeprecated( __METHOD__, '1.35' );
437 $message = MediaWikiServices::getInstance()
438 ->getBlockErrorFormatter()->getMessage(
439 $this,
440 $context->getUser(),
441 $context->getLanguage(),
442 $context->getRequest()->getIP()
444 return array_merge( [ $message->getKey() ], $message->getParams() );
448 * Determine whether the block allows the user to edit their own
449 * user talk page. This is done separately from
450 * AbstractBlock::appliesToRight because there is no right for
451 * editing one's own user talk page and because the user's talk
452 * page needs to be passed into the block object, which is unaware
453 * of the user.
455 * The ipb_allow_usertalk flag (which corresponds to the property
456 * allowUsertalk) is used on sitewide blocks and partial blocks
457 * that contain a namespace restriction on the user talk namespace,
458 * but do not contain a page restriction on the user's talk page.
459 * For all other (i.e. most) partial blocks, the flag is ignored,
460 * and the user can always edit their user talk page unless there
461 * is a page restriction on their user talk page, in which case
462 * they can never edit it. (Ideally the flag would be stored as
463 * null in these cases, but the database field isn't nullable.)
465 * This method does not validate that the passed in talk page belongs to the
466 * block target since the target (an IP) might not be the same as the user's
467 * talk page (if they are logged in).
469 * @since 1.33
470 * @param Title|null $usertalk The user's user talk page. If null,
471 * and if the target is a User, the target's userpage is used
472 * @return bool The user can edit their talk page
474 public function appliesToUsertalk( Title $usertalk = null ) {
475 if ( !$usertalk ) {
476 if ( $this->target instanceof UserIdentity ) {
477 $usertalk = Title::makeTitle(
478 NS_USER_TALK,
479 $this->target->getName()
481 } else {
482 throw new InvalidArgumentException(
483 '$usertalk must be provided if block target is not a user/IP'
488 if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
489 throw new InvalidArgumentException(
490 '$usertalk must be a user talk page'
494 if ( !$this->isSitewide() ) {
495 if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
496 return true;
498 if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
499 return false;
503 // This is a type of block which uses the ipb_allow_usertalk
504 // flag. The flag can still be overridden by global configs.
505 if ( !MediaWikiServices::getInstance()->getMainConfig()
506 ->get( MainConfigNames::BlockAllowsUTEdit )
508 return true;
510 return !$this->isUsertalkEditAllowed();
514 * Checks if a block applies to a particular title
516 * This check does not consider whether `$this->isUsertalkEditAllowed`
517 * returns false, as the identity of the user making the hypothetical edit
518 * isn't known here (particularly in the case of IP hard blocks, range
519 * blocks, and auto-blocks).
521 * @param Title $title
522 * @return bool
524 public function appliesToTitle( Title $title ) {
525 return $this->isSitewide();
529 * Checks if a block applies to a particular namespace
531 * @since 1.33
533 * @param int $ns
534 * @return bool
536 public function appliesToNamespace( $ns ) {
537 return $this->isSitewide();
541 * Checks if a block applies to a particular page
543 * This check does not consider whether `$this->isUsertalkEditAllowed`
544 * returns false, as the identity of the user making the hypothetical edit
545 * isn't known here (particularly in the case of IP hard blocks, range
546 * blocks, and auto-blocks).
548 * @since 1.33
550 * @param int $pageId
551 * @return bool
553 public function appliesToPage( $pageId ) {
554 return $this->isSitewide();
558 * Check if the block prevents a user from resetting their password
560 * @since 1.33
561 * @return bool The block blocks password reset
563 public function appliesToPasswordReset() {
564 return $this->isCreateAccountBlocked();
567 public function toArray(): array {
568 return [ $this ];