Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
7.20% |
9 / 125 |
|
16.13% |
5 / 31 |
CRAP | |
0.00% |
0 / 1 |
AbstractBlock | |
7.20% |
9 / 125 |
|
16.13% |
5 / 31 |
2937.04 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
30 | |||
getBy | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getByName | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getId | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getReasonComment | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setReason | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHideName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setHideName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isSitewide | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isCreateAccountBlocked | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isEmailBlocked | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isUsertalkEditAllowed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isHardblock | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
appliesToRight | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
90 | |||
getTarget | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRedactedTarget | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getTargetUserIdentity | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getTargetName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isBlocking | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
getExpiry | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isIndefinite | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setExpiry | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setTarget | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
getWikiId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
appliesToUsertalk | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
72 | |||
appliesToTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
appliesToNamespace | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
appliesToPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
appliesToPasswordReset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
toArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
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 InvalidArgumentException; |
24 | use LogicException; |
25 | use MediaWiki\CommentStore\CommentStoreComment; |
26 | use MediaWiki\DAO\WikiAwareEntityTrait; |
27 | use MediaWiki\MainConfigNames; |
28 | use MediaWiki\MediaWikiServices; |
29 | use MediaWiki\Message\Message; |
30 | use MediaWiki\Title\Title; |
31 | use MediaWiki\User\UserIdentity; |
32 | |
33 | /** |
34 | * @note Extensions should not subclass this, as MediaWiki currently does not |
35 | * support custom block types. |
36 | * @since 1.34 Factored out from DatabaseBlock (previously Block). |
37 | */ |
38 | abstract class AbstractBlock implements Block { |
39 | use WikiAwareEntityTrait; |
40 | |
41 | /** @var CommentStoreComment */ |
42 | protected $reason; |
43 | |
44 | /** @var string */ |
45 | protected $timestamp = ''; |
46 | |
47 | /** @var string */ |
48 | protected $expiry = ''; |
49 | |
50 | /** @var bool */ |
51 | protected $blockEmail = false; |
52 | |
53 | /** @var bool */ |
54 | protected $allowUsertalk = false; |
55 | |
56 | /** @var bool */ |
57 | protected $blockCreateAccount = false; |
58 | |
59 | /** @var bool */ |
60 | protected $hideName = false; |
61 | |
62 | /** @var bool */ |
63 | protected $isHardblock; |
64 | |
65 | /** @var BlockTarget|null */ |
66 | protected $target; |
67 | |
68 | /** @var bool */ |
69 | protected $isSitewide = true; |
70 | |
71 | protected string|false $wikiId; |
72 | |
73 | /** |
74 | * Create a new block with specified parameters on a user, IP or IP range. |
75 | * |
76 | * @param array $options Parameters of the block, with supported options: |
77 | * - target: (BlockTarget) The target object (since 1.44) |
78 | * - address: (string|UserIdentity) Deprecated since 1.45, use 'target'. |
79 | * - wiki: (string|false) The wiki the block has been issued in, |
80 | * self::LOCAL for the local wiki (since 1.38) |
81 | * - reason: (string|Message|CommentStoreComment) Reason for the block |
82 | * - timestamp: (string) The time at which the block comes into effect, |
83 | * in any format supported by wfTimestamp() |
84 | * - decodedTimestamp: (string) The timestamp in MW 14-character format |
85 | * - hideName: (bool) Hide the target user name |
86 | * - anonOnly: (bool) Used if the target is an IP address. The block only |
87 | * applies to anon and temporary users using this IP address, and not to |
88 | * logged-in users. |
89 | */ |
90 | public function __construct( array $options = [] ) { |
91 | $defaults = [ |
92 | 'wiki' => self::LOCAL, |
93 | 'reason' => '', |
94 | 'timestamp' => '', |
95 | 'hideName' => false, |
96 | 'anonOnly' => false, |
97 | ]; |
98 | |
99 | $options += $defaults; |
100 | |
101 | $this->wikiId = $options['wiki']; |
102 | if ( isset( $options['target'] ) ) { |
103 | if ( !( $options['target'] instanceof BlockTarget ) ) { |
104 | throw new InvalidArgumentException( 'Invalid block target' ); |
105 | } |
106 | $this->setTarget( $options['target'] ); |
107 | } elseif ( isset( $options['address'] ) ) { |
108 | wfDeprecatedMsg( |
109 | 'The address parameter to AbstractBlock::__construct is deprecated since 1.45', |
110 | '1.45' ); |
111 | $this->setTarget( $options['address'] ); |
112 | } else { |
113 | $this->setTarget( null ); |
114 | } |
115 | $this->setReason( $options['reason'] ); |
116 | if ( isset( $options['decodedTimestamp'] ) ) { |
117 | $this->setTimestamp( $options['decodedTimestamp'] ); |
118 | } else { |
119 | $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) ); |
120 | } |
121 | $this->setHideName( (bool)$options['hideName'] ); |
122 | $this->isHardblock( !$options['anonOnly'] ); |
123 | } |
124 | |
125 | /** |
126 | * Get the user id of the blocking sysop |
127 | * |
128 | * @param string|false $wikiId (since 1.38) |
129 | * @return int (0 for foreign users) |
130 | */ |
131 | abstract public function getBy( $wikiId = self::LOCAL ): int; |
132 | |
133 | /** |
134 | * Get the username of the blocking sysop |
135 | * |
136 | * @return string |
137 | */ |
138 | abstract public function getByName(); |
139 | |
140 | /** |
141 | * @inheritDoc |
142 | */ |
143 | public function getId( $wikiId = self::LOCAL ): ?int { |
144 | $this->assertWiki( $wikiId ); |
145 | return null; |
146 | } |
147 | |
148 | /** |
149 | * Get the reason for creating the block. |
150 | * |
151 | * @since 1.35 |
152 | * @return CommentStoreComment |
153 | */ |
154 | public function getReasonComment(): CommentStoreComment { |
155 | return $this->reason; |
156 | } |
157 | |
158 | /** |
159 | * Set the reason for creating the block. |
160 | * |
161 | * @since 1.33 |
162 | * @param string|Message|CommentStoreComment $reason |
163 | */ |
164 | public function setReason( $reason ) { |
165 | $this->reason = CommentStoreComment::newUnsavedComment( $reason ); |
166 | } |
167 | |
168 | /** |
169 | * Get whether the block hides the target's username |
170 | * |
171 | * @since 1.33 |
172 | * @return bool The block hides the username |
173 | */ |
174 | public function getHideName() { |
175 | return $this->hideName; |
176 | } |
177 | |
178 | /** |
179 | * Set whether the block hides the target's username |
180 | * |
181 | * @since 1.33 |
182 | * @param bool $hideName The block hides the username |
183 | */ |
184 | public function setHideName( $hideName ) { |
185 | $this->hideName = $hideName; |
186 | } |
187 | |
188 | /** |
189 | * Indicates that the block is a sitewide block. This means the user is |
190 | * prohibited from editing any page on the site (other than their own talk |
191 | * page). |
192 | * |
193 | * @since 1.33 |
194 | * @param null|bool $x |
195 | * @return bool |
196 | */ |
197 | public function isSitewide( $x = null ): bool { |
198 | return wfSetVar( $this->isSitewide, $x ); |
199 | } |
200 | |
201 | /** |
202 | * Get or set the flag indicating whether this block blocks the target from |
203 | * creating an account. (Note that the flag may be overridden depending on |
204 | * global configs.) |
205 | * |
206 | * @since 1.33 |
207 | * @param null|bool $x Value to set (if null, just get the property value) |
208 | * @return bool Value of the property |
209 | */ |
210 | public function isCreateAccountBlocked( $x = null ): bool { |
211 | return wfSetVar( $this->blockCreateAccount, $x ); |
212 | } |
213 | |
214 | /** |
215 | * Get or set the flag indicating whether this block blocks the target from |
216 | * sending emails. (Note that the flag may be overridden depending on |
217 | * global configs.) |
218 | * |
219 | * @since 1.33 |
220 | * @param null|bool $x Value to set (if null, just get the property value) |
221 | * @return bool Value of the property |
222 | */ |
223 | public function isEmailBlocked( $x = null ) { |
224 | return wfSetVar( $this->blockEmail, $x ); |
225 | } |
226 | |
227 | /** |
228 | * Get or set the flag indicating whether this block blocks the target from |
229 | * editing their own user talk page. (Note that the flag may be overridden |
230 | * depending on global configs.) |
231 | * |
232 | * @since 1.33 |
233 | * @param null|bool $x Value to set (if null, just get the property value) |
234 | * @return bool Value of the property |
235 | */ |
236 | public function isUsertalkEditAllowed( $x = null ) { |
237 | return wfSetVar( $this->allowUsertalk, $x ); |
238 | } |
239 | |
240 | /** |
241 | * Get/set whether the block is a hard block (affects logged-in users on a |
242 | * given IP/range). |
243 | * |
244 | * Note that temporary users are not considered logged-in here - they are |
245 | * always blocked by IP-address blocks. |
246 | * |
247 | * Note that user blocks are always hard blocks, since the target is logged |
248 | * in by definition. |
249 | * |
250 | * @since 1.36 Moved up from DatabaseBlock |
251 | * @param bool|null $x |
252 | * @return bool |
253 | */ |
254 | public function isHardblock( $x = null ): bool { |
255 | wfSetVar( $this->isHardblock, $x ); |
256 | |
257 | return $this->getType() == self::TYPE_USER |
258 | ? true |
259 | : $this->isHardblock; |
260 | } |
261 | |
262 | /** |
263 | * Determine whether the block prevents a given right. A right may be |
264 | * allowed or disallowed by default, or determined from a property on the |
265 | * block object. For certain rights, the property may be overridden |
266 | * according to global configs. |
267 | * |
268 | * @since 1.33 |
269 | * @param string $right |
270 | * @return bool|null The block applies to the right, or null if |
271 | * unsure (e.g. unrecognized right or unset property) |
272 | */ |
273 | public function appliesToRight( $right ) { |
274 | $blockDisablesLogin = MediaWikiServices::getInstance()->getMainConfig() |
275 | ->get( MainConfigNames::BlockDisablesLogin ); |
276 | |
277 | $res = null; |
278 | switch ( $right ) { |
279 | case 'autocreateaccount': |
280 | case 'createaccount': |
281 | $res = $this->isCreateAccountBlocked(); |
282 | break; |
283 | case 'sendemail': |
284 | $res = $this->isEmailBlocked(); |
285 | break; |
286 | case 'upload': |
287 | // Sitewide blocks always block upload. This may be overridden in a subclass. |
288 | $res = $this->isSitewide(); |
289 | break; |
290 | case 'read': |
291 | $res = false; |
292 | break; |
293 | } |
294 | if ( !$res && $blockDisablesLogin ) { |
295 | // If a block would disable login, then it should |
296 | // prevent any right that all users cannot do |
297 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
298 | $anon = MediaWikiServices::getInstance()->getUserFactory()->newAnonymous(); |
299 | $res = $permissionManager->userHasRight( $anon, $right ) ? $res : true; |
300 | } |
301 | |
302 | return $res; |
303 | } |
304 | |
305 | public function getTarget(): ?BlockTarget { |
306 | return $this->target; |
307 | } |
308 | |
309 | public function getRedactedTarget(): ?BlockTarget { |
310 | $target = $this->getTarget(); |
311 | if ( $this->getType() === Block::TYPE_AUTO |
312 | && !( $target instanceof AutoBlockTarget ) |
313 | ) { |
314 | $id = $this->getId( $this->wikiId ); |
315 | if ( $id === null ) { |
316 | throw new LogicException( 'no ID available for autoblock redaction' ); |
317 | } |
318 | $target = new AutoBlockTarget( $id, $this->wikiId ); |
319 | } |
320 | return $target; |
321 | } |
322 | |
323 | /** |
324 | * Get the type of target for this particular block. |
325 | * @return int|null AbstractBlock::TYPE_ constant |
326 | */ |
327 | public function getType(): ?int { |
328 | return $this->target ? $this->target->getType() : null; |
329 | } |
330 | |
331 | /** |
332 | * @since 1.37 |
333 | * @return ?UserIdentity |
334 | */ |
335 | public function getTargetUserIdentity(): ?UserIdentity { |
336 | return $this->target instanceof BlockTargetWithUserIdentity |
337 | ? $this->target->getUserIdentity() : null; |
338 | } |
339 | |
340 | /** |
341 | * @since 1.37 |
342 | * @return string |
343 | */ |
344 | public function getTargetName(): string { |
345 | return (string)$this->target; |
346 | } |
347 | |
348 | /** |
349 | * @param BlockTarget|UserIdentity|string $target |
350 | * |
351 | * @return bool |
352 | * @since 1.37 |
353 | */ |
354 | public function isBlocking( $target ): bool { |
355 | $targetName = $target instanceof UserIdentity |
356 | ? $target->getName() |
357 | : (string)$target; |
358 | |
359 | return $targetName === $this->getTargetName(); |
360 | } |
361 | |
362 | /** |
363 | * Get the block expiry time |
364 | * |
365 | * @since 1.19 |
366 | * @return string |
367 | */ |
368 | public function getExpiry(): string { |
369 | return $this->expiry; |
370 | } |
371 | |
372 | /** @inheritDoc */ |
373 | public function isIndefinite(): bool { |
374 | return wfIsInfinity( $this->getExpiry() ); |
375 | } |
376 | |
377 | /** |
378 | * Set the block expiry time |
379 | * |
380 | * @since 1.33 |
381 | * @param string $expiry |
382 | */ |
383 | public function setExpiry( $expiry ) { |
384 | // Force string so getExpiry() return typehint doesn't break things |
385 | $this->expiry = (string)$expiry; |
386 | } |
387 | |
388 | /** |
389 | * Get the timestamp indicating when the block was created |
390 | * |
391 | * @since 1.33 |
392 | * @return string |
393 | */ |
394 | public function getTimestamp(): string { |
395 | return $this->timestamp; |
396 | } |
397 | |
398 | /** |
399 | * Set the timestamp indicating when the block was created |
400 | * |
401 | * @since 1.33 |
402 | * @param string $timestamp |
403 | */ |
404 | public function setTimestamp( $timestamp ) { |
405 | // Force string so getTimestamp() return typehint doesn't break things |
406 | $this->timestamp = (string)$timestamp; |
407 | } |
408 | |
409 | /** |
410 | * Set the target for this block |
411 | * @param BlockTarget|string|UserIdentity|null $target Passing UserIdentity|string is deprecated |
412 | * since 1.45. Set the target by passing BlockTarget|null. |
413 | */ |
414 | public function setTarget( $target ) { |
415 | // Small optimization to make this code testable, this is what would happen anyway |
416 | if ( $target === '' || $target === null ) { |
417 | $this->target = null; |
418 | } elseif ( $target instanceof BlockTarget ) { |
419 | $this->assertWiki( $target->getWikiId() ); |
420 | $this->target = $target; |
421 | } else { |
422 | wfDeprecatedMsg( |
423 | 'Passing UserIdentity|string to AbstractBlock::setTarget is deprecated since 1.45', |
424 | '1.45' |
425 | ); |
426 | $parsedTarget = MediaWikiServices::getInstance() |
427 | ->getCrossWikiBlockTargetFactory() |
428 | ->getFactory( $this->wikiId ) |
429 | ->newFromLegacyUnion( $target ); |
430 | $this->assertWiki( $parsedTarget->getWikiId() ); |
431 | $this->target = $parsedTarget; |
432 | } |
433 | } |
434 | |
435 | /** |
436 | * @since 1.38 |
437 | * @return string|false |
438 | */ |
439 | public function getWikiId() { |
440 | return $this->wikiId; |
441 | } |
442 | |
443 | /** |
444 | * Determine whether the block allows the user to edit their own |
445 | * user talk page. This is done separately from |
446 | * AbstractBlock::appliesToRight because there is no right for |
447 | * editing one's own user talk page and because the user's talk |
448 | * page needs to be passed into the block object, which is unaware |
449 | * of the user. |
450 | * |
451 | * The bl_allow_usertalk flag (which corresponds to the property |
452 | * allowUsertalk) is used on sitewide blocks and partial blocks |
453 | * that contain a namespace restriction on the user talk namespace, |
454 | * but do not contain a page restriction on the user's talk page. |
455 | * For all other (i.e. most) partial blocks, the flag is ignored, |
456 | * and the user can always edit their user talk page unless there |
457 | * is a page restriction on their user talk page, in which case |
458 | * they can never edit it. (Ideally the flag would be stored as |
459 | * null in these cases, but the database field isn't nullable.) |
460 | * |
461 | * This method does not validate that the passed in talk page belongs to the |
462 | * block target since the target (an IP) might not be the same as the user's |
463 | * talk page (if they are logged in). |
464 | * |
465 | * @since 1.33 |
466 | * @param Title|null $usertalk The user's user talk page. If null, |
467 | * and if the target is a User, the target's userpage is used |
468 | * @return bool The user can edit their talk page |
469 | */ |
470 | public function appliesToUsertalk( ?Title $usertalk = null ) { |
471 | if ( !$usertalk ) { |
472 | if ( $this->target instanceof BlockTargetWithUserPage ) { |
473 | $usertalk = Title::makeTitle( |
474 | NS_USER_TALK, |
475 | $this->target->getUserPage()->getDBkey() |
476 | ); |
477 | } else { |
478 | throw new InvalidArgumentException( |
479 | '$usertalk must be provided if block target is not a user/IP' |
480 | ); |
481 | } |
482 | } |
483 | |
484 | if ( $usertalk->getNamespace() !== NS_USER_TALK ) { |
485 | throw new InvalidArgumentException( |
486 | '$usertalk must be a user talk page' |
487 | ); |
488 | } |
489 | |
490 | if ( !$this->isSitewide() ) { |
491 | if ( $this->appliesToPage( $usertalk->getArticleID() ) ) { |
492 | return true; |
493 | } |
494 | if ( !$this->appliesToNamespace( NS_USER_TALK ) ) { |
495 | return false; |
496 | } |
497 | } |
498 | |
499 | // This is a type of block which uses the bl_allow_usertalk |
500 | // flag. The flag can still be overridden by global configs. |
501 | if ( !MediaWikiServices::getInstance()->getMainConfig() |
502 | ->get( MainConfigNames::BlockAllowsUTEdit ) |
503 | ) { |
504 | return true; |
505 | } |
506 | return !$this->isUsertalkEditAllowed(); |
507 | } |
508 | |
509 | /** |
510 | * Checks if a block applies to a particular title |
511 | * |
512 | * This check does not consider whether `$this->isUsertalkEditAllowed` |
513 | * returns false, as the identity of the user making the hypothetical edit |
514 | * isn't known here (particularly in the case of IP hard blocks, range |
515 | * blocks, and auto-blocks). |
516 | * |
517 | * @param Title $title |
518 | * @return bool |
519 | */ |
520 | public function appliesToTitle( Title $title ) { |
521 | return $this->isSitewide(); |
522 | } |
523 | |
524 | /** |
525 | * Checks if a block applies to a particular namespace |
526 | * |
527 | * @since 1.33 |
528 | * |
529 | * @param int $ns |
530 | * @return bool |
531 | */ |
532 | public function appliesToNamespace( $ns ) { |
533 | return $this->isSitewide(); |
534 | } |
535 | |
536 | /** |
537 | * Checks if a block applies to a particular page |
538 | * |
539 | * This check does not consider whether `$this->isUsertalkEditAllowed` |
540 | * returns false, as the identity of the user making the hypothetical edit |
541 | * isn't known here (particularly in the case of IP hard blocks, range |
542 | * blocks, and auto-blocks). |
543 | * |
544 | * @since 1.33 |
545 | * |
546 | * @param int $pageId |
547 | * @return bool |
548 | */ |
549 | public function appliesToPage( $pageId ) { |
550 | return $this->isSitewide(); |
551 | } |
552 | |
553 | /** |
554 | * Check if the block prevents a user from resetting their password |
555 | * |
556 | * @since 1.33 |
557 | * @return bool The block blocks password reset |
558 | */ |
559 | public function appliesToPasswordReset() { |
560 | return $this->isCreateAccountBlocked(); |
561 | } |
562 | |
563 | /** |
564 | * @return AbstractBlock[] |
565 | */ |
566 | public function toArray(): array { |
567 | return [ $this ]; |
568 | } |
569 | |
570 | } |