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