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