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