Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.20% covered (danger)
7.20%
9 / 125
16.13% covered (danger)
16.13%
5 / 31
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractBlock
7.20% covered (danger)
7.20%
9 / 125
16.13% covered (danger)
16.13%
5 / 31
2937.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
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% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getReasonComment
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setReason
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHideName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHideName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isSitewide
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCreateAccountBlocked
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isEmailBlocked
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isUsertalkEditAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isHardblock
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 appliesToRight
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
90
 getTarget
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRedactedTarget
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getTargetUserIdentity
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getTargetName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBlocking
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getExpiry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isIndefinite
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setExpiry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTarget
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 getWikiId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appliesToUsertalk
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
72
 appliesToTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appliesToNamespace
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appliesToPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appliesToPasswordReset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 toArray
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
21namespace MediaWiki\Block;
22
23use InvalidArgumentException;
24use LogicException;
25use MediaWiki\CommentStore\CommentStoreComment;
26use MediaWiki\DAO\WikiAwareEntityTrait;
27use MediaWiki\MainConfigNames;
28use MediaWiki\MediaWikiServices;
29use MediaWiki\Message\Message;
30use MediaWiki\Title\Title;
31use 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 */
38abstract 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}