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