Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
71.88% covered (warning)
71.88%
23 / 32
CRAP
67.09% covered (warning)
67.09%
106 / 158
AbstractBlock
0.00% covered (danger)
0.00%
0 / 1
72.73% covered (warning)
72.73%
24 / 33
275.52
67.09% covered (warning)
67.09%
106 / 158
 __construct
0.00% covered (danger)
0.00%
0 / 1
2.01
88.89% covered (warning)
88.89%
8 / 9
 getBy
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getByName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getId
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getReason
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setReason
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getHideName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setHideName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isSitewide
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isCreateAccountBlocked
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isEmailBlocked
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isUsertalkEditAllowed
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 appliesToRight
0.00% covered (danger)
0.00%
0 / 1
10.41
84.00% covered (warning)
84.00%
21 / 25
 prevents
0.00% covered (danger)
0.00%
0 / 1
182
0.00% covered (danger)
0.00%
0 / 32
 parseTarget
0.00% covered (danger)
0.00%
0 / 1
9.08
90.00% covered (success)
90.00%
18 / 20
 getType
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTargetAndType
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTarget
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getExpiry
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setExpiry
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getTimestamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setTimestamp
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setTarget
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getBlocker
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setBlocker
0.00% covered (danger)
0.00%
0 / 1
4.37
71.43% covered (warning)
71.43%
5 / 7
 getPermissionsError
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
0 / 0
 getBlockErrorParams
0.00% covered (danger)
0.00%
0 / 1
3.00
94.44% covered (success)
94.44%
17 / 18
 appliesToUsertalk
0.00% covered (danger)
0.00%
0 / 1
14.67
52.94% covered (warning)
52.94%
9 / 17
 appliesToTitle
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 appliesToNamespace
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 appliesToPage
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 shouldTrackWithCookie
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 appliesToPasswordReset
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
namespace MediaWiki\Block;
use IContextSource;
use InvalidArgumentException;
use IP;
use RequestContext;
use Title;
use User;
/**
 * @since 1.34 Factored out from Block.
 */
