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
21namespace MediaWiki\Block;
22
23use CommentStoreComment;
24use IContextSource;
25use InvalidArgumentException;
26use MediaWiki\MediaWikiServices;
27use MediaWiki\User\UserIdentity;
28use Message;
29use RequestContext;
30use Title;
31use User;
32
33/**
34 * @note Extensions should not subclass this, as MediaWiki currently does not support custom block types.
35 * @since 1.34 Factored out from DatabaseBlock (previously Block).
36 */
37abstract class AbstractBlock {
38	/** @var CommentStoreComment */
39	protected $reason;
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 bool */
69	protected $isHardblock;
70
71	/** @var User|string|null */
72	protected $target;
73
74	/**
75	 * @var int|null AbstractBlock::TYPE_ constant. After the block has been loaded
76	 * from the database, this can only be USER, IP or RANGE.
77	 */
78	protected $type;
79
80	/** @var bool */
81	protected $isSitewide = true;
82
83	# TYPE constants
84	# Do not introduce negative constants without changing BlockUser command object.
85	public const TYPE_USER = 1;
86	public const TYPE_IP = 2;
87	public const TYPE_RANGE = 3;
88	public const TYPE_AUTO = 4;
89	public const TYPE_ID = 5;
90
91	/**
92	 * Create a new block with specified parameters on a user, IP or IP range.
93	 *
94	 * @param array $options Parameters of the block, with supported options:
95	 *  - address: (string|UserIdentity) Target user name, user identity object, IP address or IP range
96	 *  - reason: (string|Message|CommentStoreComment) Reason for the block
97	 *  - timestamp: (string) The time at which the block comes into effect
98	 *  - hideName: (bool) Hide the target user name
99	 */
100	public function __construct( array $options = [] ) {
101		$defaults = [
102			'address'         => '',
103			'reason'          => '',
104			'timestamp'       => '',
105			'hideName'        => false,
106			'anonOnly'        => false,
107		];
108
109		$options += $defaults;
110
111		$this->setTarget( $options['address'] );
112
113		$this->setReason( $options['reason'] );
114		$this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
115		$this->setHideName( (bool)$options['hideName'] );
116		$this->isHardblock( !$options['anonOnly'] );
117	}
118
119	/**
120	 * Get the user id of the blocking sysop
121	 *
122	 * @return int (0 for foreign users)
123	 */
124	abstract public function getBy();
125
126	/**
127	 * Get the username of the blocking sysop
128	 *
129	 * @return string
130	 */
131	abstract public function getByName();
132
133	/**
134	 * Get the block ID
135	 * @return int|null
136	 */
137	public function getId() {
138		return null;
139	}
140
141	/**
142	 * Get the information that identifies this block, such that a user could
143	 * look up everything that can be found about this block. May be an ID,
144	 * array of IDs, type, etc.
145	 *
146	 * @return mixed Identifying information
147	 */
148	abstract public function getIdentifier();
149
150	/**
151	 * Get the reason given for creating the block, as a string.
152	 *
153	 * Deprecated, since this gives the caller no control over the language
154	 * or format, and no access to the comment's data.
155	 *
156	 * @deprecated since 1.35. Use getReasonComment instead.
157	 * @since 1.33
158	 * @return string
159	 */
160	public function getReason() {
161		$language = RequestContext::getMain()->getLanguage();
162		return $this->reason->message->inLanguage( $language )->plain();
163	}
164
165	/**
166	 * Get the reason for creating the block.
167	 *
168	 * @since 1.35
169	 * @return CommentStoreComment
170	 */
171	public function getReasonComment() {
172		return $this->reason;
173	}
174
175	/**
176	 * Set the reason for creating the block.
177	 *
178	 * @since 1.33
179	 * @param string|Message|CommentStoreComment $reason
180	 */
181	public function setReason( $reason ) {
182		$this->reason = CommentStoreComment::newUnsavedComment( $reason );
183	}
184
185	/**
186	 * Get whether the block hides the target's username
187	 *
188	 * @since 1.33
189	 * @return bool The block hides the username
190	 */
191	public function getHideName() {
192		return $this->mHideName;
193	}
194
195	/**
196	 * Set whether ths block hides the target's username
197	 *
198	 * @since 1.33
199	 * @param bool $hideName The block hides the username
200	 */
201	public function setHideName( $hideName ) {
202		$this->mHideName = $hideName;
203	}
204
205	/**
206	 * Indicates that the block is a sitewide block. This means the user is
207	 * prohibited from editing any page on the site (other than their own talk
208	 * page).
209	 *
210	 * @since 1.33
211	 * @param null|bool $x
212	 * @return bool
213	 */
214	public function isSitewide( $x = null ) {
215		return wfSetVar( $this->isSitewide, $x );
216	}
217
218	/**
219	 * Get or set the flag indicating whether this block blocks the target from
220	 * creating an account. (Note that the flag may be overridden depending on
221	 * global configs.)
222	 *
223	 * @since 1.33
224	 * @param null|bool $x Value to set (if null, just get the property value)
225	 * @return bool Value of the property
226	 */
227	public function isCreateAccountBlocked( $x = null ) {
228		return wfSetVar( $this->blockCreateAccount, $x );
229	}
230
231	/**
232	 * Get or set the flag indicating whether this block blocks the target from
233	 * sending emails. (Note that the flag may be overridden depending on
234	 * global configs.)
235	 *
236	 * @since 1.33
237	 * @param null|bool $x Value to set (if null, just get the property value)
238	 * @return bool Value of the property
239	 */
240	public function isEmailBlocked( $x = null ) {
241		return wfSetVar( $this->mBlockEmail, $x );
242	}
243
244	/**
245	 * Get or set the flag indicating whether this block blocks the target from
246	 * editing their own user talk page. (Note that the flag may be overridden
247	 * depending on global configs.)
248	 *
249	 * @since 1.33
250	 * @param null|bool $x Value to set (if null, just get the property value)
251	 * @return bool Value of the property
252	 */
253	public function isUsertalkEditAllowed( $x = null ) {
254		return wfSetVar( $this->allowUsertalk, $x );
255	}
256
257	/**
258	 * Get/set whether the block is a hardblock (affects logged-in users on a given IP/range)
259	 *
260	 * Note that users are always hardblocked, since they're logged in by definition.
261	 *
262	 * @since 1.36 Moved up from DatabaseBlock
263	 * @param bool|null $x
264	 * @return bool
265	 */
266	public function isHardblock( $x = null ) {
267		wfSetVar( $this->isHardblock, $x );
268
269		return $this->getType() == self::TYPE_USER
270			? true
271			: $this->isHardblock;
272	}
273
274	/**
275	 * Determine whether the block prevents a given right. A right
276	 * may be blacklisted or whitelisted, or determined from a
277	 * property on the block object. For certain rights, the property
278	 * may be overridden according to global configs.
279	 *
280	 * @since 1.33
281	 * @param string $right
282	 * @return bool|null The block applies to the right, or null if
283	 *  unsure (e.g. unrecognized right or unset property)
284	 */
285	public function appliesToRight( $right ) {
286		$config = RequestContext::getMain()->getConfig();
287		$blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
288
289		$res = null;
290		switch ( $right ) {
291			case 'edit':
292				// TODO: fix this case to return proper value
293				$res = true;
294				break;
295			case 'createaccount':
296				$res = $this->isCreateAccountBlocked();
297				break;
298			case 'sendemail':
299				$res = $this->isEmailBlocked();
300				break;
301			case 'upload':
302				// Until T6995 is completed
303				$res = $this->isSitewide();
304				break;
305			case 'read':
306				$res = false;
307				break;
308			case 'purge':
309				$res = false;
310				break;
311		}
312		if ( !$res && $blockDisablesLogin ) {
313			// If a block would disable login, then it should
314			// prevent any right that all users cannot do
315			$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
316			$anon = new User;
317			$res = $permissionManager->userHasRight( $anon, $right ) ? $res : true;
318		}
319
320		return $res;
321	}
322
323	/**
324	 * From an existing block, get the target and the type of target.
325	 * Note that, except for null, it is always safe to treat the target
326	 * as a string; for User objects this will return User::__toString()
327	 * which in turn gives User::getName().
328	 *
329	 * If the type is not null, it will be an AbstractBlock::TYPE_ constant.
330	 *
331	 * @deprecated since 1.36. Use BlockUtils service instead.
332	 * @param string|UserIdentity|null $target
333	 * @return array [ User|string|null, int|null ]
334	 */
335	public static function parseTarget( $target ) {
336		wfDeprecated( __METHOD__, '1.36' );
337		return MediaWikiServices::getInstance()
338			->getBlockUtils()
339			->parseBlockTarget( $target );
340	}
341
342	/**
343	 * Get the type of target for this particular block.
344	 * @return int|null AbstractBlock::TYPE_ constant, will never be TYPE_ID
345	 */
346	public function getType() {
347		return $this->type;
348	}
349
350	/**
351	 * Get the target and target type for this particular block. Note that for autoblocks,
352	 * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
353	 * in this situation.
354	 *
355	 * If the type is not null, it will be an AbstractBlock::TYPE_ constant.
356	 *
357	 * @return array [ User|String|null, int|null ]
358	 * @todo FIXME: This should be an integral part of the block member variables
359	 */
360	public function getTargetAndType() {
361		return [ $this->getTarget(), $this->getType() ];
362	}
363
364	/**
365	 * Get the target for this particular block.  Note that for autoblocks,
366	 * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
367	 * in this situation.
368	 * @return User|string|null
369	 */
370	public function getTarget() {
371		return $this->target;
372	}
373
374	/**
375	 * Get the block expiry time
376	 *
377	 * @since 1.19
378	 * @return string
379	 */
380	public function getExpiry() {
381		return $this->mExpiry;
382	}
383
384	/**
385	 * Set the block expiry time
386	 *
387	 * @since 1.33
388	 * @param string $expiry
389	 */
390	public function setExpiry( $expiry ) {
391		$this->mExpiry = $expiry;
392	}
393
394	/**
395	 * Get the timestamp indicating when the block was created
396	 *
397	 * @since 1.33
398	 * @return string
399	 */
400	public function getTimestamp() {
401		return $this->mTimestamp;
402	}
403
404	/**
405	 * Set the timestamp indicating when the block was created
406	 *
407	 * @since 1.33
408	 * @param string $timestamp
409	 */
410	public function setTimestamp( $timestamp ) {
411		$this->mTimestamp = $timestamp;
412	}
413
414	/**
415	 * Set the target for this block, and update $this->type accordingly
416	 * @param string|UserIdentity|null $target
417	 */
418	public function setTarget( $target ) {
419		// Small optimization to make this code testable, this is what would happen anyway
420		if ( $target === '' ) {
421			$this->target = null;
422			$this->type = null;
423		} else {
424			list( $this->target, $this->type ) = MediaWikiServices::getInstance()
425				->getBlockUtils()
426				->parseBlockTarget( $target );
427		}
428	}
429
430	/**
431	 * Get the key and parameters for the corresponding error message.
432	 *
433	 * @deprecated since 1.35 Use BlockErrorFormatter::getMessage instead, and
434	 *  build the array using Message::getKey and Message::getParams.
435	 * @since 1.22
436	 * @param IContextSource $context
437	 * @return array
438	 */
439	public function getPermissionsError( IContextSource $context ) {
440		$message = MediaWikiServices::getInstance()
441			->getBlockErrorFormatter()->getMessage(
442				$this,
443				$context->getUser(),
444				$context->getLanguage(),
445				$context->getRequest()->getIP()
446			);
447		return array_merge( [ [ $message->getKey() ], $message->getParams() ] );
448	}
449
450	/**
451	 * Determine whether the block allows the user to edit their own
452	 * user talk page. This is done separately from
453	 * AbstractBlock::appliesToRight because there is no right for
454	 * editing one's own user talk page and because the user's talk
455	 * page needs to be passed into the block object, which is unaware
456	 * of the user.
457	 *
458	 * The ipb_allow_usertalk flag (which corresponds to the property
459	 * allowUsertalk) is used on sitewide blocks and partial blocks
460	 * that contain a namespace restriction on the user talk namespace,
461	 * but do not contain a page restriction on the user's talk page.
462	 * For all other (i.e. most) partial blocks, the flag is ignored,
463	 * and the user can always edit their user talk page unless there
464	 * is a page restriction on their user talk page, in which case
465	 * they can never edit it. (Ideally the flag would be stored as
466	 * null in these cases, but the database field isn't nullable.)
467	 *
468	 * This method does not validate that the passed in talk page belongs to the
469	 * block target since the target (an IP) might not be the same as the user's
470	 * talk page (if they are logged in).
471	 *
472	 * @since 1.33
473	 * @param Title|null $usertalk The user's user talk page. If null,
474	 *  and if the target is a User, the target's userpage is used
475	 * @return bool The user can edit their talk page
476	 */
477	public function appliesToUsertalk( Title $usertalk = null ) {
478		if ( !$usertalk ) {
479			if ( $this->target instanceof User ) {
480				$usertalk = $this->target->getTalkPage();
481			} else {
482				throw new InvalidArgumentException(
483					'$usertalk must be provided if block target is not a user/IP'
484				);
485			}
486		}
487
488		if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
489			throw new InvalidArgumentException(
490				'$usertalk must be a user talk page'
491			);
492		}
493
494		if ( !$this->isSitewide() ) {
495			if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
496				return true;
497			}
498			if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
499				return false;
500			}
501		}
502
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		$config = RequestContext::getMain()->getConfig();
506		if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
507			return true;
508		}
509		return !$this->isUsertalkEditAllowed();
510	}
511
512	/**
513	 * Checks if a block applies to a particular title
514	 *
515	 * This check does not consider whether `$this->isUsertalkEditAllowed`
516	 * returns false, as the identity of the user making the hypothetical edit
517	 * isn't known here (particularly in the case of IP hardblocks, range
518	 * blocks, and auto-blocks).
519	 *
520	 * @param Title $title
521	 * @return bool
522	 */
523	public function appliesToTitle( Title $title ) {
524		return $this->isSitewide();
525	}
526
527	/**
528	 * Checks if a block applies to a particular namespace
529	 *
530	 * @since 1.33
531	 *
532	 * @param int $ns
533	 * @return bool
534	 */
535	public function appliesToNamespace( $ns ) {
536		return $this->isSitewide();
537	}
538
539	/**
540	 * Checks if a block applies to a particular page
541	 *
542	 * This check does not consider whether `$this->isUsertalkEditAllowed`
543	 * returns false, as the identity of the user making the hypothetical edit
544	 * isn't known here (particularly in the case of IP hardblocks, range
545	 * blocks, and auto-blocks).
546	 *
547	 * @since 1.33
548	 *
549	 * @param int $pageId
550	 * @return bool
551	 */
552	public function appliesToPage( $pageId ) {
553		return $this->isSitewide();
554	}
555
556	/**
557	 * Check if the block prevents a user from resetting their password
558	 *
559	 * @since 1.33
560	 * @return bool The block blocks password reset
561	 */
562	public function appliesToPasswordReset() {
563		return $this->isCreateAccountBlocked();
564	}
565
566}
567