abstract class AbstractBlock {
    /** @var string */
    public $mReason;
    /** @var string */
    public $mTimestamp;
    /** @var string */
    public $mExpiry = '';
    /** @var bool */
    protected $mBlockEmail = false;
    /** @var bool */
    protected $allowUsertalk = false;
    /** @var bool */
    protected $blockCreateAccount = false;
    /** @var bool */
    public $mHideName = false;
    /** @var User|string */
    protected $target;
    /**
     * @var int Block::TYPE_ constant. After the block has been loaded
     * from the database, this can only be USER, IP or RANGE.
     */
    protected $type;
    /** @var User */
    protected $blocker;
    /** @var bool */
    protected $isSitewide = true;
    # TYPE constants
    const TYPE_USER = 1;
    const TYPE_IP = 2;
    const TYPE_RANGE = 3;
    const TYPE_AUTO = 4;
    const TYPE_ID = 5;
    /**
     * Create a new block with specified parameters on a user, IP or IP range.
     *
     * @param array $options Parameters of the block:
     *     address string|User  Target user name, User object, IP address or IP range
     *     by int               User ID of the blocker
     *     reason string        Reason of the block
     *     timestamp string     The time at which the block comes into effect
     *     byText string        Username of the blocker (for foreign users)
     */
    function __construct( $options = [] ) {
        $defaults = [
            'address'         => '',
            'by'              => null,
            'reason'          => '',
            'timestamp'       => '',
            'byText'          => '',
        ];
        $options += $defaults;
        $this->setTarget( $options['address'] );
        if ( $options['by'] ) {
            # Local user
            $this->setBlocker( User::newFromId( $options['by'] ) );
        } else {
            # Foreign user
            $this->setBlocker( $options['byText'] );
        }
        $this->setReason( $options['reason'] );
        $this->setTimestamp( wfTimestamp( TS_MW, $options['timestamp'] ) );
    }
    /**
     * Get the user id of the blocking sysop
     *
     * @return int (0 for foreign users)
     */
    public function getBy() {
        return $this->getBlocker()->getId();
    }
    /**
     * Get the username of the blocking sysop
     *
     * @return string
     */
    public function getByName() {
        return $this->getBlocker()->getName();
    }
    /**
     * Get the block ID
     * @return int|null
     */
    public function getId() {
        return null;
    }
    /**
     * Get the reason given for creating the block
     *
     * @since 1.33
     * @return string
     */
    public function getReason() {
        return $this->mReason;
    }
    /**
     * Set the reason for creating the block
     *
     * @since 1.33
     * @param string $reason
     */
    public function setReason( $reason ) {
        $this->mReason = $reason;
    }
    /**
     * Get whether the block hides the target's username
     *
     * @since 1.33
     * @return bool The block hides the username
     */
    public function getHideName() {
        return $this->mHideName;
    }
    /**
     * Set whether ths block hides the target's username
     *
     * @since 1.33
     * @param bool $hideName The block hides the username
     */
    public function setHideName( $hideName ) {
        $this->mHideName = $hideName;
    }
    /**
     * Indicates that the block is a sitewide block. This means the user is
     * prohibited from editing any page on the site (other than their own talk
     * page).
     *
     * @since 1.33
     * @param null|bool $x
     * @return bool
     */
    public function isSitewide( $x = null ) {
        return wfSetVar( $this->isSitewide, $x );
    }
    /**
     * Get or set the flag indicating whether this block blocks the target from
     * creating an account. (Note that the flag may be overridden depending on
     * global configs.)
     *
     * @since 1.33
     * @param null|bool $x Value to set (if null, just get the property value)
     * @return bool Value of the property
     */
    public function isCreateAccountBlocked( $x = null ) {
        return wfSetVar( $this->blockCreateAccount, $x );
    }
    /**
     * Get or set the flag indicating whether this block blocks the target from
     * sending emails. (Note that the flag may be overridden depending on
     * global configs.)
     *
     * @since 1.33
     * @param null|bool $x Value to set (if null, just get the property value)
     * @return bool Value of the property
     */
    public function isEmailBlocked( $x = null ) {
        return wfSetVar( $this->mBlockEmail, $x );
    }
    /**
     * Get or set the flag indicating whether this block blocks the target from
     * editing their own user talk page. (Note that the flag may be overridden
     * depending on global configs.)
     *
     * @since 1.33
     * @param null|bool $x Value to set (if null, just get the property value)
     * @return bool Value of the property
     */
    public function isUsertalkEditAllowed( $x = null ) {
        return wfSetVar( $this->allowUsertalk, $x );
    }
    /**
     * Determine whether the Block prevents a given right. A right
     * may be blacklisted or whitelisted, or determined from a
     * property on the Block object. For certain rights, the property
     * may be overridden according to global configs.
     *
     * @since 1.33
     * @param string $right Right to check
     * @return bool|null null if unrecognized right or unset property
     */
    public function appliesToRight( $right ) {
        $config = RequestContext::getMain()->getConfig();
        $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
        $res = null;
        switch ( $right ) {
            case 'edit':
                // TODO: fix this case to return proper value
                $res = true;
                break;
            case 'createaccount':
                $res = $this->isCreateAccountBlocked();
                break;
            case 'sendemail':
                $res = $this->isEmailBlocked();
                break;
            case 'upload':
                // Until T6995 is completed
                $res = $this->isSitewide();
                break;
            case 'read':
                $res = false;
                break;
            case 'purge':
                $res = false;
                break;
        }
        if ( !$res && $blockDisablesLogin ) {
            // If a block would disable login, then it should
            // prevent any right that all users cannot do
            $anon = new User;
            $res = $anon->isAllowed( $right ) ? $res : true;
        }
        return $res;
    }
    /**
     * Get/set whether the Block prevents a given action
     *
     * @deprecated since 1.33, use appliesToRight to determine block
     *  behaviour, and specific methods to get/set properties
     * @param string $action Action to check
     * @param bool|null $x Value for set, or null to just get value
     * @return bool|null Null for unrecognized rights.
     */
    public function prevents( $action, $x = null ) {
        $config = RequestContext::getMain()->getConfig();
        $blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
        $blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
        $res = null;
        switch ( $action ) {
            case 'edit':
                # For now... <evil laugh>
                $res = true;
                break;
            case 'createaccount':
                $res = wfSetVar( $this->blockCreateAccount, $x );
                break;
            case 'sendemail':
                $res = wfSetVar( $this->mBlockEmail, $x );
                break;
            case 'upload':
                // Until T6995 is completed
                $res = $this->isSitewide();
                break;
            case 'editownusertalk':
                // NOTE: this check is not reliable on partial blocks
                // since partially blocked users are always allowed to edit
                // their own talk page unless a restriction exists on the
                // page or User_talk: namespace
                wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
                $res = !$this->isUsertalkEditAllowed();
                // edit own user talk can be disabled by config
                if ( !$blockAllowsUTEdit ) {
                    $res = true;
                }
                break;
            case 'read':
                $res = false;
                break;
            case 'purge':
                $res = false;
                break;
        }
        if ( !$res && $blockDisablesLogin ) {
            // If a block would disable login, then it should
            // prevent any action that all users cannot do
            $anon = new User;
            $res = $anon->isAllowed( $action ) ? $res : true;
        }
        return $res;
    }
    /**
     * From an existing Block, get the target and the type of target.
     * Note that, except for null, it is always safe to treat the target
     * as a string; for User objects this will return User::__toString()
     * which in turn gives User::getName().
     *
     * @param string|int|User|null $target
     * @return array [ User|String|null, Block::TYPE_ constant|null ]
     */
    public static function parseTarget( $target ) {
        # We may have been through this before
        if ( $target instanceof User ) {
            if ( IP::isValid( $target->getName() ) ) {
                return [ $target, self::TYPE_IP ];
            } else {
                return [ $target, self::TYPE_USER ];
            }
        } elseif ( $target === null ) {
            return [ null, null ];
        }
        $target = trim( $target );
        if ( IP::isValid( $target ) ) {
            # We can still create a User if it's an IP address, but we need to turn
            # off validation checking (which would exclude IP addresses)
            return [
                User::newFromName( IP::sanitizeIP( $target ), false ),
                self::TYPE_IP
            ];
        } elseif ( IP::isValidRange( $target ) ) {
            # Can't create a User from an IP range
            return [ IP::sanitizeRange( $target ), self::TYPE_RANGE ];
        }
        # Consider the possibility that this is not a username at all
        # but actually an old subpage (T31797)
        if ( strpos( $target, '/' ) !== false ) {
            # An old subpage, drill down to the user behind it
            $target = explode( '/', $target )[0];
        }
        $userObj = User::newFromName( $target );
        if ( $userObj instanceof User ) {
            # Note that since numbers are valid usernames, a $target of "12345" will be
            # considered a User.  If you want to pass a block ID, prepend a hash "#12345",
            # since hash characters are not valid in usernames or titles generally.
            return [ $userObj, self::TYPE_USER ];
        } elseif ( preg_match( '/^#\d+$/', $target ) ) {
            # Autoblock reference in the form "#12345"
            return [ substr( $target, 1 ), self::TYPE_AUTO ];
        } else {
            return [ null, null ];
        }
    }
    /**
     * Get the type of target for this particular block.
     * @return int Block::TYPE_ constant, will never be TYPE_ID
     */
    public function getType() {
        return $this->type;
    }
    /**
     * Get the target and target type for this particular Block.  Note that for autoblocks,
     * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
     * in this situation.
     * @return array [ User|String, Block::TYPE_ constant ]
     * @todo FIXME: This should be an integral part of the Block member variables
     */
    public function getTargetAndType() {
        return [ $this->getTarget(), $this->getType() ];
    }
    /**
     * Get the target for this particular Block.  Note that for autoblocks,
     * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
     * in this situation.
     * @return User|string
     */
    public function getTarget() {
        return $this->target;
    }
    /**
     * Get the block expiry time
     *
     * @since 1.19
     * @return string
     */
    public function getExpiry() {
        return $this->mExpiry;
    }
    /**
     * Set the block expiry time
     *
     * @since 1.33
     * @param string $expiry
     */
    public function setExpiry( $expiry ) {
        $this->mExpiry = $expiry;
    }
    /**
     * Get the timestamp indicating when the block was created
     *
     * @since 1.33
     * @return string
     */
    public function getTimestamp() {
        return $this->mTimestamp;
    }
    /**
     * Set the timestamp indicating when the block was created
     *
     * @since 1.33
     * @param string $timestamp
     */
    public function setTimestamp( $timestamp ) {
        $this->mTimestamp = $timestamp;
    }
    /**
     * Set the target for this block, and update $this->type accordingly
     * @param mixed $target
     */
    public function setTarget( $target ) {
        list( $this->target, $this->type ) = static::parseTarget( $target );
    }
    /**
     * Get the user who implemented this block
     * @return User User object. May name a foreign user.
     */
    public function getBlocker() {
        return $this->blocker;
    }
    /**
     * Set the user who implemented (or will implement) this block
     * @param User|string $user Local User object or username string
     */
    public function setBlocker( $user ) {
        if ( is_string( $user ) ) {
            $user = User::newFromName( $user, false );
        }
        if ( $user->isAnon() && User::isUsableName( $user->getName() ) ) {
            throw new InvalidArgumentException(
                'Blocker must be a local user or a name that cannot be a local user'
            );
        }
        $this->blocker = $user;
    }
    /**
     * Get the key and parameters for the corresponding error message.
     *
     * @since 1.22
     * @param IContextSource $context
     * @return array
     */
    abstract public function getPermissionsError( IContextSource $context );
    /**
     * Get block information used in different block error messages
     *
     * @since 1.33
     * @param IContextSource $context
     * @return array
     */
    public function getBlockErrorParams( IContextSource $context ) {
        $blocker = $this->getBlocker();
        if ( $blocker instanceof User ) { // local user
            $blockerUserpage = $blocker->getUserPage();
            $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
        } else { // foreign user
            $link = $blocker;
        }
        $reason = $this->getReason();
        if ( $reason == '' ) {
            $reason = $context->msg( 'blockednoreason' )->text();
        }
        /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
         * This could be a username, an IP range, or a single IP. */
        $intended = $this->getTarget();
        $lang = $context->getLanguage();
        return [
            $link,
            $reason,
            $context->getRequest()->getIP(),
            $this->getByName(),
            // TODO: SystemBlock replaces this with the system block type. Clean up
            // error params so that this is not necessary.
            $this->getId(),
            $lang->formatExpiry( $this->getExpiry() ),
            (string)$intended,
            $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
        ];
    }
    /**
     * Determine whether the block allows the user to edit their own
     * user talk page. This is done separately from Block::appliesToRight
     * because there is no right for editing one's own user talk page
     * and because the user's talk page needs to be passed into the
     * Block object, which is unaware of the user.
     *
     * The ipb_allow_usertalk flag (which corresponds to the property
     * allowUsertalk) is used on sitewide blocks and partial blocks
     * that contain a namespace restriction on the user talk namespace,
     * but do not contain a page restriction on the user's talk page.
     * For all other (i.e. most) partial blocks, the flag is ignored,
     * and the user can always edit their user talk page unless there
     * is a page restriction on their user talk page, in which case
     * they can never edit it. (Ideally the flag would be stored as
     * null in these cases, but the database field isn't nullable.)
     *
     * This method does not validate that the passed in talk page belongs to the
     * block target since the target (an IP) might not be the same as the user's
     * talk page (if they are logged in).
     *
     * @since 1.33
     * @param Title|null $usertalk The user's user talk page. If null,
     *  and if the target is a User, the target's userpage is used
     * @return bool The user can edit their talk page
     */
    public function appliesToUsertalk( Title $usertalk = null ) {
        if ( !$usertalk ) {
            if ( $this->target instanceof User ) {
                $usertalk = $this->target->getTalkPage();
            } else {
                throw new InvalidArgumentException(
                    '$usertalk must be provided if block target is not a user/IP'
                );
            }
        }
        if ( $usertalk->getNamespace() !== NS_USER_TALK ) {
            throw new InvalidArgumentException(
                '$usertalk must be a user talk page'
            );
        }
        if ( !$this->isSitewide() ) {
            if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
                return true;
            }
            if ( !$this->appliesToNamespace( NS_USER_TALK ) ) {
                return false;
            }
        }
        // This is a type of block which uses the ipb_allow_usertalk
        // flag. The flag can still be overridden by global configs.
        $config = RequestContext::getMain()->getConfig();
        if ( !$config->get( 'BlockAllowsUTEdit' ) ) {
            return true;
        }
        return !$this->isUsertalkEditAllowed();
    }
    /**
     * Checks if a block applies to a particular title
     *
     * This check does not consider whether `$this->isUsertalkEditAllowed`
     * returns false, as the identity of the user making the hypothetical edit
     * isn't known here (particularly in the case of IP hardblocks, range
     * blocks, and auto-blocks).
     *
     * @param Title $title
     * @return bool
     */
    public function appliesToTitle( Title $title ) {
        return $this->isSitewide();
    }
    /**
     * Checks if a block applies to a particular namespace
     *
     * @since 1.33
     *
     * @param int $ns
     * @return bool
     */
    public function appliesToNamespace( $ns ) {
        return $this->isSitewide();
    }
    /**
     * Checks if a block applies to a particular page
     *
     * This check does not consider whether `$this->isUsertalkEditAllowed`
     * returns false, as the identity of the user making the hypothetical edit
     * isn't known here (particularly in the case of IP hardblocks, range
     * blocks, and auto-blocks).
     *
     * @since 1.33
     *
     * @param int $pageId
     * @return bool
     */
    public function appliesToPage( $pageId ) {
        return $this->isSitewide();
    }
    /**
     * Check if the block should be tracked with a cookie.
     *
     * @since 1.33
     * @param bool $isAnon The user is logged out
     * @return bool The block should be tracked with a cookie
     */
    public function shouldTrackWithCookie( $isAnon ) {
        return false;
    }
    /**
     * Check if the block prevents a user from resetting their password
     *
     * @since 1.33
     * @return bool The block blocks password reset
     */
    public function appliesToPasswordReset() {
        return $this->isCreateAccountBlocked();
    }
}