* The getSubjectPage, getTalkPage, and getOtherPage of Title are deprecated.
Use NamespaceInfo's getSubjectPage, getTalkPage, and getAssociatedPage.
* MWMessagePack class, no longer used, has been deprecated in 1.34.
-* The Block class is separated into Block (for blocks stored in the database),
- and SystemBlock (for temporary blocks created by the system). SystemBlock
- should be used when creating any temporary blocks.
+* The Block class is separated into DatabaseBlock (for blocks stored in the
+ database), and SystemBlock (for temporary blocks created by the system).
+ SystemBlock should be used when creating any temporary blocks. Block is
+ a deprecated alias for DatabaseBlock.
* Parser::$mConf is deprecated. It will be removed entirely in a later version.
Some context can be found at T224165.
* Constructing Parser directly is deprecated. Obtain one from ParserFactory.
'BitmapHandler_ClientOnly' => __DIR__ . '/includes/media/BitmapHandler_ClientOnly.php',
'BitmapMetadataHandler' => __DIR__ . '/includes/media/BitmapMetadataHandler.php',
'Blob' => __DIR__ . '/includes/libs/rdbms/encasing/Blob.php',
- 'Block' => __DIR__ . '/includes/Block.php',
+ 'Block' => __DIR__ . '/includes/block/DatabaseBlock.php',
'BlockLevelPass' => __DIR__ . '/includes/parser/BlockLevelPass.php',
'BlockListPager' => __DIR__ . '/includes/specials/pagers/BlockListPager.php',
'BlockLogFormatter' => __DIR__ . '/includes/logging/BlockLogFormatter.php',
+++ /dev/null
-<?php
-/**
- * Class for blocks stored in the database.
- *
- * 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
- */
-
-use Wikimedia\Rdbms\Database;
-use Wikimedia\Rdbms\IDatabase;
-use MediaWiki\Block\AbstractBlock;
-use MediaWiki\Block\BlockRestrictionStore;
-use MediaWiki\Block\Restriction\Restriction;
-use MediaWiki\Block\Restriction\NamespaceRestriction;
-use MediaWiki\Block\Restriction\PageRestriction;
-use MediaWiki\MediaWikiServices;
-
-/**
- * Blocks (as opposed to system blocks) are stored in the database, may
- * give rise to autoblocks and may be tracked with cookies. Blocks are
- * more customizable than system blocks: they may be hardblocks, and
- * they may be sitewide or partial.
- */
-class Block extends AbstractBlock {
- /** @var bool */
- public $mAuto;
-
- /** @var int */
- public $mParentBlockId;
-
- /** @var int */
- private $mId;
-
- /** @var bool */
- private $mFromMaster;
-
- /** @var int Hack for foreign blocking (CentralAuth) */
- private $forcedTargetID;
-
- /** @var bool */
- private $isHardblock;
-
- /** @var bool */
- private $isAutoblocking;
-
- /** @var Restriction[] */
- private $restrictions;
-
- /**
- * Create a new block with specified option parameters on a user, IP or IP range.
- *
- * @param array $options Parameters of the block:
- * user int Override target user ID (for foreign users)
- * auto bool Is this an automatic block?
- * expiry string Timestamp of expiration of the block or 'infinity'
- * anonOnly bool Only disallow anonymous actions
- * createAccount bool Disallow creation of new accounts
- * enableAutoblock bool Enable automatic blocking
- * hideName bool Hide the target user name
- * blockEmail bool Disallow sending emails
- * allowUsertalk bool Allow the target to edit its own talk page
- * sitewide bool Disallow editing all pages and all contribution
- * actions, except those specifically allowed by
- * other block flags
- *
- * @since 1.26 $options array
- */
- public function __construct( array $options = [] ) {
- parent::__construct( $options );
-
- $defaults = [
- 'user' => null,
- 'auto' => false,
- 'expiry' => '',
- 'anonOnly' => false,
- 'createAccount' => false,
- 'enableAutoblock' => false,
- 'hideName' => false,
- 'blockEmail' => false,
- 'allowUsertalk' => false,
- 'sitewide' => true,
- ];
-
- $options += $defaults;
-
- if ( $this->target instanceof User && $options['user'] ) {
- # Needed for foreign users
- $this->forcedTargetID = $options['user'];
- }
-
- $this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
-
- # Boolean settings
- $this->mAuto = (bool)$options['auto'];
- $this->setHideName( (bool)$options['hideName'] );
- $this->isHardblock( !$options['anonOnly'] );
- $this->isAutoblocking( (bool)$options['enableAutoblock'] );
- $this->isSitewide( (bool)$options['sitewide'] );
- $this->isEmailBlocked( (bool)$options['blockEmail'] );
- $this->isCreateAccountBlocked( (bool)$options['createAccount'] );
- $this->isUsertalkEditAllowed( (bool)$options['allowUsertalk'] );
-
- $this->mFromMaster = false;
- }
-
- /**
- * Load a block from the block id.
- *
- * @param int $id Block id to search for
- * @return Block|null
- */
- public static function newFromID( $id ) {
- $dbr = wfGetDB( DB_REPLICA );
- $blockQuery = self::getQueryInfo();
- $res = $dbr->selectRow(
- $blockQuery['tables'],
- $blockQuery['fields'],
- [ 'ipb_id' => $id ],
- __METHOD__,
- [],
- $blockQuery['joins']
- );
- if ( $res ) {
- return self::newFromRow( $res );
- } else {
- return null;
- }
- }
-
- /**
- * Return the list of ipblocks fields that should be selected to create
- * a new block.
- * @deprecated since 1.31, use self::getQueryInfo() instead.
- * @return array
- */
- public static function selectFields() {
- global $wgActorTableSchemaMigrationStage;
-
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
- // If code is using this instead of self::getQueryInfo(), there's a
- // decent chance it's going to try to directly access
- // $row->ipb_by or $row->ipb_by_text and we can't give it
- // useful values here once those aren't being used anymore.
- throw new BadMethodCallException(
- 'Cannot use ' . __METHOD__
- . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
- );
- }
-
- wfDeprecated( __METHOD__, '1.31' );
- return [
- 'ipb_id',
- 'ipb_address',
- 'ipb_by',
- 'ipb_by_text',
- 'ipb_by_actor' => 'NULL',
- 'ipb_timestamp',
- 'ipb_auto',
- 'ipb_anon_only',
- 'ipb_create_account',
- 'ipb_enable_autoblock',
- 'ipb_expiry',
- 'ipb_deleted',
- 'ipb_block_email',
- 'ipb_allow_usertalk',
- 'ipb_parent_block_id',
- 'ipb_sitewide',
- ] + CommentStore::getStore()->getFields( 'ipb_reason' );
- }
-
- /**
- * Return the tables, fields, and join conditions to be selected to create
- * a new block object.
- * @since 1.31
- * @return array With three keys:
- * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
- * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
- * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
- */
- public static function getQueryInfo() {
- $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
- $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
- return [
- 'tables' => [ 'ipblocks' ] + $commentQuery['tables'] + $actorQuery['tables'],
- 'fields' => [
- 'ipb_id',
- 'ipb_address',
- 'ipb_timestamp',
- 'ipb_auto',
- 'ipb_anon_only',
- 'ipb_create_account',
- 'ipb_enable_autoblock',
- 'ipb_expiry',
- 'ipb_deleted',
- 'ipb_block_email',
- 'ipb_allow_usertalk',
- 'ipb_parent_block_id',
- 'ipb_sitewide',
- ] + $commentQuery['fields'] + $actorQuery['fields'],
- 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
- ];
- }
-
- /**
- * Check if two blocks are effectively equal. Doesn't check irrelevant things like
- * the blocking user or the block timestamp, only things which affect the blocked user
- *
- * @param Block $block
- *
- * @return bool
- */
- public function equals( Block $block ) {
- return (
- (string)$this->target == (string)$block->target
- && $this->type == $block->type
- && $this->mAuto == $block->mAuto
- && $this->isHardblock() == $block->isHardblock()
- && $this->isCreateAccountBlocked() == $block->isCreateAccountBlocked()
- && $this->getExpiry() == $block->getExpiry()
- && $this->isAutoblocking() == $block->isAutoblocking()
- && $this->getHideName() == $block->getHideName()
- && $this->isEmailBlocked() == $block->isEmailBlocked()
- && $this->isUsertalkEditAllowed() == $block->isUsertalkEditAllowed()
- && $this->getReason() == $block->getReason()
- && $this->isSitewide() == $block->isSitewide()
- // Block::getRestrictions() may perform a database query, so keep it at
- // the end.
- && $this->getBlockRestrictionStore()->equals(
- $this->getRestrictions(), $block->getRestrictions()
- )
- );
- }
-
- /**
- * Load a block from the database which affects the already-set $this->target:
- * 1) A block directly on the given user or IP
- * 2) A rangeblock encompassing the given IP (smallest first)
- * 3) An autoblock on the given IP
- * @param User|string|null $vagueTarget Also search for blocks affecting this target. Doesn't
- * make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
- * @throws MWException
- * @return bool Whether a relevant block was found
- */
- protected function newLoad( $vagueTarget = null ) {
- $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_REPLICA );
-
- if ( $this->type !== null ) {
- $conds = [
- 'ipb_address' => [ (string)$this->target ],
- ];
- } else {
- $conds = [ 'ipb_address' => [] ];
- }
-
- # Be aware that the != '' check is explicit, since empty values will be
- # passed by some callers (T31116)
- if ( $vagueTarget != '' ) {
- list( $target, $type ) = self::parseTarget( $vagueTarget );
- switch ( $type ) {
- case self::TYPE_USER:
- # Slightly weird, but who are we to argue?
- $conds['ipb_address'][] = (string)$target;
- break;
-
- case self::TYPE_IP:
- $conds['ipb_address'][] = (string)$target;
- $conds[] = self::getRangeCond( IP::toHex( $target ) );
- $conds = $db->makeList( $conds, LIST_OR );
- break;
-
- case self::TYPE_RANGE:
- list( $start, $end ) = IP::parseRange( $target );
- $conds['ipb_address'][] = (string)$target;
- $conds[] = self::getRangeCond( $start, $end );
- $conds = $db->makeList( $conds, LIST_OR );
- break;
-
- default:
- throw new MWException( "Tried to load block with invalid type" );
- }
- }
-
- $blockQuery = self::getQueryInfo();
- $res = $db->select(
- $blockQuery['tables'], $blockQuery['fields'], $conds, __METHOD__, [], $blockQuery['joins']
- );
-
- # This result could contain a block on the user, a block on the IP, and a russian-doll
- # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
- $bestRow = null;
-
- # Lower will be better
- $bestBlockScore = 100;
-
- foreach ( $res as $row ) {
- $block = self::newFromRow( $row );
-
- # Don't use expired blocks
- if ( $block->isExpired() ) {
- continue;
- }
-
- # Don't use anon only blocks on users
- if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) {
- continue;
- }
-
- if ( $block->getType() == self::TYPE_RANGE ) {
- # This is the number of bits that are allowed to vary in the block, give
- # or take some floating point errors
- $target = $block->getTarget();
- $max = IP::isIPv6( $target ) ? 128 : 32;
- list( $network, $bits ) = IP::parseCIDR( $target );
- $size = $max - $bits;
-
- # Rank a range block covering a single IP equally with a single-IP block
- $score = self::TYPE_RANGE - 1 + ( $size / $max );
-
- } else {
- $score = $block->getType();
- }
-
- if ( $score < $bestBlockScore ) {
- $bestBlockScore = $score;
- $bestRow = $row;
- }
- }
-
- if ( $bestRow !== null ) {
- $this->initFromRow( $bestRow );
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Get a set of SQL conditions which will select rangeblocks encompassing a given range
- * @param string $start Hexadecimal IP representation
- * @param string|null $end Hexadecimal IP representation, or null to use $start = $end
- * @return string
- */
- public static function getRangeCond( $start, $end = null ) {
- if ( $end === null ) {
- $end = $start;
- }
- # Per T16634, we want to include relevant active rangeblocks; for
- # rangeblocks, we want to include larger ranges which enclose the given
- # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
- # so we can improve performance by filtering on a LIKE clause
- $chunk = self::getIpFragment( $start );
- $dbr = wfGetDB( DB_REPLICA );
- $like = $dbr->buildLike( $chunk, $dbr->anyString() );
-
- # Fairly hard to make a malicious SQL statement out of hex characters,
- # but stranger things have happened...
- $safeStart = $dbr->addQuotes( $start );
- $safeEnd = $dbr->addQuotes( $end );
-
- return $dbr->makeList(
- [
- "ipb_range_start $like",
- "ipb_range_start <= $safeStart",
- "ipb_range_end >= $safeEnd",
- ],
- LIST_AND
- );
- }
-
- /**
- * Get the component of an IP address which is certain to be the same between an IP
- * address and a rangeblock containing that IP address.
- * @param string $hex Hexadecimal IP representation
- * @return string
- */
- protected static function getIpFragment( $hex ) {
- global $wgBlockCIDRLimit;
- if ( substr( $hex, 0, 3 ) == 'v6-' ) {
- return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
- } else {
- return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
- }
- }
-
- /**
- * Given a database row from the ipblocks table, initialize
- * member variables
- * @param stdClass $row A row from the ipblocks table
- */
- protected function initFromRow( $row ) {
- $this->setTarget( $row->ipb_address );
- $this->setBlocker( User::newFromAnyId(
- $row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ?? null
- ) );
-
- $this->setTimestamp( wfTimestamp( TS_MW, $row->ipb_timestamp ) );
- $this->mAuto = $row->ipb_auto;
- $this->setHideName( $row->ipb_deleted );
- $this->mId = (int)$row->ipb_id;
- $this->mParentBlockId = $row->ipb_parent_block_id;
-
- // I wish I didn't have to do this
- $db = wfGetDB( DB_REPLICA );
- $this->setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
- $this->setReason(
- CommentStore::getStore()
- // Legacy because $row may have come from self::selectFields()
- ->getCommentLegacy( $db, 'ipb_reason', $row )->text
- );
-
- $this->isHardblock( !$row->ipb_anon_only );
- $this->isAutoblocking( $row->ipb_enable_autoblock );
- $this->isSitewide( (bool)$row->ipb_sitewide );
-
- $this->isCreateAccountBlocked( $row->ipb_create_account );
- $this->isEmailBlocked( $row->ipb_block_email );
- $this->isUsertalkEditAllowed( $row->ipb_allow_usertalk );
- }
-
- /**
- * Create a new Block object from a database row
- * @param stdClass $row Row from the ipblocks table
- * @return Block
- */
- public static function newFromRow( $row ) {
- $block = new Block;
- $block->initFromRow( $row );
- return $block;
- }
-
- /**
- * Delete the row from the IP blocks table.
- *
- * @throws MWException
- * @return bool
- */
- public function delete() {
- if ( wfReadOnly() ) {
- return false;
- }
-
- if ( !$this->getId() ) {
- throw new MWException( "Block::delete() requires that the mId member be filled\n" );
- }
-
- $dbw = wfGetDB( DB_MASTER );
-
- $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
- $dbw->delete( 'ipblocks', [ 'ipb_parent_block_id' => $this->getId() ], __METHOD__ );
-
- $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
- $dbw->delete( 'ipblocks', [ 'ipb_id' => $this->getId() ], __METHOD__ );
-
- return $dbw->affectedRows() > 0;
- }
-
- /**
- * Insert a block into the block table. Will fail if there is a conflicting
- * block (same name and options) already in the database.
- *
- * @param IDatabase|null $dbw If you have one available
- * @return bool|array False on failure, assoc array on success:
- * ('id' => block ID, 'autoIds' => array of autoblock IDs)
- */
- public function insert( $dbw = null ) {
- global $wgBlockDisablesLogin;
-
- if ( !$this->getBlocker() || $this->getBlocker()->getName() === '' ) {
- throw new MWException( 'Cannot insert a block without a blocker set' );
- }
-
- wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
-
- if ( $dbw === null ) {
- $dbw = wfGetDB( DB_MASTER );
- }
-
- self::purgeExpired();
-
- $row = $this->getDatabaseArray( $dbw );
-
- $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
- $affected = $dbw->affectedRows();
- if ( $affected ) {
- $this->setId( $dbw->insertId() );
- if ( $this->restrictions ) {
- $this->getBlockRestrictionStore()->insert( $this->restrictions );
- }
- }
-
- # Don't collide with expired blocks.
- # Do this after trying to insert to avoid locking.
- if ( !$affected ) {
- # T96428: The ipb_address index uses a prefix on a field, so
- # use a standard SELECT + DELETE to avoid annoying gap locks.
- $ids = $dbw->selectFieldValues( 'ipblocks',
- 'ipb_id',
- [
- 'ipb_address' => $row['ipb_address'],
- 'ipb_user' => $row['ipb_user'],
- 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
- ],
- __METHOD__
- );
- if ( $ids ) {
- $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
- $this->getBlockRestrictionStore()->deleteByBlockId( $ids );
- $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
- $affected = $dbw->affectedRows();
- $this->setId( $dbw->insertId() );
- if ( $this->restrictions ) {
- $this->getBlockRestrictionStore()->insert( $this->restrictions );
- }
- }
- }
-
- if ( $affected ) {
- $auto_ipd_ids = $this->doRetroactiveAutoblock();
-
- if ( $wgBlockDisablesLogin && $this->target instanceof User ) {
- // Change user login token to force them to be logged out.
- $this->target->setToken();
- $this->target->saveSettings();
- }
-
- return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
- }
-
- return false;
- }
-
- /**
- * Update a block in the DB with new parameters.
- * The ID field needs to be loaded first.
- *
- * @return bool|array False on failure, array on success:
- * ('id' => block ID, 'autoIds' => array of autoblock IDs)
- */
- public function update() {
- wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" );
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->startAtomic( __METHOD__ );
-
- $result = $dbw->update(
- 'ipblocks',
- $this->getDatabaseArray( $dbw ),
- [ 'ipb_id' => $this->getId() ],
- __METHOD__
- );
-
- // Only update the restrictions if they have been modified.
- if ( $this->restrictions !== null ) {
- // An empty array should remove all of the restrictions.
- if ( empty( $this->restrictions ) ) {
- $success = $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
- } else {
- $success = $this->getBlockRestrictionStore()->update( $this->restrictions );
- }
- // Update the result. The first false is the result, otherwise, true.
- $result = $result && $success;
- }
-
- if ( $this->isAutoblocking() ) {
- // update corresponding autoblock(s) (T50813)
- $dbw->update(
- 'ipblocks',
- $this->getAutoblockUpdateArray( $dbw ),
- [ 'ipb_parent_block_id' => $this->getId() ],
- __METHOD__
- );
-
- // Only update the restrictions if they have been modified.
- if ( $this->restrictions !== null ) {
- $this->getBlockRestrictionStore()->updateByParentBlockId( $this->getId(), $this->restrictions );
- }
- } else {
- // autoblock no longer required, delete corresponding autoblock(s)
- $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
- $dbw->delete(
- 'ipblocks',
- [ 'ipb_parent_block_id' => $this->getId() ],
- __METHOD__
- );
- }
-
- $dbw->endAtomic( __METHOD__ );
-
- if ( $result ) {
- $auto_ipd_ids = $this->doRetroactiveAutoblock();
- return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
- }
-
- return $result;
- }
-
- /**
- * Get an array suitable for passing to $dbw->insert() or $dbw->update()
- * @param IDatabase $dbw
- * @return array
- */
- protected function getDatabaseArray( IDatabase $dbw ) {
- $expiry = $dbw->encodeExpiry( $this->getExpiry() );
-
- if ( $this->forcedTargetID ) {
- $uid = $this->forcedTargetID;
- } else {
- $uid = $this->target instanceof User ? $this->target->getId() : 0;
- }
-
- $a = [
- 'ipb_address' => (string)$this->target,
- 'ipb_user' => $uid,
- 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
- 'ipb_auto' => $this->mAuto,
- 'ipb_anon_only' => !$this->isHardblock(),
- 'ipb_create_account' => $this->isCreateAccountBlocked(),
- 'ipb_enable_autoblock' => $this->isAutoblocking(),
- 'ipb_expiry' => $expiry,
- 'ipb_range_start' => $this->getRangeStart(),
- 'ipb_range_end' => $this->getRangeEnd(),
- 'ipb_deleted' => intval( $this->getHideName() ), // typecast required for SQLite
- 'ipb_block_email' => $this->isEmailBlocked(),
- 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
- 'ipb_parent_block_id' => $this->mParentBlockId,
- 'ipb_sitewide' => $this->isSitewide(),
- ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
- + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
-
- return $a;
- }
-
- /**
- * @param IDatabase $dbw
- * @return array
- */
- protected function getAutoblockUpdateArray( IDatabase $dbw ) {
- return [
- 'ipb_create_account' => $this->isCreateAccountBlocked(),
- 'ipb_deleted' => (int)$this->getHideName(), // typecast required for SQLite
- 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
- 'ipb_sitewide' => $this->isSitewide(),
- ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
- + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
- }
-
- /**
- * Retroactively autoblocks the last IP used by the user (if it is a user)
- * blocked by this Block.
- *
- * @return array Block IDs of retroactive autoblocks made
- */
- protected function doRetroactiveAutoblock() {
- $blockIds = [];
- # If autoblock is enabled, autoblock the LAST IP(s) used
- if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
- wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
-
- $continue = Hooks::run(
- 'PerformRetroactiveAutoblock', [ $this, &$blockIds ] );
-
- if ( $continue ) {
- self::defaultRetroactiveAutoblock( $this, $blockIds );
- }
- }
- return $blockIds;
- }
-
- /**
- * Retroactively autoblocks the last IP used by the user (if it is a user)
- * blocked by this Block. This will use the recentchanges table.
- *
- * @param Block $block
- * @param array &$blockIds
- */
- protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) {
- global $wgPutIPinRC;
-
- // No IPs are in recentchanges table, so nothing to select
- if ( !$wgPutIPinRC ) {
- return;
- }
-
- // Autoblocks only apply to TYPE_USER
- if ( $block->getType() !== self::TYPE_USER ) {
- return;
- }
- $target = $block->getTarget(); // TYPE_USER => always a User object
-
- $dbr = wfGetDB( DB_REPLICA );
- $rcQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $target, false );
-
- $options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
-
- // Just the last IP used.
- $options['LIMIT'] = 1;
-
- $res = $dbr->select(
- [ 'recentchanges' ] + $rcQuery['tables'],
- [ 'rc_ip' ],
- $rcQuery['conds'],
- __METHOD__,
- $options,
- $rcQuery['joins']
- );
-
- if ( !$res->numRows() ) {
- # No results, don't autoblock anything
- wfDebug( "No IP found to retroactively autoblock\n" );
- } else {
- foreach ( $res as $row ) {
- if ( $row->rc_ip ) {
- $id = $block->doAutoblock( $row->rc_ip );
- if ( $id ) {
- $blockIds[] = $id;
- }
- }
- }
- }
- }
-
- /**
- * Checks whether a given IP is on the autoblock whitelist.
- * TODO: this probably belongs somewhere else, but not sure where...
- *
- * @param string $ip The IP to check
- * @return bool
- */
- public static function isWhitelistedFromAutoblocks( $ip ) {
- // Try to get the autoblock_whitelist from the cache, as it's faster
- // than getting the msg raw and explode()'ing it.
- $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
- $lines = $cache->getWithSetCallback(
- $cache->makeKey( 'ip-autoblock', 'whitelist' ),
- $cache::TTL_DAY,
- function ( $curValue, &$ttl, array &$setOpts ) {
- $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
-
- return explode( "\n",
- wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() );
- }
- );
-
- wfDebug( "Checking the autoblock whitelist..\n" );
-
- foreach ( $lines as $line ) {
- # List items only
- if ( substr( $line, 0, 1 ) !== '*' ) {
- continue;
- }
-
- $wlEntry = substr( $line, 1 );
- $wlEntry = trim( $wlEntry );
-
- wfDebug( "Checking $ip against $wlEntry..." );
-
- # Is the IP in this range?
- if ( IP::isInRange( $ip, $wlEntry ) ) {
- wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" );
- return true;
- } else {
- wfDebug( " No match\n" );
- }
- }
-
- return false;
- }
-
- /**
- * Autoblocks the given IP, referring to this Block.
- *
- * @param string $autoblockIP The IP to autoblock.
- * @return int|bool Block ID if an autoblock was inserted, false if not.
- */
- public function doAutoblock( $autoblockIP ) {
- # If autoblocks are disabled, go away.
- if ( !$this->isAutoblocking() ) {
- return false;
- }
-
- # Check for presence on the autoblock whitelist.
- if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
- return false;
- }
-
- // Avoid PHP 7.1 warning of passing $this by reference
- $block = $this;
- # Allow hooks to cancel the autoblock.
- if ( !Hooks::run( 'AbortAutoblock', [ $autoblockIP, &$block ] ) ) {
- wfDebug( "Autoblock aborted by hook.\n" );
- return false;
- }
-
- # It's okay to autoblock. Go ahead and insert/update the block...
-
- # Do not add a *new* block if the IP is already blocked.
- $ipblock = self::newFromTarget( $autoblockIP );
- if ( $ipblock ) {
- # Check if the block is an autoblock and would exceed the user block
- # if renewed. If so, do nothing, otherwise prolong the block time...
- if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
- $this->getExpiry() > self::getAutoblockExpiry( $ipblock->getTimestamp() )
- ) {
- # Reset block timestamp to now and its expiry to
- # $wgAutoblockExpiry in the future
- $ipblock->updateTimestamp();
- }
- return false;
- }
-
- # Make a new block object with the desired properties.
- $autoblock = new Block;
- wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
- $autoblock->setTarget( $autoblockIP );
- $autoblock->setBlocker( $this->getBlocker() );
- $autoblock->setReason(
- wfMessage( 'autoblocker', $this->getTarget(), $this->getReason() )
- ->inContentLanguage()->plain()
- );
- $timestamp = wfTimestampNow();
- $autoblock->setTimestamp( $timestamp );
- $autoblock->mAuto = 1;
- $autoblock->isCreateAccountBlocked( $this->isCreateAccountBlocked() );
- # Continue suppressing the name if needed
- $autoblock->setHideName( $this->getHideName() );
- $autoblock->isUsertalkEditAllowed( $this->isUsertalkEditAllowed() );
- $autoblock->mParentBlockId = $this->mId;
- $autoblock->isSitewide( $this->isSitewide() );
- $autoblock->setRestrictions( $this->getRestrictions() );
-
- if ( $this->getExpiry() == 'infinity' ) {
- # Original block was indefinite, start an autoblock now
- $autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
- } else {
- # If the user is already blocked with an expiry date, we don't
- # want to pile on top of that.
- $autoblock->setExpiry( min( $this->getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
- }
-
- # Insert the block...
- $status = $autoblock->insert();
- return $status
- ? $status['id']
- : false;
- }
-
- /**
- * Check if a block has expired. Delete it if it is.
- * @return bool
- */
- public function deleteIfExpired() {
- if ( $this->isExpired() ) {
- wfDebug( "Block::deleteIfExpired() -- deleting\n" );
- $this->delete();
- $retVal = true;
- } else {
- wfDebug( "Block::deleteIfExpired() -- not expired\n" );
- $retVal = false;
- }
-
- return $retVal;
- }
-
- /**
- * Has the block expired?
- * @return bool
- */
- public function isExpired() {
- $timestamp = wfTimestampNow();
- wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" );
-
- if ( !$this->getExpiry() ) {
- return false;
- } else {
- return $timestamp > $this->getExpiry();
- }
- }
-
- /**
- * Is the block address valid (i.e. not a null string?)
- *
- * @deprecated since 1.33 No longer needed in core.
- * @return bool
- */
- public function isValid() {
- wfDeprecated( __METHOD__, '1.33' );
- return $this->getTarget() != null;
- }
-
- /**
- * Update the timestamp on autoblocks.
- */
- public function updateTimestamp() {
- if ( $this->mAuto ) {
- $this->setTimestamp( wfTimestamp() );
- $this->setExpiry( self::getAutoblockExpiry( $this->getTimestamp() ) );
-
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'ipblocks',
- [ /* SET */
- 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
- 'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
- ],
- [ /* WHERE */
- 'ipb_id' => $this->getId(),
- ],
- __METHOD__
- );
- }
- }
-
- /**
- * Get the IP address at the start of the range in Hex form
- * @throws MWException
- * @return string IP in Hex form
- */
- public function getRangeStart() {
- switch ( $this->type ) {
- case self::TYPE_USER:
- return '';
- case self::TYPE_IP:
- return IP::toHex( $this->target );
- case self::TYPE_RANGE:
- list( $start, /*...*/ ) = IP::parseRange( $this->target );
- return $start;
- default:
- throw new MWException( "Block with invalid type" );
- }
- }
-
- /**
- * Get the IP address at the end of the range in Hex form
- * @throws MWException
- * @return string IP in Hex form
- */
- public function getRangeEnd() {
- switch ( $this->type ) {
- case self::TYPE_USER:
- return '';
- case self::TYPE_IP:
- return IP::toHex( $this->target );
- case self::TYPE_RANGE:
- list( /*...*/, $end ) = IP::parseRange( $this->target );
- return $end;
- default:
- throw new MWException( "Block with invalid type" );
- }
- }
-
- /**
- * @inheritDoc
- */
- public function getId() {
- return $this->mId;
- }
-
- /**
- * Set the block ID
- *
- * @param int $blockId
- * @return self
- */
- private function setId( $blockId ) {
- $this->mId = (int)$blockId;
-
- if ( is_array( $this->restrictions ) ) {
- $this->restrictions = $this->getBlockRestrictionStore()->setBlockId(
- $blockId, $this->restrictions
- );
- }
-
- return $this;
- }
-
- /**
- * Get/set a flag determining whether the master is used for reads
- *
- * @param bool|null $x
- * @return bool
- */
- public function fromMaster( $x = null ) {
- return wfSetVar( $this->mFromMaster, $x );
- }
-
- /**
- * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range)
- * @param bool|null $x
- * @return bool
- */
- public function isHardblock( $x = null ) {
- wfSetVar( $this->isHardblock, $x );
-
- # You can't *not* hardblock a user
- return $this->getType() == self::TYPE_USER
- ? true
- : $this->isHardblock;
- }
-
- /**
- * @param null|bool $x
- * @return bool
- */
- public function isAutoblocking( $x = null ) {
- wfSetVar( $this->isAutoblocking, $x );
-
- # You can't put an autoblock on an IP or range as we don't have any history to
- # look over to get more IPs from
- return $this->getType() == self::TYPE_USER
- ? $this->isAutoblocking
- : false;
- }
-
- /**
- * Get the block name, but with autoblocked IPs hidden as per standard privacy policy
- * @return string Text is escaped
- */
- public function getRedactedName() {
- if ( $this->mAuto ) {
- return Html::element(
- 'span',
- [ 'class' => 'mw-autoblockid' ],
- wfMessage( 'autoblockid', $this->mId )->text()
- );
- } else {
- return htmlspecialchars( $this->getTarget() );
- }
- }
-
- /**
- * Get a timestamp of the expiry for autoblocks
- *
- * @param string|int $timestamp
- * @return string
- */
- public static function getAutoblockExpiry( $timestamp ) {
- global $wgAutoblockExpiry;
-
- return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
- }
-
- /**
- * Purge expired blocks from the ipblocks table
- */
- public static function purgeExpired() {
- if ( wfReadOnly() ) {
- return;
- }
-
- DeferredUpdates::addUpdate( new AutoCommitUpdate(
- wfGetDB( DB_MASTER ),
- __METHOD__,
- function ( IDatabase $dbw, $fname ) {
- $ids = $dbw->selectFieldValues( 'ipblocks',
- 'ipb_id',
- [ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
- $fname
- );
- if ( $ids ) {
- $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
- $blockRestrictionStore->deleteByBlockId( $ids );
-
- $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
- }
- }
- ) );
- }
-
- /**
- * Given a target and the target's type, get an existing Block object if possible.
- * @param string|User|int $specificTarget A block target, which may be one of several types:
- * * A user to block, in which case $target will be a User
- * * An IP to block, in which case $target will be a User generated by using
- * User::newFromName( $ip, false ) to turn off name validation
- * * An IP range, in which case $target will be a String "123.123.123.123/18" etc
- * * The ID of an existing block, in the format "#12345" (since pure numbers are valid
- * usernames
- * Calling this with a user, IP address or range will not select autoblocks, and will
- * only select a block where the targets match exactly (so looking for blocks on
- * 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32)
- * @param string|User|int|null $vagueTarget As above, but we will search for *any* block which
- * affects that target (so for an IP address, get ranges containing that IP; and also
- * get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
- * @param bool $fromMaster Whether to use the DB_MASTER database
- * @return Block|null (null if no relevant block could be found). The target and type
- * of the returned Block will refer to the actual block which was found, which might
- * not be the same as the target you gave if you used $vagueTarget!
- */
- public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
- list( $target, $type ) = self::parseTarget( $specificTarget );
- if ( $type == self::TYPE_ID || $type == self::TYPE_AUTO ) {
- return self::newFromID( $target );
-
- } elseif ( $target === null && $vagueTarget == '' ) {
- # We're not going to find anything useful here
- # Be aware that the == '' check is explicit, since empty values will be
- # passed by some callers (T31116)
- return null;
-
- } elseif ( in_array(
- $type,
- [ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE, null ] )
- ) {
- $block = new Block();
- $block->fromMaster( $fromMaster );
-
- if ( $type !== null ) {
- $block->setTarget( $target );
- }
-
- if ( $block->newLoad( $vagueTarget ) ) {
- return $block;
- }
- }
- return null;
- }
-
- /**
- * Get all blocks that match any IP from an array of IP addresses
- *
- * @param array $ipChain List of IPs (strings), usually retrieved from the
- * X-Forwarded-For header of the request
- * @param bool $isAnon Exclude anonymous-only blocks if false
- * @param bool $fromMaster Whether to query the master or replica DB
- * @return array Array of Blocks
- * @since 1.22
- */
- public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
- if ( $ipChain === [] ) {
- return [];
- }
-
- $conds = [];
- $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
- foreach ( array_unique( $ipChain ) as $ipaddr ) {
- # Discard invalid IP addresses. Since XFF can be spoofed and we do not
- # necessarily trust the header given to us, make sure that we are only
- # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
- # Do not treat private IP spaces as special as it may be desirable for wikis
- # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
- if ( !IP::isValid( $ipaddr ) ) {
- continue;
- }
- # Don't check trusted IPs (includes local CDNs which will be in every request)
- if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
- continue;
- }
- # Check both the original IP (to check against single blocks), as well as build
- # the clause to check for rangeblocks for the given IP.
- $conds['ipb_address'][] = $ipaddr;
- $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
- }
-
- if ( $conds === [] ) {
- return [];
- }
-
- if ( $fromMaster ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_REPLICA );
- }
- $conds = $db->makeList( $conds, LIST_OR );
- if ( !$isAnon ) {
- $conds = [ $conds, 'ipb_anon_only' => 0 ];
- }
- $blockQuery = self::getQueryInfo();
- $rows = $db->select(
- $blockQuery['tables'],
- array_merge( [ 'ipb_range_start', 'ipb_range_end' ], $blockQuery['fields'] ),
- $conds,
- __METHOD__,
- [],
- $blockQuery['joins']
- );
-
- $blocks = [];
- foreach ( $rows as $row ) {
- $block = self::newFromRow( $row );
- if ( !$block->isExpired() ) {
- $blocks[] = $block;
- }
- }
-
- return $blocks;
- }
-
- /**
- * From a list of multiple blocks, find the most exact and strongest Block.
- *
- * The logic for finding the "best" block is:
- * - Blocks that match the block's target IP are preferred over ones in a range
- * - Hardblocks are chosen over softblocks that prevent account creation
- * - Softblocks that prevent account creation are chosen over other softblocks
- * - Other softblocks are chosen over autoblocks
- * - If there are multiple exact or range blocks at the same level, the one chosen
- * is random
- * This should be used when $blocks were retrieved from the user's IP address
- * and $ipChain is populated from the same IP address information.
- *
- * @param array $blocks Array of Block objects
- * @param array $ipChain List of IPs (strings). This is used to determine how "close"
- * a block is to the server, and if a block matches exactly, or is in a range.
- * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2,
- * local-cdn, ...)
- * @throws MWException
- * @return Block|null The "best" block from the list
- */
- public static function chooseBlock( array $blocks, array $ipChain ) {
- if ( $blocks === [] ) {
- return null;
- } elseif ( count( $blocks ) == 1 ) {
- return $blocks[0];
- }
-
- // Sort hard blocks before soft ones and secondarily sort blocks
- // that disable account creation before those that don't.
- usort( $blocks, function ( Block $a, Block $b ) {
- $aWeight = (int)$a->isHardblock() . (int)$a->appliesToRight( 'createaccount' );
- $bWeight = (int)$b->isHardblock() . (int)$b->appliesToRight( 'createaccount' );
- return strcmp( $bWeight, $aWeight ); // highest weight first
- } );
-
- $blocksListExact = [
- 'hard' => false,
- 'disable_create' => false,
- 'other' => false,
- 'auto' => false
- ];
- $blocksListRange = [
- 'hard' => false,
- 'disable_create' => false,
- 'other' => false,
- 'auto' => false
- ];
- $ipChain = array_reverse( $ipChain );
-
- /** @var Block $block */
- foreach ( $blocks as $block ) {
- // Stop searching if we have already have a "better" block. This
- // is why the order of the blocks matters
- if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
- break;
- } elseif ( !$block->appliesToRight( 'createaccount' ) && $blocksListExact['disable_create'] ) {
- break;
- }
-
- foreach ( $ipChain as $checkip ) {
- $checkipHex = IP::toHex( $checkip );
- if ( (string)$block->getTarget() === $checkip ) {
- if ( $block->isHardblock() ) {
- $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
- } elseif ( $block->appliesToRight( 'createaccount' ) ) {
- $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
- } elseif ( $block->mAuto ) {
- $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
- } else {
- $blocksListExact['other'] = $blocksListExact['other'] ?: $block;
- }
- // We found closest exact match in the ip list, so go to the next Block
- break;
- } elseif ( array_filter( $blocksListExact ) == []
- && $block->getRangeStart() <= $checkipHex
- && $block->getRangeEnd() >= $checkipHex
- ) {
- if ( $block->isHardblock() ) {
- $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
- } elseif ( $block->appliesToRight( 'createaccount' ) ) {
- $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
- } elseif ( $block->mAuto ) {
- $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
- } else {
- $blocksListRange['other'] = $blocksListRange['other'] ?: $block;
- }
- break;
- }
- }
- }
-
- if ( array_filter( $blocksListExact ) == [] ) {
- $blocksList = &$blocksListRange;
- } else {
- $blocksList = &$blocksListExact;
- }
-
- $chosenBlock = null;
- if ( $blocksList['hard'] ) {
- $chosenBlock = $blocksList['hard'];
- } elseif ( $blocksList['disable_create'] ) {
- $chosenBlock = $blocksList['disable_create'];
- } elseif ( $blocksList['other'] ) {
- $chosenBlock = $blocksList['other'];
- } elseif ( $blocksList['auto'] ) {
- $chosenBlock = $blocksList['auto'];
- } else {
- throw new MWException( "Proxy block found, but couldn't be classified." );
- }
-
- return $chosenBlock;
- }
-
- /**
- * @inheritDoc
- *
- * Autoblocks have whichever type corresponds to their target, so to detect if a block is an
- * autoblock, we have to check the mAuto property instead.
- */
- public function getType() {
- return $this->mAuto
- ? self::TYPE_AUTO
- : parent::getType();
- }
-
- /**
- * Set the 'BlockID' cookie to this block's ID and expiry time. The cookie's expiry will be
- * the same as the block's, to a maximum of 24 hours.
- *
- * @since 1.29
- *
- * @param WebResponse $response The response on which to set the cookie.
- */
- public function setCookie( WebResponse $response ) {
- // Calculate the default expiry time.
- $maxExpiryTime = wfTimestamp( TS_MW, wfTimestamp() + ( 24 * 60 * 60 ) );
-
- // Use the Block's expiry time only if it's less than the default.
- $expiryTime = $this->getExpiry();
- if ( $expiryTime === 'infinity' || $expiryTime > $maxExpiryTime ) {
- $expiryTime = $maxExpiryTime;
- }
-
- // Set the cookie. Reformat the MediaWiki datetime as a Unix timestamp for the cookie.
- $expiryValue = DateTime::createFromFormat( 'YmdHis', $expiryTime )->format( 'U' );
- $cookieOptions = [ 'httpOnly' => false ];
- $cookieValue = $this->getCookieValue();
- $response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
- }
-
- /**
- * Unset the 'BlockID' cookie.
- *
- * @since 1.29
- *
- * @param WebResponse $response The response on which to unset the cookie.
- */
- public static function clearCookie( WebResponse $response ) {
- $response->clearCookie( 'BlockID', [ 'httpOnly' => false ] );
- }
-
- /**
- * Get the BlockID cookie's value for this block. This is usually the block ID concatenated
- * with an HMAC in order to avoid spoofing (T152951), but if wgSecretKey is not set will just
- * be the block ID.
- *
- * @since 1.29
- *
- * @return string The block ID, probably concatenated with "!" and the HMAC.
- */
- public function getCookieValue() {
- $config = RequestContext::getMain()->getConfig();
- $id = $this->getId();
- $secretKey = $config->get( 'SecretKey' );
- if ( !$secretKey ) {
- // If there's no secret key, don't append a HMAC.
- return $id;
- }
- $hmac = MWCryptHash::hmac( $id, $secretKey, false );
- $cookieValue = $id . '!' . $hmac;
- return $cookieValue;
- }
-
- /**
- * Get the stored ID from the 'BlockID' cookie. The cookie's value is usually a combination of
- * the ID and a HMAC (see Block::setCookie), but will sometimes only be the ID.
- *
- * @since 1.29
- *
- * @param string $cookieValue The string in which to find the ID.
- *
- * @return int|null The block ID, or null if the HMAC is present and invalid.
- */
- public static function getIdFromCookieValue( $cookieValue ) {
- // Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
- $bangPos = strpos( $cookieValue, '!' );
- $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
- // Get the site-wide secret key.
- $config = RequestContext::getMain()->getConfig();
- $secretKey = $config->get( 'SecretKey' );
- if ( !$secretKey ) {
- // If there's no secret key, just use the ID as given.
- return $id;
- }
- $storedHmac = substr( $cookieValue, $bangPos + 1 );
- $calculatedHmac = MWCryptHash::hmac( $id, $secretKey, false );
- if ( $calculatedHmac === $storedHmac ) {
- return $id;
- } else {
- return null;
- }
- }
-
- /**
- * @inheritDoc
- *
- * Build different messages for autoblocks and partial blocks.
- */
- public function getPermissionsError( IContextSource $context ) {
- $params = $this->getBlockErrorParams( $context );
-
- $msg = 'blockedtext';
- if ( $this->mAuto ) {
- $msg = 'autoblockedtext';
- } elseif ( !$this->isSitewide() ) {
- $msg = 'blockedtext-partial';
- }
-
- array_unshift( $params, $msg );
-
- return $params;
- }
-
- /**
- * Get Restrictions.
- *
- * Getting the restrictions will perform a database query if the restrictions
- * are not already loaded.
- *
- * @since 1.33
- * @return Restriction[]
- */
- public function getRestrictions() {
- if ( $this->restrictions === null ) {
- // If the block id has not been set, then do not attempt to load the
- // restrictions.
- if ( !$this->mId ) {
- return [];
- }
- $this->restrictions = $this->getBlockRestrictionStore()->loadByBlockId( $this->mId );
- }
-
- return $this->restrictions;
- }
-
- /**
- * Set Restrictions.
- *
- * @since 1.33
- * @param Restriction[] $restrictions
- * @return self
- */
- public function setRestrictions( array $restrictions ) {
- $this->restrictions = array_filter( $restrictions, function ( $restriction ) {
- return $restriction instanceof Restriction;
- } );
-
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function appliesToTitle( Title $title ) {
- if ( $this->isSitewide() ) {
- return true;
- }
-
- $restrictions = $this->getRestrictions();
- foreach ( $restrictions as $restriction ) {
- if ( $restriction->matches( $title ) ) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * @inheritDoc
- */
- public function appliesToNamespace( $ns ) {
- if ( $this->isSitewide() ) {
- return true;
- }
-
- // Blocks do not apply to virtual namespaces.
- if ( $ns < 0 ) {
- return false;
- }
-
- $restriction = $this->findRestriction( NamespaceRestriction::TYPE, $ns );
-
- return (bool)$restriction;
- }
-
- /**
- * @inheritDoc
- */
- public function appliesToPage( $pageId ) {
- if ( $this->isSitewide() ) {
- return true;
- }
-
- // If the pageId is not over zero, the block cannot apply to it.
- if ( $pageId <= 0 ) {
- return false;
- }
-
- $restriction = $this->findRestriction( PageRestriction::TYPE, $pageId );
-
- return (bool)$restriction;
- }
-
- /**
- * Find Restriction by type and value.
- *
- * @param string $type
- * @param int $value
- * @return Restriction|null
- */
- private function findRestriction( $type, $value ) {
- $restrictions = $this->getRestrictions();
- foreach ( $restrictions as $restriction ) {
- if ( $restriction->getType() !== $type ) {
- continue;
- }
-
- if ( $restriction->getValue() === $value ) {
- return $restriction;
- }
- }
-
- return null;
- }
-
- /**
- * @inheritDoc
- */
- public function shouldTrackWithCookie( $isAnon ) {
- $config = RequestContext::getMain()->getConfig();
- switch ( $this->getType() ) {
- case self::TYPE_IP:
- case self::TYPE_RANGE:
- return $isAnon && $config->get( 'CookieSetOnIpBlock' );
- case self::TYPE_USER:
- return !$isAnon && $config->get( 'CookieSetOnAutoblock' ) && $this->isAutoblocking();
- default:
- return false;
- }
- }
-
- /**
- * Get a BlockRestrictionStore instance
- *
- * @return BlockRestrictionStore
- */
- private function getBlockRestrictionStore() : BlockRestrictionStore {
- return MediaWikiServices::getInstance()->getBlockRestrictionStore();
- }
-}
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\EditPage\TextboxBuilder;
use MediaWiki\EditPage\TextConflictHelper;
use MediaWiki\Logger\LoggerFactory;
$username = explode( '/', $this->mTitle->getText(), 2 )[0];
$user = User::newFromName( $username, false /* allow IP users */ );
$ip = User::isIP( $username );
- $block = Block::newFromTarget( $user, $user );
+ $block = DatabaseBlock::newFromTarget( $user, $user );
if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
[ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
} elseif (
!is_null( $block ) &&
- $block->getType() != Block::TYPE_AUTO &&
+ $block->getType() != DatabaseBlock::TYPE_AUTO &&
( $block->isSitewide() || $user->isBlockedFrom( $this->mTitle ) )
) {
// Show log extract if the user is sitewide blocked or is partially
*/
use MediaWiki\Block\AbstractBlock;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use Wikimedia\Rdbms\IDatabase;
*/
public function dieBlocked( AbstractBlock $block ) {
// Die using the appropriate message depending on block type
- if ( $block->getType() == Block::TYPE_AUTO ) {
+ if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
$this->dieWithError(
'apierror-autoblocked',
'autoblocked',
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* API module that facilitates the blocking of users. Requires API write mode
* to be enabled.
// T40633 - if the target is a user (not an IP address), but it
// doesn't exist or is unusable, error.
- if ( $type === Block::TYPE_USER &&
+ if ( $type === DatabaseBlock::TYPE_USER &&
( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $params['user'] ) )
) {
$this->dieWithError( [ 'nosuchusershort', $params['user'] ], 'nosuchuser' );
$res['user'] = $params['user'];
$res['userID'] = $target instanceof User ? $target->getId() : 0;
- $block = Block::newFromTarget( $target, null, true );
- if ( $block instanceof Block ) {
+ $block = DatabaseBlock::newFromTarget( $target, null, true );
+ if ( $block instanceof DatabaseBlock ) {
$res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' );
$res['id'] = $block->getId();
} else {
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* API module that facilitates the unblocking of users. Requires API write mode
* to be enabled.
'Reason' => $params['reason'],
'Tags' => $params['tags']
];
- $block = Block::newFromTarget( $data['Target'] );
+ $block = DatabaseBlock::newFromTarget( $data['Target'] );
$retval = SpecialUnblock::processUnblock( $data, $this->getContext() );
if ( $retval !== true ) {
$this->dieStatus( $this->errorArrayToStatus( $retval ) );
}
$res['id'] = $block->getId();
- $target = $block->getType() == Block::TYPE_AUTO ? '' : $block->getTarget();
+ $target = $block->getType() == DatabaseBlock::TYPE_AUTO ? '' : $block->getTarget();
$res['user'] = $target instanceof User ? $target->getName() : $target;
$res['userid'] = $target instanceof User ? $target->getId() : 0;
$res['reason'] = $params['reason'];
namespace MediaWiki\Auth;
use Config;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
$block->getByName()
];
- if ( $block->getType() === \Block::TYPE_RANGE ) {
+ if ( $block->getType() === DatabaseBlock::TYPE_RANGE ) {
$errorMessage = 'cantcreateaccount-range-text';
$errorParams[] = $this->getRequest()->getIP();
} else {
namespace MediaWiki\Auth;
use Config;
+use MediaWiki\Block\DatabaseBlock;
use StatusValue;
/**
$block->getByName()
];
- if ( $block->getType() === \Block::TYPE_RANGE ) {
+ if ( $block->getType() === DatabaseBlock::TYPE_RANGE ) {
$errorMessage = 'cantcreateaccount-range-text';
$errorParams[] = $this->manager->getRequest()->getIP();
} else {
use User;
/**
- * @since 1.34 Factored out from Block.
+ * @since 1.34 Factored out from DatabaseBlock (previously Block).
*/
abstract class AbstractBlock {
/** @var string */
protected $target;
/**
- * @var int Block::TYPE_ constant. After the block has been loaded
+ * @var int AbstractBlock::TYPE_ constant. After the block has been loaded
* from the database, this can only be USER, IP or RANGE.
*/
protected $type;
}
/**
- * Determine whether the Block prevents a given right. A right
+ * 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
+ * property on the block object. For certain rights, the property
* may be overridden according to global configs.
*
* @since 1.33
}
/**
- * Get/set whether the Block prevents a given action
+ * 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
}
/**
- * From an existing Block, get the target and the type of target.
+ * 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 ]
+ * @return array [ User|String|null, AbstractBlock::TYPE_ constant|null ]
*/
public static function parseTarget( $target ) {
# We may have been through this before
/**
* Get the type of target for this particular block.
- * @return int Block::TYPE_ constant, will never be TYPE_ID
+ * @return int AbstractBlock::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,
+ * 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
+ * @return array [ User|String, AbstractBlock::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,
+ * 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
/**
* 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.
+ * user talk page. This is done separately from
+ * AbstractBlock::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
namespace MediaWiki\Block;
-use Block;
use IP;
use MediaWiki\User\UserIdentity;
use User;
// we should not look for XFF or cookie blocks.
$request = $user->getRequest();
- # We only need to worry about passing the IP address to the Block generator if the
+ # We only need to worry about passing the IP address to the block generator if the
# user is not immune to autoblocks/hardblocks, and they are the current user so we
# know which IP address they're actually coming from
$ip = null;
}
// User/IP blocking
- // TODO: remove dependency on Block
- $block = Block::newFromTarget( $user, $ip, !$fromReplica );
+ // TODO: remove dependency on DatabaseBlock
+ $block = DatabaseBlock::newFromTarget( $user, $ip, !$fromReplica );
// Cookie blocking
if ( !$block instanceof AbstractBlock ) {
$xff = $request->getHeader( 'X-Forwarded-For' );
$xff = array_map( 'trim', explode( ',', $xff ) );
$xff = array_diff( $xff, [ $ip ] );
- // TODO: remove dependency on Block
- $xffblocks = Block::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
- // TODO: remove dependency on Block
- $block = Block::chooseBlock( $xffblocks, $xff );
+ // TODO: remove dependency on DatabaseBlock
+ $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, !$fromReplica );
+ // TODO: remove dependency on DatabaseBlock
+ $block = DatabaseBlock::chooseBlock( $xffblocks, $xff );
if ( $block instanceof AbstractBlock ) {
# Mangle the reason to alert the user that the block
# originated from matching the X-Forwarded-For header.
}
/**
- * Try to load a Block from an ID given in a cookie value.
+ * Try to load a block from an ID given in a cookie value.
*
* @param UserIdentity $user
* @param WebRequest $request
- * @return Block|bool The Block object, or false if none could be loaded.
+ * @return DatabaseBlock|bool The block object, or false if none could be loaded.
*/
private function getBlockFromCookieValue(
UserIdentity $user,
if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
return false;
}
- // Load the Block from the ID in the cookie.
- // TODO: remove dependency on Block
- $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
+ // Load the block from the ID in the cookie.
+ // TODO: remove dependency on DatabaseBlock
+ $blockCookieId = DatabaseBlock::getIdFromCookieValue( $blockCookieVal );
if ( $blockCookieId !== null ) {
// An ID was found in the cookie.
- // TODO: remove dependency on Block
- $tmpBlock = Block::newFromID( $blockCookieId );
- if ( $tmpBlock instanceof Block ) {
+ // TODO: remove dependency on DatabaseBlock
+ $tmpBlock = DatabaseBlock::newFromID( $blockCookieId );
+ if ( $tmpBlock instanceof DatabaseBlock ) {
switch ( $tmpBlock->getType() ) {
- case Block::TYPE_USER:
+ case DatabaseBlock::TYPE_USER:
$blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
$useBlockCookie = ( $this->cookieSetOnAutoblock === true );
break;
- case Block::TYPE_IP:
- case Block::TYPE_RANGE:
+ case DatabaseBlock::TYPE_IP:
+ case DatabaseBlock::TYPE_RANGE:
// If block is type IP or IP range, load only if user is not logged in (T152462)
$blockIsValid = !$tmpBlock->isExpired() && $user->getId() === 0;
$useBlockCookie = ( $this->cookieSetOnIpBlock === true );
}
// If the block is not valid, remove the cookie.
- // TODO: remove dependency on Block
- Block::clearCookie( $response );
+ // TODO: remove dependency on DatabaseBlock
+ DatabaseBlock::clearCookie( $response );
} else {
// If the block doesn't exist, remove the cookie.
- // TODO: remove dependency on Block
- Block::clearCookie( $response );
+ // TODO: remove dependency on DatabaseBlock
+ DatabaseBlock::clearCookie( $response );
}
}
return false;
}
/**
- * Delete the restrictions by Block ID.
+ * Delete the restrictions by block ID.
*
* @since 1.33
* @param int|array $blockId
}
/**
- * Delete the restrictions by Parent Block ID.
+ * Delete the restrictions by parent block ID.
*
* @since 1.33
* @param int|array $parentBlockId
--- /dev/null
+<?php
+/**
+ * Class for blocks stored in the database.
+ *
+ * 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 ActorMigration;
+use AutoCommitUpdate;
+use BadMethodCallException;
+use CommentStore;
+use DateTime;
+use DeferredUpdates;
+use Hooks;
+use Html;
+use IContextSource;
+use IP;
+use MediaWiki\Block\Restriction\NamespaceRestriction;
+use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\Restriction\Restriction;
+use MediaWiki\MediaWikiServices;
+use MWCryptHash;
+use MWException;
+use RequestContext;
+use stdClass;
+use Title;
+use User;
+use WebResponse;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * A DatabaseBlock (unlike a SystemBlock) is stored in the database, may
+ * give rise to autoblocks and may be tracked with cookies. Such blocks
+ * are more customizable than system blocks: they may be hardblocks, and
+ * they may be sitewide or partial.
+ *
+ * @since 1.34 Renamed from Block.
+ */
+class DatabaseBlock extends AbstractBlock {
+ /** @var bool */
+ public $mAuto;
+
+ /** @var int */
+ public $mParentBlockId;
+
+ /** @var int */
+ private $mId;
+
+ /** @var bool */
+ private $mFromMaster;
+
+ /** @var int Hack for foreign blocking (CentralAuth) */
+ private $forcedTargetID;
+
+ /** @var bool */
+ private $isHardblock;
+
+ /** @var bool */
+ private $isAutoblocking;
+
+ /** @var Restriction[] */
+ private $restrictions;
+
+ /**
+ * Create a new block with specified option parameters on a user, IP or IP range.
+ *
+ * @param array $options Parameters of the block:
+ * user int Override target user ID (for foreign users)
+ * auto bool Is this an automatic block?
+ * expiry string Timestamp of expiration of the block or 'infinity'
+ * anonOnly bool Only disallow anonymous actions
+ * createAccount bool Disallow creation of new accounts
+ * enableAutoblock bool Enable automatic blocking
+ * hideName bool Hide the target user name
+ * blockEmail bool Disallow sending emails
+ * allowUsertalk bool Allow the target to edit its own talk page
+ * sitewide bool Disallow editing all pages and all contribution
+ * actions, except those specifically allowed by
+ * other block flags
+ *
+ * @since 1.26 $options array
+ */
+ public function __construct( array $options = [] ) {
+ parent::__construct( $options );
+
+ $defaults = [
+ 'user' => null,
+ 'auto' => false,
+ 'expiry' => '',
+ 'anonOnly' => false,
+ 'createAccount' => false,
+ 'enableAutoblock' => false,
+ 'hideName' => false,
+ 'blockEmail' => false,
+ 'allowUsertalk' => false,
+ 'sitewide' => true,
+ ];
+
+ $options += $defaults;
+
+ if ( $this->target instanceof User && $options['user'] ) {
+ # Needed for foreign users
+ $this->forcedTargetID = $options['user'];
+ }
+
+ $this->setExpiry( wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] ) );
+
+ # Boolean settings
+ $this->mAuto = (bool)$options['auto'];
+ $this->setHideName( (bool)$options['hideName'] );
+ $this->isHardblock( !$options['anonOnly'] );
+ $this->isAutoblocking( (bool)$options['enableAutoblock'] );
+ $this->isSitewide( (bool)$options['sitewide'] );
+ $this->isEmailBlocked( (bool)$options['blockEmail'] );
+ $this->isCreateAccountBlocked( (bool)$options['createAccount'] );
+ $this->isUsertalkEditAllowed( (bool)$options['allowUsertalk'] );
+
+ $this->mFromMaster = false;
+ }
+
+ /**
+ * Load a block from the block id.
+ *
+ * @param int $id id to search for
+ * @return DatabaseBlock|null
+ */
+ public static function newFromID( $id ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $blockQuery = self::getQueryInfo();
+ $res = $dbr->selectRow(
+ $blockQuery['tables'],
+ $blockQuery['fields'],
+ [ 'ipb_id' => $id ],
+ __METHOD__,
+ [],
+ $blockQuery['joins']
+ );
+ if ( $res ) {
+ return self::newFromRow( $res );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the list of ipblocks fields that should be selected to create
+ * a new block.
+ * @deprecated since 1.31, use self::getQueryInfo() instead.
+ * @return array
+ */
+ public static function selectFields() {
+ global $wgActorTableSchemaMigrationStage;
+
+ if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) {
+ // If code is using this instead of self::getQueryInfo(), there's a
+ // decent chance it's going to try to directly access
+ // $row->ipb_by or $row->ipb_by_text and we can't give it
+ // useful values here once those aren't being used anymore.
+ throw new BadMethodCallException(
+ 'Cannot use ' . __METHOD__
+ . ' when $wgActorTableSchemaMigrationStage has SCHEMA_COMPAT_READ_NEW'
+ );
+ }
+
+ wfDeprecated( __METHOD__, '1.31' );
+ return [
+ 'ipb_id',
+ 'ipb_address',
+ 'ipb_by',
+ 'ipb_by_text',
+ 'ipb_by_actor' => 'NULL',
+ 'ipb_timestamp',
+ 'ipb_auto',
+ 'ipb_anon_only',
+ 'ipb_create_account',
+ 'ipb_enable_autoblock',
+ 'ipb_expiry',
+ 'ipb_deleted',
+ 'ipb_block_email',
+ 'ipb_allow_usertalk',
+ 'ipb_parent_block_id',
+ 'ipb_sitewide',
+ ] + CommentStore::getStore()->getFields( 'ipb_reason' );
+ }
+
+ /**
+ * Return the tables, fields, and join conditions to be selected to create
+ * a new block object.
+ * @since 1.31
+ * @return array With three keys:
+ * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+ * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+ * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+ */
+ public static function getQueryInfo() {
+ $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
+ $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
+ return [
+ 'tables' => [ 'ipblocks' ] + $commentQuery['tables'] + $actorQuery['tables'],
+ 'fields' => [
+ 'ipb_id',
+ 'ipb_address',
+ 'ipb_timestamp',
+ 'ipb_auto',
+ 'ipb_anon_only',
+ 'ipb_create_account',
+ 'ipb_enable_autoblock',
+ 'ipb_expiry',
+ 'ipb_deleted',
+ 'ipb_block_email',
+ 'ipb_allow_usertalk',
+ 'ipb_parent_block_id',
+ 'ipb_sitewide',
+ ] + $commentQuery['fields'] + $actorQuery['fields'],
+ 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
+ ];
+ }
+
+ /**
+ * Check if two blocks are effectively equal. Doesn't check irrelevant things like
+ * the blocking user or the block timestamp, only things which affect the blocked user
+ *
+ * @param DatabaseBlock $block
+ * @return bool
+ */
+ public function equals( DatabaseBlock $block ) {
+ return (
+ (string)$this->target == (string)$block->target
+ && $this->type == $block->type
+ && $this->mAuto == $block->mAuto
+ && $this->isHardblock() == $block->isHardblock()
+ && $this->isCreateAccountBlocked() == $block->isCreateAccountBlocked()
+ && $this->getExpiry() == $block->getExpiry()
+ && $this->isAutoblocking() == $block->isAutoblocking()
+ && $this->getHideName() == $block->getHideName()
+ && $this->isEmailBlocked() == $block->isEmailBlocked()
+ && $this->isUsertalkEditAllowed() == $block->isUsertalkEditAllowed()
+ && $this->getReason() == $block->getReason()
+ && $this->isSitewide() == $block->isSitewide()
+ // DatabaseBlock::getRestrictions() may perform a database query, so
+ // keep it at the end.
+ && $this->getBlockRestrictionStore()->equals(
+ $this->getRestrictions(), $block->getRestrictions()
+ )
+ );
+ }
+
+ /**
+ * Load a block from the database which affects the already-set $this->target:
+ * 1) A block directly on the given user or IP
+ * 2) A rangeblock encompassing the given IP (smallest first)
+ * 3) An autoblock on the given IP
+ * @param User|string|null $vagueTarget Also search for blocks affecting this target. Doesn't
+ * make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
+ * @throws MWException
+ * @return bool Whether a relevant block was found
+ */
+ protected function newLoad( $vagueTarget = null ) {
+ $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_REPLICA );
+
+ if ( $this->type !== null ) {
+ $conds = [
+ 'ipb_address' => [ (string)$this->target ],
+ ];
+ } else {
+ $conds = [ 'ipb_address' => [] ];
+ }
+
+ # Be aware that the != '' check is explicit, since empty values will be
+ # passed by some callers (T31116)
+ if ( $vagueTarget != '' ) {
+ list( $target, $type ) = self::parseTarget( $vagueTarget );
+ switch ( $type ) {
+ case self::TYPE_USER:
+ # Slightly weird, but who are we to argue?
+ $conds['ipb_address'][] = (string)$target;
+ break;
+
+ case self::TYPE_IP:
+ $conds['ipb_address'][] = (string)$target;
+ $conds[] = self::getRangeCond( IP::toHex( $target ) );
+ $conds = $db->makeList( $conds, LIST_OR );
+ break;
+
+ case self::TYPE_RANGE:
+ list( $start, $end ) = IP::parseRange( $target );
+ $conds['ipb_address'][] = (string)$target;
+ $conds[] = self::getRangeCond( $start, $end );
+ $conds = $db->makeList( $conds, LIST_OR );
+ break;
+
+ default:
+ throw new MWException( "Tried to load block with invalid type" );
+ }
+ }
+
+ $blockQuery = self::getQueryInfo();
+ $res = $db->select(
+ $blockQuery['tables'], $blockQuery['fields'], $conds, __METHOD__, [], $blockQuery['joins']
+ );
+
+ # This result could contain a block on the user, a block on the IP, and a russian-doll
+ # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
+ $bestRow = null;
+
+ # Lower will be better
+ $bestBlockScore = 100;
+
+ foreach ( $res as $row ) {
+ $block = self::newFromRow( $row );
+
+ # Don't use expired blocks
+ if ( $block->isExpired() ) {
+ continue;
+ }
+
+ # Don't use anon only blocks on users
+ if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) {
+ continue;
+ }
+
+ if ( $block->getType() == self::TYPE_RANGE ) {
+ # This is the number of bits that are allowed to vary in the block, give
+ # or take some floating point errors
+ $target = $block->getTarget();
+ $max = IP::isIPv6( $target ) ? 128 : 32;
+ list( $network, $bits ) = IP::parseCIDR( $target );
+ $size = $max - $bits;
+
+ # Rank a range block covering a single IP equally with a single-IP block
+ $score = self::TYPE_RANGE - 1 + ( $size / $max );
+
+ } else {
+ $score = $block->getType();
+ }
+
+ if ( $score < $bestBlockScore ) {
+ $bestBlockScore = $score;
+ $bestRow = $row;
+ }
+ }
+
+ if ( $bestRow !== null ) {
+ $this->initFromRow( $bestRow );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get a set of SQL conditions which will select rangeblocks encompassing a given range
+ * @param string $start Hexadecimal IP representation
+ * @param string|null $end Hexadecimal IP representation, or null to use $start = $end
+ * @return string
+ */
+ public static function getRangeCond( $start, $end = null ) {
+ if ( $end === null ) {
+ $end = $start;
+ }
+ # Per T16634, we want to include relevant active rangeblocks; for
+ # rangeblocks, we want to include larger ranges which enclose the given
+ # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
+ # so we can improve performance by filtering on a LIKE clause
+ $chunk = self::getIpFragment( $start );
+ $dbr = wfGetDB( DB_REPLICA );
+ $like = $dbr->buildLike( $chunk, $dbr->anyString() );
+
+ # Fairly hard to make a malicious SQL statement out of hex characters,
+ # but stranger things have happened...
+ $safeStart = $dbr->addQuotes( $start );
+ $safeEnd = $dbr->addQuotes( $end );
+
+ return $dbr->makeList(
+ [
+ "ipb_range_start $like",
+ "ipb_range_start <= $safeStart",
+ "ipb_range_end >= $safeEnd",
+ ],
+ LIST_AND
+ );
+ }
+
+ /**
+ * Get the component of an IP address which is certain to be the same between an IP
+ * address and a rangeblock containing that IP address.
+ * @param string $hex Hexadecimal IP representation
+ * @return string
+ */
+ protected static function getIpFragment( $hex ) {
+ global $wgBlockCIDRLimit;
+ if ( substr( $hex, 0, 3 ) == 'v6-' ) {
+ return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
+ } else {
+ return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
+ }
+ }
+
+ /**
+ * Given a database row from the ipblocks table, initialize
+ * member variables
+ * @param stdClass $row A row from the ipblocks table
+ */
+ protected function initFromRow( $row ) {
+ $this->setTarget( $row->ipb_address );
+ $this->setBlocker( User::newFromAnyId(
+ $row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ?? null
+ ) );
+
+ $this->setTimestamp( wfTimestamp( TS_MW, $row->ipb_timestamp ) );
+ $this->mAuto = $row->ipb_auto;
+ $this->setHideName( $row->ipb_deleted );
+ $this->mId = (int)$row->ipb_id;
+ $this->mParentBlockId = $row->ipb_parent_block_id;
+
+ // I wish I didn't have to do this
+ $db = wfGetDB( DB_REPLICA );
+ $this->setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
+ $this->setReason(
+ CommentStore::getStore()
+ // Legacy because $row may have come from self::selectFields()
+ ->getCommentLegacy( $db, 'ipb_reason', $row )->text
+ );
+
+ $this->isHardblock( !$row->ipb_anon_only );
+ $this->isAutoblocking( $row->ipb_enable_autoblock );
+ $this->isSitewide( (bool)$row->ipb_sitewide );
+
+ $this->isCreateAccountBlocked( $row->ipb_create_account );
+ $this->isEmailBlocked( $row->ipb_block_email );
+ $this->isUsertalkEditAllowed( $row->ipb_allow_usertalk );
+ }
+
+ /**
+ * Create a new DatabaseBlock object from a database row
+ * @param stdClass $row Row from the ipblocks table
+ * @return DatabaseBlock
+ */
+ public static function newFromRow( $row ) {
+ $block = new DatabaseBlock;
+ $block->initFromRow( $row );
+ return $block;
+ }
+
+ /**
+ * Delete the row from the IP blocks table.
+ *
+ * @throws MWException
+ * @return bool
+ */
+ public function delete() {
+ if ( wfReadOnly() ) {
+ return false;
+ }
+
+ if ( !$this->getId() ) {
+ throw new MWException(
+ __METHOD__ . " requires that the mId member be filled\n"
+ );
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
+ $dbw->delete( 'ipblocks', [ 'ipb_parent_block_id' => $this->getId() ], __METHOD__ );
+
+ $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
+ $dbw->delete( 'ipblocks', [ 'ipb_id' => $this->getId() ], __METHOD__ );
+
+ return $dbw->affectedRows() > 0;
+ }
+
+ /**
+ * Insert a block into the block table. Will fail if there is a conflicting
+ * block (same name and options) already in the database.
+ *
+ * @param IDatabase|null $dbw If you have one available
+ * @return bool|array False on failure, assoc array on success:
+ * ('id' => block ID, 'autoIds' => array of autoblock IDs)
+ */
+ public function insert( $dbw = null ) {
+ global $wgBlockDisablesLogin;
+
+ if ( !$this->getBlocker() || $this->getBlocker()->getName() === '' ) {
+ throw new MWException( 'Cannot insert a block without a blocker set' );
+ }
+
+ wfDebug( __METHOD__ . "; timestamp {$this->mTimestamp}\n" );
+
+ if ( $dbw === null ) {
+ $dbw = wfGetDB( DB_MASTER );
+ }
+
+ self::purgeExpired();
+
+ $row = $this->getDatabaseArray( $dbw );
+
+ $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
+ $affected = $dbw->affectedRows();
+ if ( $affected ) {
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ $this->getBlockRestrictionStore()->insert( $this->restrictions );
+ }
+ }
+
+ # Don't collide with expired blocks.
+ # Do this after trying to insert to avoid locking.
+ if ( !$affected ) {
+ # T96428: The ipb_address index uses a prefix on a field, so
+ # use a standard SELECT + DELETE to avoid annoying gap locks.
+ $ids = $dbw->selectFieldValues( 'ipblocks',
+ 'ipb_id',
+ [
+ 'ipb_address' => $row['ipb_address'],
+ 'ipb_user' => $row['ipb_user'],
+ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
+ ],
+ __METHOD__
+ );
+ if ( $ids ) {
+ $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
+ $this->getBlockRestrictionStore()->deleteByBlockId( $ids );
+ $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
+ $affected = $dbw->affectedRows();
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ $this->getBlockRestrictionStore()->insert( $this->restrictions );
+ }
+ }
+ }
+
+ if ( $affected ) {
+ $auto_ipd_ids = $this->doRetroactiveAutoblock();
+
+ if ( $wgBlockDisablesLogin && $this->target instanceof User ) {
+ // Change user login token to force them to be logged out.
+ $this->target->setToken();
+ $this->target->saveSettings();
+ }
+
+ return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
+ }
+
+ return false;
+ }
+
+ /**
+ * Update a block in the DB with new parameters.
+ * The ID field needs to be loaded first.
+ *
+ * @return bool|array False on failure, array on success:
+ * ('id' => block ID, 'autoIds' => array of autoblock IDs)
+ */
+ public function update() {
+ wfDebug( __METHOD__ . "; timestamp {$this->mTimestamp}\n" );
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->startAtomic( __METHOD__ );
+
+ $result = $dbw->update(
+ 'ipblocks',
+ $this->getDatabaseArray( $dbw ),
+ [ 'ipb_id' => $this->getId() ],
+ __METHOD__
+ );
+
+ // Only update the restrictions if they have been modified.
+ if ( $this->restrictions !== null ) {
+ // An empty array should remove all of the restrictions.
+ if ( empty( $this->restrictions ) ) {
+ $success = $this->getBlockRestrictionStore()->deleteByBlockId( $this->getId() );
+ } else {
+ $success = $this->getBlockRestrictionStore()->update( $this->restrictions );
+ }
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $result && $success;
+ }
+
+ if ( $this->isAutoblocking() ) {
+ // update corresponding autoblock(s) (T50813)
+ $dbw->update(
+ 'ipblocks',
+ $this->getAutoblockUpdateArray( $dbw ),
+ [ 'ipb_parent_block_id' => $this->getId() ],
+ __METHOD__
+ );
+
+ // Only update the restrictions if they have been modified.
+ if ( $this->restrictions !== null ) {
+ $this->getBlockRestrictionStore()->updateByParentBlockId( $this->getId(), $this->restrictions );
+ }
+ } else {
+ // autoblock no longer required, delete corresponding autoblock(s)
+ $this->getBlockRestrictionStore()->deleteByParentBlockId( $this->getId() );
+ $dbw->delete(
+ 'ipblocks',
+ [ 'ipb_parent_block_id' => $this->getId() ],
+ __METHOD__
+ );
+ }
+
+ $dbw->endAtomic( __METHOD__ );
+
+ if ( $result ) {
+ $auto_ipd_ids = $this->doRetroactiveAutoblock();
+ return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get an array suitable for passing to $dbw->insert() or $dbw->update()
+ * @param IDatabase $dbw
+ * @return array
+ */
+ protected function getDatabaseArray( IDatabase $dbw ) {
+ $expiry = $dbw->encodeExpiry( $this->getExpiry() );
+
+ if ( $this->forcedTargetID ) {
+ $uid = $this->forcedTargetID;
+ } else {
+ $uid = $this->target instanceof User ? $this->target->getId() : 0;
+ }
+
+ $a = [
+ 'ipb_address' => (string)$this->target,
+ 'ipb_user' => $uid,
+ 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
+ 'ipb_auto' => $this->mAuto,
+ 'ipb_anon_only' => !$this->isHardblock(),
+ 'ipb_create_account' => $this->isCreateAccountBlocked(),
+ 'ipb_enable_autoblock' => $this->isAutoblocking(),
+ 'ipb_expiry' => $expiry,
+ 'ipb_range_start' => $this->getRangeStart(),
+ 'ipb_range_end' => $this->getRangeEnd(),
+ 'ipb_deleted' => intval( $this->getHideName() ), // typecast required for SQLite
+ 'ipb_block_email' => $this->isEmailBlocked(),
+ 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
+ 'ipb_parent_block_id' => $this->mParentBlockId,
+ 'ipb_sitewide' => $this->isSitewide(),
+ ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
+ + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
+
+ return $a;
+ }
+
+ /**
+ * @param IDatabase $dbw
+ * @return array
+ */
+ protected function getAutoblockUpdateArray( IDatabase $dbw ) {
+ return [
+ 'ipb_create_account' => $this->isCreateAccountBlocked(),
+ 'ipb_deleted' => (int)$this->getHideName(), // typecast required for SQLite
+ 'ipb_allow_usertalk' => $this->isUsertalkEditAllowed(),
+ 'ipb_sitewide' => $this->isSitewide(),
+ ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->getReason() )
+ + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
+ }
+
+ /**
+ * Retroactively autoblocks the last IP used by the user (if it is a user)
+ * blocked by this block.
+ *
+ * @return array IDs of retroactive autoblocks made
+ */
+ protected function doRetroactiveAutoblock() {
+ $blockIds = [];
+ # If autoblock is enabled, autoblock the LAST IP(s) used
+ if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
+ wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
+
+ $continue = Hooks::run(
+ 'PerformRetroactiveAutoblock', [ $this, &$blockIds ] );
+
+ if ( $continue ) {
+ self::defaultRetroactiveAutoblock( $this, $blockIds );
+ }
+ }
+ return $blockIds;
+ }
+
+ /**
+ * Retroactively autoblocks the last IP used by the user (if it is a user)
+ * blocked by this block. This will use the recentchanges table.
+ *
+ * @param DatabaseBlock $block
+ * @param array &$blockIds
+ */
+ protected static function defaultRetroactiveAutoblock( DatabaseBlock $block, array &$blockIds ) {
+ global $wgPutIPinRC;
+
+ // No IPs are in recentchanges table, so nothing to select
+ if ( !$wgPutIPinRC ) {
+ return;
+ }
+
+ // Autoblocks only apply to TYPE_USER
+ if ( $block->getType() !== self::TYPE_USER ) {
+ return;
+ }
+ $target = $block->getTarget(); // TYPE_USER => always a User object
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $rcQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $target, false );
+
+ $options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
+
+ // Just the last IP used.
+ $options['LIMIT'] = 1;
+
+ $res = $dbr->select(
+ [ 'recentchanges' ] + $rcQuery['tables'],
+ [ 'rc_ip' ],
+ $rcQuery['conds'],
+ __METHOD__,
+ $options,
+ $rcQuery['joins']
+ );
+
+ if ( !$res->numRows() ) {
+ # No results, don't autoblock anything
+ wfDebug( "No IP found to retroactively autoblock\n" );
+ } else {
+ foreach ( $res as $row ) {
+ if ( $row->rc_ip ) {
+ $id = $block->doAutoblock( $row->rc_ip );
+ if ( $id ) {
+ $blockIds[] = $id;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether a given IP is on the autoblock whitelist.
+ * TODO: this probably belongs somewhere else, but not sure where...
+ *
+ * @param string $ip The IP to check
+ * @return bool
+ */
+ public static function isWhitelistedFromAutoblocks( $ip ) {
+ // Try to get the autoblock_whitelist from the cache, as it's faster
+ // than getting the msg raw and explode()'ing it.
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ $lines = $cache->getWithSetCallback(
+ $cache->makeKey( 'ip-autoblock', 'whitelist' ),
+ $cache::TTL_DAY,
+ function ( $curValue, &$ttl, array &$setOpts ) {
+ $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
+
+ return explode( "\n",
+ wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() );
+ }
+ );
+
+ wfDebug( "Checking the autoblock whitelist..\n" );
+
+ foreach ( $lines as $line ) {
+ # List items only
+ if ( substr( $line, 0, 1 ) !== '*' ) {
+ continue;
+ }
+
+ $wlEntry = substr( $line, 1 );
+ $wlEntry = trim( $wlEntry );
+
+ wfDebug( "Checking $ip against $wlEntry..." );
+
+ # Is the IP in this range?
+ if ( IP::isInRange( $ip, $wlEntry ) ) {
+ wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" );
+ return true;
+ } else {
+ wfDebug( " No match\n" );
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Autoblocks the given IP, referring to this block.
+ *
+ * @param string $autoblockIP The IP to autoblock.
+ * @return int|bool ID if an autoblock was inserted, false if not.
+ */
+ public function doAutoblock( $autoblockIP ) {
+ # If autoblocks are disabled, go away.
+ if ( !$this->isAutoblocking() ) {
+ return false;
+ }
+
+ # Check for presence on the autoblock whitelist.
+ if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
+ return false;
+ }
+
+ // Avoid PHP 7.1 warning of passing $this by reference
+ $block = $this;
+ # Allow hooks to cancel the autoblock.
+ if ( !Hooks::run( 'AbortAutoblock', [ $autoblockIP, &$block ] ) ) {
+ wfDebug( "Autoblock aborted by hook.\n" );
+ return false;
+ }
+
+ # It's okay to autoblock. Go ahead and insert/update the block...
+
+ # Do not add a *new* block if the IP is already blocked.
+ $ipblock = self::newFromTarget( $autoblockIP );
+ if ( $ipblock ) {
+ # Check if the block is an autoblock and would exceed the user block
+ # if renewed. If so, do nothing, otherwise prolong the block time...
+ if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
+ $this->getExpiry() > self::getAutoblockExpiry( $ipblock->getTimestamp() )
+ ) {
+ # Reset block timestamp to now and its expiry to
+ # $wgAutoblockExpiry in the future
+ $ipblock->updateTimestamp();
+ }
+ return false;
+ }
+
+ # Make a new block object with the desired properties.
+ $autoblock = new DatabaseBlock;
+ wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
+ $autoblock->setTarget( $autoblockIP );
+ $autoblock->setBlocker( $this->getBlocker() );
+ $autoblock->setReason(
+ wfMessage( 'autoblocker', $this->getTarget(), $this->getReason() )
+ ->inContentLanguage()->plain()
+ );
+ $timestamp = wfTimestampNow();
+ $autoblock->setTimestamp( $timestamp );
+ $autoblock->mAuto = 1;
+ $autoblock->isCreateAccountBlocked( $this->isCreateAccountBlocked() );
+ # Continue suppressing the name if needed
+ $autoblock->setHideName( $this->getHideName() );
+ $autoblock->isUsertalkEditAllowed( $this->isUsertalkEditAllowed() );
+ $autoblock->mParentBlockId = $this->mId;
+ $autoblock->isSitewide( $this->isSitewide() );
+ $autoblock->setRestrictions( $this->getRestrictions() );
+
+ if ( $this->getExpiry() == 'infinity' ) {
+ # Original block was indefinite, start an autoblock now
+ $autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
+ } else {
+ # If the user is already blocked with an expiry date, we don't
+ # want to pile on top of that.
+ $autoblock->setExpiry( min( $this->getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
+ }
+
+ # Insert the block...
+ $status = $autoblock->insert();
+ return $status
+ ? $status['id']
+ : false;
+ }
+
+ /**
+ * Check if a block has expired. Delete it if it is.
+ * @return bool
+ */
+ public function deleteIfExpired() {
+ if ( $this->isExpired() ) {
+ wfDebug( __METHOD__ . " -- deleting\n" );
+ $this->delete();
+ $retVal = true;
+ } else {
+ wfDebug( __METHOD__ . " -- not expired\n" );
+ $retVal = false;
+ }
+
+ return $retVal;
+ }
+
+ /**
+ * Has the block expired?
+ * @return bool
+ */
+ public function isExpired() {
+ $timestamp = wfTimestampNow();
+ wfDebug( __METHOD__ . " checking current " . $timestamp . " vs $this->mExpiry\n" );
+
+ if ( !$this->getExpiry() ) {
+ return false;
+ } else {
+ return $timestamp > $this->getExpiry();
+ }
+ }
+
+ /**
+ * Is the block address valid (i.e. not a null string?)
+ *
+ * @deprecated since 1.33 No longer needed in core.
+ * @return bool
+ */
+ public function isValid() {
+ wfDeprecated( __METHOD__, '1.33' );
+ return $this->getTarget() != null;
+ }
+
+ /**
+ * Update the timestamp on autoblocks.
+ */
+ public function updateTimestamp() {
+ if ( $this->mAuto ) {
+ $this->setTimestamp( wfTimestamp() );
+ $this->setExpiry( self::getAutoblockExpiry( $this->getTimestamp() ) );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'ipblocks',
+ [ /* SET */
+ 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
+ 'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
+ ],
+ [ /* WHERE */
+ 'ipb_id' => $this->getId(),
+ ],
+ __METHOD__
+ );
+ }
+ }
+
+ /**
+ * Get the IP address at the start of the range in Hex form
+ * @throws MWException
+ * @return string IP in Hex form
+ */
+ public function getRangeStart() {
+ switch ( $this->type ) {
+ case self::TYPE_USER:
+ return '';
+ case self::TYPE_IP:
+ return IP::toHex( $this->target );
+ case self::TYPE_RANGE:
+ list( $start, /*...*/ ) = IP::parseRange( $this->target );
+ return $start;
+ default:
+ throw new MWException( "Block with invalid type" );
+ }
+ }
+
+ /**
+ * Get the IP address at the end of the range in Hex form
+ * @throws MWException
+ * @return string IP in Hex form
+ */
+ public function getRangeEnd() {
+ switch ( $this->type ) {
+ case self::TYPE_USER:
+ return '';
+ case self::TYPE_IP:
+ return IP::toHex( $this->target );
+ case self::TYPE_RANGE:
+ list( /*...*/, $end ) = IP::parseRange( $this->target );
+ return $end;
+ default:
+ throw new MWException( "Block with invalid type" );
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getId() {
+ return $this->mId;
+ }
+
+ /**
+ * Set the block ID
+ *
+ * @param int $blockId
+ * @return self
+ */
+ private function setId( $blockId ) {
+ $this->mId = (int)$blockId;
+
+ if ( is_array( $this->restrictions ) ) {
+ $this->restrictions = $this->getBlockRestrictionStore()->setBlockId(
+ $blockId, $this->restrictions
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get/set a flag determining whether the master is used for reads
+ *
+ * @param bool|null $x
+ * @return bool
+ */
+ public function fromMaster( $x = null ) {
+ return wfSetVar( $this->mFromMaster, $x );
+ }
+
+ /**
+ * Get/set whether the block is a hardblock (affects logged-in users on a given IP/range)
+ * @param bool|null $x
+ * @return bool
+ */
+ public function isHardblock( $x = null ) {
+ wfSetVar( $this->isHardblock, $x );
+
+ # You can't *not* hardblock a user
+ return $this->getType() == self::TYPE_USER
+ ? true
+ : $this->isHardblock;
+ }
+
+ /**
+ * @param null|bool $x
+ * @return bool
+ */
+ public function isAutoblocking( $x = null ) {
+ wfSetVar( $this->isAutoblocking, $x );
+
+ # You can't put an autoblock on an IP or range as we don't have any history to
+ # look over to get more IPs from
+ return $this->getType() == self::TYPE_USER
+ ? $this->isAutoblocking
+ : false;
+ }
+
+ /**
+ * Get the block name, but with autoblocked IPs hidden as per standard privacy policy
+ * @return string Text is escaped
+ */
+ public function getRedactedName() {
+ if ( $this->mAuto ) {
+ return Html::element(
+ 'span',
+ [ 'class' => 'mw-autoblockid' ],
+ wfMessage( 'autoblockid', $this->mId )->text()
+ );
+ } else {
+ return htmlspecialchars( $this->getTarget() );
+ }
+ }
+
+ /**
+ * Get a timestamp of the expiry for autoblocks
+ *
+ * @param string|int $timestamp
+ * @return string
+ */
+ public static function getAutoblockExpiry( $timestamp ) {
+ global $wgAutoblockExpiry;
+
+ return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
+ }
+
+ /**
+ * Purge expired blocks from the ipblocks table
+ */
+ public static function purgeExpired() {
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ DeferredUpdates::addUpdate( new AutoCommitUpdate(
+ wfGetDB( DB_MASTER ),
+ __METHOD__,
+ function ( IDatabase $dbw, $fname ) {
+ $ids = $dbw->selectFieldValues( 'ipblocks',
+ 'ipb_id',
+ [ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
+ $fname
+ );
+ if ( $ids ) {
+ $blockRestrictionStore = MediaWikiServices::getInstance()->getBlockRestrictionStore();
+ $blockRestrictionStore->deleteByBlockId( $ids );
+
+ $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
+ }
+ }
+ ) );
+ }
+
+ /**
+ * Given a target and the target's type, get an existing block object if possible.
+ * @param string|User|int $specificTarget A block target, which may be one of several types:
+ * * A user to block, in which case $target will be a User
+ * * An IP to block, in which case $target will be a User generated by using
+ * User::newFromName( $ip, false ) to turn off name validation
+ * * An IP range, in which case $target will be a String "123.123.123.123/18" etc
+ * * The ID of an existing block, in the format "#12345" (since pure numbers are valid
+ * usernames
+ * Calling this with a user, IP address or range will not select autoblocks, and will
+ * only select a block where the targets match exactly (so looking for blocks on
+ * 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32)
+ * @param string|User|int|null $vagueTarget As above, but we will search for *any* block which
+ * affects that target (so for an IP address, get ranges containing that IP; and also
+ * get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
+ * @param bool $fromMaster Whether to use the DB_MASTER database
+ * @return DatabaseBlock|null (null if no relevant block could be found). The target and type
+ * of the returned block will refer to the actual block which was found, which might
+ * not be the same as the target you gave if you used $vagueTarget!
+ */
+ public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
+ list( $target, $type ) = self::parseTarget( $specificTarget );
+ if ( $type == self::TYPE_ID || $type == self::TYPE_AUTO ) {
+ return self::newFromID( $target );
+
+ } elseif ( $target === null && $vagueTarget == '' ) {
+ # We're not going to find anything useful here
+ # Be aware that the == '' check is explicit, since empty values will be
+ # passed by some callers (T31116)
+ return null;
+
+ } elseif ( in_array(
+ $type,
+ [ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE, null ] )
+ ) {
+ $block = new DatabaseBlock();
+ $block->fromMaster( $fromMaster );
+
+ if ( $type !== null ) {
+ $block->setTarget( $target );
+ }
+
+ if ( $block->newLoad( $vagueTarget ) ) {
+ return $block;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get all blocks that match any IP from an array of IP addresses
+ *
+ * @param array $ipChain List of IPs (strings), usually retrieved from the
+ * X-Forwarded-For header of the request
+ * @param bool $isAnon Exclude anonymous-only blocks if false
+ * @param bool $fromMaster Whether to query the master or replica DB
+ * @return array Array of Blocks
+ * @since 1.22
+ */
+ public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
+ if ( $ipChain === [] ) {
+ return [];
+ }
+
+ $conds = [];
+ $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
+ foreach ( array_unique( $ipChain ) as $ipaddr ) {
+ # Discard invalid IP addresses. Since XFF can be spoofed and we do not
+ # necessarily trust the header given to us, make sure that we are only
+ # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
+ # Do not treat private IP spaces as special as it may be desirable for wikis
+ # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
+ if ( !IP::isValid( $ipaddr ) ) {
+ continue;
+ }
+ # Don't check trusted IPs (includes local CDNs which will be in every request)
+ if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
+ continue;
+ }
+ # Check both the original IP (to check against single blocks), as well as build
+ # the clause to check for rangeblocks for the given IP.
+ $conds['ipb_address'][] = $ipaddr;
+ $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
+ }
+
+ if ( $conds === [] ) {
+ return [];
+ }
+
+ if ( $fromMaster ) {
+ $db = wfGetDB( DB_MASTER );
+ } else {
+ $db = wfGetDB( DB_REPLICA );
+ }
+ $conds = $db->makeList( $conds, LIST_OR );
+ if ( !$isAnon ) {
+ $conds = [ $conds, 'ipb_anon_only' => 0 ];
+ }
+ $blockQuery = self::getQueryInfo();
+ $rows = $db->select(
+ $blockQuery['tables'],
+ array_merge( [ 'ipb_range_start', 'ipb_range_end' ], $blockQuery['fields'] ),
+ $conds,
+ __METHOD__,
+ [],
+ $blockQuery['joins']
+ );
+
+ $blocks = [];
+ foreach ( $rows as $row ) {
+ $block = self::newFromRow( $row );
+ if ( !$block->isExpired() ) {
+ $blocks[] = $block;
+ }
+ }
+
+ return $blocks;
+ }
+
+ /**
+ * From a list of multiple blocks, find the most exact and strongest block.
+ *
+ * The logic for finding the "best" block is:
+ * - Blocks that match the block's target IP are preferred over ones in a range
+ * - Hardblocks are chosen over softblocks that prevent account creation
+ * - Softblocks that prevent account creation are chosen over other softblocks
+ * - Other softblocks are chosen over autoblocks
+ * - If there are multiple exact or range blocks at the same level, the one chosen
+ * is random
+ * This should be used when $blocks were retrieved from the user's IP address
+ * and $ipChain is populated from the same IP address information.
+ *
+ * @param array $blocks Array of DatabaseBlock objects
+ * @param array $ipChain List of IPs (strings). This is used to determine how "close"
+ * a block is to the server, and if a block matches exactly, or is in a range.
+ * The order is furthest from the server to nearest e.g., (Browser, proxy1, proxy2,
+ * local-cdn, ...)
+ * @throws MWException
+ * @return DatabaseBlock|null The "best" block from the list
+ */
+ public static function chooseBlock( array $blocks, array $ipChain ) {
+ if ( $blocks === [] ) {
+ return null;
+ } elseif ( count( $blocks ) == 1 ) {
+ return $blocks[0];
+ }
+
+ // Sort hard blocks before soft ones and secondarily sort blocks
+ // that disable account creation before those that don't.
+ usort( $blocks, function ( DatabaseBlock $a, DatabaseBlock $b ) {
+ $aWeight = (int)$a->isHardblock() . (int)$a->appliesToRight( 'createaccount' );
+ $bWeight = (int)$b->isHardblock() . (int)$b->appliesToRight( 'createaccount' );
+ return strcmp( $bWeight, $aWeight ); // highest weight first
+ } );
+
+ $blocksListExact = [
+ 'hard' => false,
+ 'disable_create' => false,
+ 'other' => false,
+ 'auto' => false
+ ];
+ $blocksListRange = [
+ 'hard' => false,
+ 'disable_create' => false,
+ 'other' => false,
+ 'auto' => false
+ ];
+ $ipChain = array_reverse( $ipChain );
+
+ foreach ( $blocks as $block ) {
+ // Stop searching if we have already have a "better" block. This
+ // is why the order of the blocks matters
+ if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
+ break;
+ } elseif ( !$block->appliesToRight( 'createaccount' ) && $blocksListExact['disable_create'] ) {
+ break;
+ }
+
+ foreach ( $ipChain as $checkip ) {
+ $checkipHex = IP::toHex( $checkip );
+ if ( (string)$block->getTarget() === $checkip ) {
+ if ( $block->isHardblock() ) {
+ $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
+ } elseif ( $block->appliesToRight( 'createaccount' ) ) {
+ $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
+ } elseif ( $block->mAuto ) {
+ $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
+ } else {
+ $blocksListExact['other'] = $blocksListExact['other'] ?: $block;
+ }
+ // We found closest exact match in the ip list, so go to the next block
+ break;
+ } elseif ( array_filter( $blocksListExact ) == []
+ && $block->getRangeStart() <= $checkipHex
+ && $block->getRangeEnd() >= $checkipHex
+ ) {
+ if ( $block->isHardblock() ) {
+ $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
+ } elseif ( $block->appliesToRight( 'createaccount' ) ) {
+ $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
+ } elseif ( $block->mAuto ) {
+ $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
+ } else {
+ $blocksListRange['other'] = $blocksListRange['other'] ?: $block;
+ }
+ break;
+ }
+ }
+ }
+
+ if ( array_filter( $blocksListExact ) == [] ) {
+ $blocksList = &$blocksListRange;
+ } else {
+ $blocksList = &$blocksListExact;
+ }
+
+ $chosenBlock = null;
+ if ( $blocksList['hard'] ) {
+ $chosenBlock = $blocksList['hard'];
+ } elseif ( $blocksList['disable_create'] ) {
+ $chosenBlock = $blocksList['disable_create'];
+ } elseif ( $blocksList['other'] ) {
+ $chosenBlock = $blocksList['other'];
+ } elseif ( $blocksList['auto'] ) {
+ $chosenBlock = $blocksList['auto'];
+ } else {
+ throw new MWException( "Proxy block found, but couldn't be classified." );
+ }
+
+ return $chosenBlock;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * Autoblocks have whichever type corresponds to their target, so to detect if a block is an
+ * autoblock, we have to check the mAuto property instead.
+ */
+ public function getType() {
+ return $this->mAuto
+ ? self::TYPE_AUTO
+ : parent::getType();
+ }
+
+ /**
+ * Set the 'BlockID' cookie to this block's ID and expiry time. The cookie's expiry will be
+ * the same as the block's, to a maximum of 24 hours.
+ *
+ * @since 1.29
+ *
+ * @param WebResponse $response The response on which to set the cookie.
+ */
+ public function setCookie( WebResponse $response ) {
+ // Calculate the default expiry time.
+ $maxExpiryTime = wfTimestamp( TS_MW, wfTimestamp() + ( 24 * 60 * 60 ) );
+
+ // Use the block's expiry time only if it's less than the default.
+ $expiryTime = $this->getExpiry();
+ if ( $expiryTime === 'infinity' || $expiryTime > $maxExpiryTime ) {
+ $expiryTime = $maxExpiryTime;
+ }
+
+ // Set the cookie. Reformat the MediaWiki datetime as a Unix timestamp for the cookie.
+ $expiryValue = DateTime::createFromFormat( 'YmdHis', $expiryTime )->format( 'U' );
+ $cookieOptions = [ 'httpOnly' => false ];
+ $cookieValue = $this->getCookieValue();
+ $response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
+ }
+
+ /**
+ * Unset the 'BlockID' cookie.
+ *
+ * @since 1.29
+ *
+ * @param WebResponse $response The response on which to unset the cookie.
+ */
+ public static function clearCookie( WebResponse $response ) {
+ $response->clearCookie( 'BlockID', [ 'httpOnly' => false ] );
+ }
+
+ /**
+ * Get the BlockID cookie's value for this block. This is usually the block ID concatenated
+ * with an HMAC in order to avoid spoofing (T152951), but if wgSecretKey is not set will just
+ * be the block ID.
+ *
+ * @since 1.29
+ *
+ * @return string The block ID, probably concatenated with "!" and the HMAC.
+ */
+ public function getCookieValue() {
+ $config = RequestContext::getMain()->getConfig();
+ $id = $this->getId();
+ $secretKey = $config->get( 'SecretKey' );
+ if ( !$secretKey ) {
+ // If there's no secret key, don't append a HMAC.
+ return $id;
+ }
+ $hmac = MWCryptHash::hmac( $id, $secretKey, false );
+ $cookieValue = $id . '!' . $hmac;
+ return $cookieValue;
+ }
+
+ /**
+ * Get the stored ID from the 'BlockID' cookie. The cookie's value is usually a combination of
+ * the ID and a HMAC (see DatabaseBlock::setCookie), but will sometimes only be the ID.
+ *
+ * @since 1.29
+ *
+ * @param string $cookieValue The string in which to find the ID.
+ *
+ * @return int|null The block ID, or null if the HMAC is present and invalid.
+ */
+ public static function getIdFromCookieValue( $cookieValue ) {
+ // Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
+ $bangPos = strpos( $cookieValue, '!' );
+ $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
+ // Get the site-wide secret key.
+ $config = RequestContext::getMain()->getConfig();
+ $secretKey = $config->get( 'SecretKey' );
+ if ( !$secretKey ) {
+ // If there's no secret key, just use the ID as given.
+ return $id;
+ }
+ $storedHmac = substr( $cookieValue, $bangPos + 1 );
+ $calculatedHmac = MWCryptHash::hmac( $id, $secretKey, false );
+ if ( $calculatedHmac === $storedHmac ) {
+ return $id;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * Build different messages for autoblocks and partial blocks.
+ */
+ public function getPermissionsError( IContextSource $context ) {
+ $params = $this->getBlockErrorParams( $context );
+
+ $msg = 'blockedtext';
+ if ( $this->mAuto ) {
+ $msg = 'autoblockedtext';
+ } elseif ( !$this->isSitewide() ) {
+ $msg = 'blockedtext-partial';
+ }
+
+ array_unshift( $params, $msg );
+
+ return $params;
+ }
+
+ /**
+ * Get Restrictions.
+ *
+ * Getting the restrictions will perform a database query if the restrictions
+ * are not already loaded.
+ *
+ * @since 1.33
+ * @return Restriction[]
+ */
+ public function getRestrictions() {
+ if ( $this->restrictions === null ) {
+ // If the block id has not been set, then do not attempt to load the
+ // restrictions.
+ if ( !$this->mId ) {
+ return [];
+ }
+ $this->restrictions = $this->getBlockRestrictionStore()->loadByBlockId( $this->mId );
+ }
+
+ return $this->restrictions;
+ }
+
+ /**
+ * Set Restrictions.
+ *
+ * @since 1.33
+ * @param Restriction[] $restrictions
+ * @return self
+ */
+ public function setRestrictions( array $restrictions ) {
+ $this->restrictions = array_filter( $restrictions, function ( $restriction ) {
+ return $restriction instanceof Restriction;
+ } );
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function appliesToTitle( Title $title ) {
+ if ( $this->isSitewide() ) {
+ return true;
+ }
+
+ $restrictions = $this->getRestrictions();
+ foreach ( $restrictions as $restriction ) {
+ if ( $restriction->matches( $title ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function appliesToNamespace( $ns ) {
+ if ( $this->isSitewide() ) {
+ return true;
+ }
+
+ // Blocks do not apply to virtual namespaces.
+ if ( $ns < 0 ) {
+ return false;
+ }
+
+ $restriction = $this->findRestriction( NamespaceRestriction::TYPE, $ns );
+
+ return (bool)$restriction;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function appliesToPage( $pageId ) {
+ if ( $this->isSitewide() ) {
+ return true;
+ }
+
+ // If the pageId is not over zero, the block cannot apply to it.
+ if ( $pageId <= 0 ) {
+ return false;
+ }
+
+ $restriction = $this->findRestriction( PageRestriction::TYPE, $pageId );
+
+ return (bool)$restriction;
+ }
+
+ /**
+ * Find Restriction by type and value.
+ *
+ * @param string $type
+ * @param int $value
+ * @return Restriction|null
+ */
+ private function findRestriction( $type, $value ) {
+ $restrictions = $this->getRestrictions();
+ foreach ( $restrictions as $restriction ) {
+ if ( $restriction->getType() !== $type ) {
+ continue;
+ }
+
+ if ( $restriction->getValue() === $value ) {
+ return $restriction;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function shouldTrackWithCookie( $isAnon ) {
+ $config = RequestContext::getMain()->getConfig();
+ switch ( $this->getType() ) {
+ case self::TYPE_IP:
+ case self::TYPE_RANGE:
+ return $isAnon && $config->get( 'CookieSetOnIpBlock' );
+ case self::TYPE_USER:
+ return !$isAnon && $config->get( 'CookieSetOnAutoblock' ) && $this->isAutoblocking();
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Get a BlockRestrictionStore instance
+ *
+ * @return BlockRestrictionStore
+ */
+ private function getBlockRestrictionStore() : BlockRestrictionStore {
+ return MediaWikiServices::getInstance()->getBlockRestrictionStore();
+ }
+}
+
+/**
+ * @deprecated since 1.34
+ */
+class_alias( DatabaseBlock::class, 'Block' );
<?php
/**
- * A Block restriction object of type 'Namespace'.
+ * A block restriction object of type 'Namespace'.
*
* 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
<?php
/**
- * A Block restriction object of type 'Page'.
+ * A block restriction object of type 'Page'.
*
* 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
*
* @file
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\RevisionRecord;
} else {
$specificTarget = $titleText;
}
- if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
+ if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
return [
'index' => 'noindex',
'follow' => 'nofollow'
$rootPart = explode( '/', $title->getText() )[0];
$user = User::newFromName( $rootPart, false /* allow IP users */ );
$ip = User::isIP( $rootPart );
- $block = Block::newFromTarget( $user, $user );
+ $block = DatabaseBlock::newFromTarget( $user, $user );
if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
$outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
[ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
} elseif (
!is_null( $block ) &&
- $block->getType() != Block::TYPE_AUTO &&
+ $block->getType() != DatabaseBlock::TYPE_AUTO &&
( $block->isSitewide() || $user->isBlockedFrom( $title ) )
) {
// Show log extract if the user is sitewide blocked or is partially
* @ingroup SpecialPage
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\MediaWikiServices;
* or as subpage (Special:Block/Foo) */
protected $target;
- /** @var int Block::TYPE_ constant */
+ /** @var int DatabaseBlock::TYPE_ constant */
protected $type;
/** @var User|string The previous block target */
}
list( $this->previousTarget, /*...*/ ) =
- Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
+ DatabaseBlock::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
$this->requestedHideUser = $request->getBool( 'wpHideUser' );
}
# This won't be
$fields['PreviousTarget']['default'] = (string)$this->target;
- $block = Block::newFromTarget( $this->target );
+ $block = DatabaseBlock::newFromTarget( $this->target );
// Populate fields if there is a block that is not an autoblock; if it is a range
// block, only populate the fields if the range is the same as $this->target
- if ( $block instanceof Block && $block->getType() !== Block::TYPE_AUTO
- && ( $this->type != Block::TYPE_RANGE
+ if ( $block instanceof DatabaseBlock && $block->getType() !== DatabaseBlock::TYPE_AUTO
+ && ( $this->type != DatabaseBlock::TYPE_RANGE
|| $block->getTarget() == $this->target )
) {
$fields['HardBlock']['default'] = $block->isHardblock();
}
if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
- if ( $block instanceof Block && !$block->isSitewide() ) {
+ if ( $block instanceof DatabaseBlock && !$block->isSitewide() ) {
$fields['EditingRestriction']['default'] = 'partial';
} else {
$fields['EditingRestriction']['default'] = 'sitewide';
}
- if ( $block instanceof Block ) {
+ if ( $block instanceof DatabaseBlock ) {
$pageRestrictions = [];
$namespaceRestrictions = [];
foreach ( $block->getRestrictions() as $restriction ) {
/**
* Determine the target of the block, and the type of target
- * @todo Should be in Block.php?
+ * @todo Should be in DatabaseBlock.php?
* @param string $par Subpage parameter passed to setup, or data value from
* the HTMLForm
* @param WebRequest|null $request Optionally try and get data from a request too
- * @return array [ User|string|null, Block::TYPE_ constant|null ]
+ * @return array [ User|string|null, DatabaseBlock::TYPE_ constant|null ]
* @phan-return array{0:User|string|null,1:int|null}
*/
public static function getTargetAndType( $par, WebRequest $request = null ) {
break 2;
}
- list( $target, $type ) = Block::parseTarget( $target );
+ list( $target, $type ) = DatabaseBlock::parseTarget( $target );
if ( $type !== null ) {
return [ $target, $type ];
list( $target, $type ) = self::getTargetAndType( $value );
$status = Status::newGood( $target );
- if ( $type == Block::TYPE_USER ) {
+ if ( $type == DatabaseBlock::TYPE_USER ) {
if ( $target->isAnon() ) {
$status->fatal(
'nosuchusershort',
if ( $unblockStatus !== true ) {
$status->fatal( 'badaccess', $unblockStatus );
}
- } elseif ( $type == Block::TYPE_RANGE ) {
+ } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
list( $ip, $range ) = explode( '/', $target, 2 );
if (
if ( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
$status->fatal( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
}
- } elseif ( $type == Block::TYPE_IP ) {
+ } elseif ( $type == DatabaseBlock::TYPE_IP ) {
# All is well
} else {
$status->fatal( 'badipaddress' );
/** @var User $target */
list( $target, $type ) = self::getTargetAndType( $data['Target'] );
- if ( $type == Block::TYPE_USER ) {
+ if ( $type == DatabaseBlock::TYPE_USER ) {
$user = $target;
$target = $user->getName();
$userId = $user->getId();
) {
return [ 'ipb-blockingself', 'ipb-confirmaction' ];
}
- } elseif ( $type == Block::TYPE_RANGE ) {
+ } elseif ( $type == DatabaseBlock::TYPE_RANGE ) {
$user = null;
$userId = 0;
- } elseif ( $type == Block::TYPE_IP ) {
+ } elseif ( $type == DatabaseBlock::TYPE_IP ) {
$user = null;
$target = $target->getName();
$userId = 0;
}
# Recheck params here...
- if ( $type != Block::TYPE_USER ) {
+ if ( $type != DatabaseBlock::TYPE_USER ) {
$data['HideUser'] = false; # IP users should not be hidden
} elseif ( !wfIsInfinity( $data['Expiry'] ) ) {
# Bad expiry.
}
# Create block object.
- $block = new Block();
+ $block = new DatabaseBlock();
$block->setTarget( $target );
$block->setBlocker( $performer );
$block->setReason( $data['Reason'][0] );
} else {
# This returns direct blocks before autoblocks/rangeblocks, since we should
# be sure the user is blocked by now it should work for our purposes
- $currentBlock = Block::newFromTarget( $target );
+ $currentBlock = DatabaseBlock::newFromTarget( $target );
if ( $block->equals( $currentBlock ) ) {
return [ [ 'ipb_already_blocked', $block->getTarget() ] ];
}
}
# Can't watch a rangeblock
- if ( $type != Block::TYPE_RANGE && $data['Watch'] ) {
+ if ( $type != DatabaseBlock::TYPE_RANGE && $data['Watch'] ) {
WatchAction::doWatch(
Title::makeTitle( NS_USER, $target ),
$performer,
);
}
- # Block constructor sanitizes certain block options on insert
+ # DatabaseBlock constructor sanitizes certain block options on insert
$data['BlockEmail'] = $block->isEmailBlocked();
$data['AutoBlock'] = $block->isAutoblocking();
}
} elseif (
$target instanceof User &&
- $performer->getBlock() instanceof Block &&
+ $performer->getBlock() instanceof DatabaseBlock &&
$performer->getBlock()->getBy() &&
$performer->getBlock()->getBy() === $target->getId()
) {
* Return a comma-delimited list of "flags" to be passed to the log
* reader for this block, to provide more information in the logs
* @param array $data From HTMLForm data
- * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
+ * @param int $type DatabaseBlock::TYPE_ constant (USER, RANGE, or IP)
* @return string
*/
protected static function blockLogFlags( array $data, $type ) {
# when blocking a user the option 'anononly' is not available/has no effect
# -> do not write this into log
- if ( !$data['HardBlock'] && $type != Block::TYPE_USER ) {
+ if ( !$data['HardBlock'] && $type != DatabaseBlock::TYPE_USER ) {
// For grepping: message block-log-flags-anononly
$flags[] = 'anononly';
}
}
# Same as anononly, this is not displayed when blocking an IP address
- if ( !$data['AutoBlock'] && $type == Block::TYPE_USER ) {
+ if ( !$data['AutoBlock'] && $type == DatabaseBlock::TYPE_USER ) {
// For grepping: message block-log-flags-noautoblock
$flags[] = 'noautoblock';
}
* @ingroup SpecialPage
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* A special page that lists existing blocks
*
}
if ( $this->target !== '' ) {
- list( $target, $type ) = Block::parseTarget( $this->target );
+ list( $target, $type ) = DatabaseBlock::parseTarget( $this->target );
switch ( $type ) {
- case Block::TYPE_ID:
- case Block::TYPE_AUTO:
+ case DatabaseBlock::TYPE_ID:
+ case DatabaseBlock::TYPE_AUTO:
$conds['ipb_id'] = $target;
break;
- case Block::TYPE_IP:
- case Block::TYPE_RANGE:
+ case DatabaseBlock::TYPE_IP:
+ case DatabaseBlock::TYPE_RANGE:
list( $start, $end ) = IP::parseRange( $target );
$conds[] = wfGetDB( DB_REPLICA )->makeList(
[
'ipb_address' => $target,
- Block::getRangeCond( $start, $end )
+ DatabaseBlock::getRangeCond( $start, $end )
],
LIST_OR
);
$conds['ipb_auto'] = 0;
break;
- case Block::TYPE_USER:
+ case DatabaseBlock::TYPE_USER:
$conds['ipb_address'] = $target->getName();
$conds['ipb_auto'] = 0;
break;
* @ingroup SpecialPage
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Widget\DateInputWidget;
// Do not expose the autoblocks, since that may lead to a leak of accounts' IPs,
// and also this will display a totally irrelevant log entry as a current block.
if ( !$this->including() ) {
- // For IP ranges you must give Block::newFromTarget the CIDR string and not a user object.
+ // For IP ranges you must give DatabaseBlock::newFromTarget the CIDR string
+ // and not a user object.
if ( $userObj->isIPRange() ) {
- $block = Block::newFromTarget( $userObj->getName(), $userObj->getName() );
+ $block = DatabaseBlock::newFromTarget( $userObj->getName(), $userObj->getName() );
} else {
- $block = Block::newFromTarget( $userObj, $userObj );
+ $block = DatabaseBlock::newFromTarget( $userObj, $userObj );
}
- if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
- if ( $block->getType() == Block::TYPE_RANGE ) {
+ if ( !is_null( $block ) && $block->getType() != DatabaseBlock::TYPE_AUTO ) {
+ if ( $block->getType() == DatabaseBlock::TYPE_RANGE ) {
$nt = MediaWikiServices::getInstance()->getNamespaceInfo()->
getCanonicalName( NS_USER ) . ':' . $block->getTarget();
}
}
if ( $sp->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
- if ( $target->getBlock() && $target->getBlock()->getType() != Block::TYPE_AUTO ) {
+ if ( $target->getBlock() && $target->getBlock()->getType() != DatabaseBlock::TYPE_AUTO ) {
$tools['block'] = $linkRenderer->makeKnownLink( # Change block link
SpecialPage::getTitleFor( 'Block', $username ),
$sp->msg( 'change-blocklink' )->text()
* @ingroup SpecialPage
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
/**
$links = $this->getLanguage()->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
- $block = Block::newFromTarget( $userObj, $userObj );
- if ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
- if ( $block->getType() == Block::TYPE_RANGE ) {
+ $block = DatabaseBlock::newFromTarget( $userObj, $userObj );
+ if ( !is_null( $block ) && $block->getType() != DatabaseBlock::TYPE_AUTO ) {
+ if ( $block->getType() == DatabaseBlock::TYPE_RANGE ) {
$nt = MediaWikiServices::getInstance()->getNamespaceInfo()->
getCanonicalName( NS_USER ) . ':' . $block->getTarget();
}
* @ingroup SpecialPage
*/
+use MediaWiki\Block\DatabaseBlock;
+
/**
* A special page for unblocking users
*
$this->checkReadOnly();
list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $this->getRequest() );
- $this->block = Block::newFromTarget( $this->target );
+ $this->block = DatabaseBlock::newFromTarget( $this->target );
if ( $this->target instanceof User ) {
# Set the 'relevant user' in the skin, so it displays links like Contributions,
# User logs, UserRights, etc.
if ( $form->show() ) {
switch ( $this->type ) {
- case Block::TYPE_IP:
+ case DatabaseBlock::TYPE_IP:
$out->addWikiMsg( 'unblocked-ip', wfEscapeWikiText( $this->target ) );
break;
- case Block::TYPE_USER:
+ case DatabaseBlock::TYPE_USER:
$out->addWikiMsg( 'unblocked', wfEscapeWikiText( $this->target ) );
break;
- case Block::TYPE_RANGE:
+ case DatabaseBlock::TYPE_RANGE:
$out->addWikiMsg( 'unblocked-range', wfEscapeWikiText( $this->target ) );
break;
- case Block::TYPE_ID:
- case Block::TYPE_AUTO:
+ case DatabaseBlock::TYPE_ID:
+ case DatabaseBlock::TYPE_AUTO:
$out->addWikiMsg( 'unblocked-id', wfEscapeWikiText( $this->target ) );
break;
}
]
];
- if ( $this->block instanceof Block ) {
+ if ( $this->block instanceof DatabaseBlock ) {
list( $target, $type ) = $this->block->getTargetAndType();
# Autoblocks are logged as "autoblock #123 because the IP was recently used by
# User:Foo, and we've just got any block, auto or not, that applies to a target
# the user has specified. Someone could be fishing to connect IPs to autoblocks,
# so don't show any distinction between unblocked IPs and autoblocked IPs
- if ( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ) {
+ if ( $type == DatabaseBlock::TYPE_AUTO && $this->type == DatabaseBlock::TYPE_IP ) {
$fields['Target']['default'] = $this->target;
unset( $fields['Name'] );
} else {
$fields['Target']['default'] = $target;
$fields['Target']['type'] = 'hidden';
switch ( $type ) {
- case Block::TYPE_IP:
+ case DatabaseBlock::TYPE_IP:
$fields['Name']['default'] = $this->getLinkRenderer()->makeKnownLink(
SpecialPage::getTitleFor( 'Contributions', $target->getName() ),
$target->getName()
);
$fields['Name']['raw'] = true;
break;
- case Block::TYPE_USER:
+ case DatabaseBlock::TYPE_USER:
$fields['Name']['default'] = $this->getLinkRenderer()->makeLink(
$target->getUserPage(),
$target->getName()
$fields['Name']['raw'] = true;
break;
- case Block::TYPE_RANGE:
+ case DatabaseBlock::TYPE_RANGE:
$fields['Name']['default'] = $target;
break;
- case Block::TYPE_AUTO:
+ case DatabaseBlock::TYPE_AUTO:
$fields['Name']['default'] = $this->block->getRedactedName();
$fields['Name']['raw'] = true;
# Don't expose the real target of the autoblock
public static function processUnblock( array $data, IContextSource $context ) {
$performer = $context->getUser();
$target = $data['Target'];
- $block = Block::newFromTarget( $data['Target'] );
+ $block = DatabaseBlock::newFromTarget( $data['Target'] );
- if ( !$block instanceof Block ) {
+ if ( !$block instanceof DatabaseBlock ) {
return [ [ 'ipb_cant_unblock', $target ] ];
}
# If the specified IP is a single address, and the block is a range block, don't
# unblock the whole range.
list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
- if ( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
+ if ( $block->getType() == DatabaseBlock::TYPE_RANGE && $type == DatabaseBlock::TYPE_IP ) {
$range = $block->getTarget();
return [ [ 'ipb_blocked_as_range', $target, $range ] ];
}
# Redact the name (IP address) for autoblocks
- if ( $block->getType() == Block::TYPE_AUTO ) {
+ if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
$page = Title::makeTitle( NS_USER, '#' . $block->getId() );
} else {
$page = $block->getTarget() instanceof User
/**
* @ingroup Pager
*/
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\Restriction;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
if ( $row->ipb_auto ) {
$formatted = $this->msg( 'autoblockid', $row->ipb_id )->parse();
} else {
- list( $target, $type ) = Block::parseTarget( $row->ipb_address );
+ list( $target, $type ) = DatabaseBlock::parseTarget( $row->ipb_address );
switch ( $type ) {
- case Block::TYPE_USER:
- case Block::TYPE_IP:
+ case DatabaseBlock::TYPE_USER:
+ case DatabaseBlock::TYPE_IP:
$formatted = Linker::userLink( $target->getId(), $target );
$formatted .= Linker::userToolLinks(
$target->getId(),
Linker::TOOL_LINKS_NOBLOCK
);
break;
- case Block::TYPE_RANGE:
+ case DatabaseBlock::TYPE_RANGE:
$formatted = htmlspecialchars( $target );
}
}
*/
use MediaWiki\Block\AbstractBlock;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\SystemBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\SessionManager;
if ( $user->isLoggedIn() ) {
$this->loadFromUserObject( $user );
if ( $user->getBlock() ) {
- // If this user is autoblocked, set a cookie to track the Block. This has to be done on
+ // If this user is autoblocked, set a cookie to track the block. This has to be done on
// every session load, because an autoblocked editor might not edit again from the same
// IP address after being blocked.
$this->trackBlockWithCookie();
return false;
}
- $userblock = Block::newFromTarget( $this->getName() );
+ $userblock = DatabaseBlock::newFromTarget( $this->getName() );
if ( !$userblock ) {
return false;
}
# blocked with createaccount disabled, prevent new account creation there even
# when the user is logged in
if ( $this->mBlockedFromCreateAccount === false && !$this->isAllowed( 'ipblock-exempt' ) ) {
- $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
+ $this->mBlockedFromCreateAccount = DatabaseBlock::newFromTarget(
+ null, $this->getRequest()->getIP()
+ );
}
return $this->mBlockedFromCreateAccount instanceof AbstractBlock
&& $this->mBlockedFromCreateAccount->appliesToRight( 'createaccount' )
require_once __DIR__ . '/Maintenance.php';
+use MediaWiki\Block\DatabaseBlock;
+
/**
* Maintenance script to clean up user blocks with user names not matching the
* 'user' table.
public function execute() {
$db = $this->getDB( DB_MASTER );
- $blockQuery = Block::getQueryInfo();
+ $blockQuery = DatabaseBlock::getQueryInfo();
$max = $db->selectField( 'ipblocks', 'MAX(ipb_user)' );
$blockQuery['joins']
);
foreach ( $res2 as $row2 ) {
- $block = Block::newFromRow( $row2 );
+ $block = DatabaseBlock::newFromRow( $row2 );
if ( !$bestBlock ) {
$bestBlock = $block;
continue;
}
// Find the most-restrictive block. Can't use
- // Block::chooseBlock because that's for IP blocks, not
+ // DatabaseBlock::chooseBlock because that's for IP blocks, not
// user blocks.
$keep = null;
if ( $keep === null && $block->getExpiry() !== $bestBlock->getExpiry() ) {
+++ /dev/null
-<?php
-
-use MediaWiki\Block\BlockRestrictionStore;
-use MediaWiki\Block\Restriction\PageRestriction;
-use MediaWiki\Block\Restriction\NamespaceRestriction;
-use MediaWiki\MediaWikiServices;
-
-/**
- * @group Database
- * @group Blocking
- */
-class BlockTest extends MediaWikiLangTestCase {
-
- /**
- * @return User
- */
- private function getUserForBlocking() {
- $testUser = $this->getMutableTestUser();
- $user = $testUser->getUser();
- $user->addToDatabase();
- TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
- $user->saveSettings();
- return $user;
- }
-
- /**
- * @param User $user
- *
- * @return Block
- * @throws MWException
- */
- private function addBlockForUser( User $user ) {
- // Delete the last round's block if it's still there
- $oldBlock = Block::newFromTarget( $user->getName() );
- if ( $oldBlock ) {
- // An old block will prevent our new one from saving.
- $oldBlock->delete();
- }
-
- $blockOptions = [
- 'address' => $user->getName(),
- 'user' => $user->getId(),
- 'by' => $this->getTestSysop()->getUser()->getId(),
- 'reason' => 'Parce que',
- 'expiry' => time() + 100500,
- ];
- $block = new Block( $blockOptions );
-
- $block->insert();
- // save up ID for use in assertion. Since ID is an autoincrement,
- // its value might change depending on the order the tests are run.
- // ApiBlockTest insert its own blocks!
- if ( !$block->getId() ) {
- throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
- }
-
- $this->addXffBlocks();
-
- return $block;
- }
-
- /**
- * @covers Block::newFromTarget
- */
- public function testINewFromTargetReturnsCorrectBlock() {
- $user = $this->getUserForBlocking();
- $block = $this->addBlockForUser( $user );
-
- $this->assertTrue(
- $block->equals( Block::newFromTarget( $user->getName() ) ),
- "newFromTarget() returns the same block as the one that was made"
- );
- }
-
- /**
- * @covers Block::newFromID
- */
- public function testINewFromIDReturnsCorrectBlock() {
- $user = $this->getUserForBlocking();
- $block = $this->addBlockForUser( $user );
-
- $this->assertTrue(
- $block->equals( Block::newFromID( $block->getId() ) ),
- "newFromID() returns the same block as the one that was made"
- );
- }
-
- /**
- * per T28425
- * @covers Block::__construct
- */
- public function testT28425BlockTimestampDefaultsToTime() {
- $user = $this->getUserForBlocking();
- $block = $this->addBlockForUser( $user );
- $madeAt = wfTimestamp( TS_MW );
-
- // delta to stop one-off errors when things happen to go over a second mark.
- $delta = abs( $madeAt - $block->getTimestamp() );
- $this->assertLessThan(
- 2,
- $delta,
- "If no timestamp is specified, the block is recorded as time()"
- );
- }
-
- /**
- * CheckUser since being changed to use Block::newFromTarget started failing
- * because the new function didn't accept empty strings like Block::load()
- * had. Regression T31116.
- *
- * @dataProvider provideT31116Data
- * @covers Block::newFromTarget
- */
- public function testT31116NewFromTargetWithEmptyIp( $vagueTarget ) {
- $user = $this->getUserForBlocking();
- $initialBlock = $this->addBlockForUser( $user );
- $block = Block::newFromTarget( $user->getName(), $vagueTarget );
-
- $this->assertTrue(
- $initialBlock->equals( $block ),
- "newFromTarget() returns the same block as the one that was made when "
- . "given empty vagueTarget param " . var_export( $vagueTarget, true )
- );
- }
-
- public static function provideT31116Data() {
- return [
- [ null ],
- [ '' ],
- [ false ]
- ];
- }
-
- /**
- * @dataProvider provideNewFromTargetRangeBlocks
- * @covers Block::newFromTarget
- */
- public function testNewFromTargetRangeBlocks( $targets, $ip, $expectedTarget ) {
- $blocker = $this->getTestSysop()->getUser();
-
- foreach ( $targets as $target ) {
- $block = new Block();
- $block->setTarget( $target );
- $block->setBlocker( $blocker );
- $block->insert();
- }
-
- // Should find the block with the narrowest range
- $blockTarget = Block::newFromTarget( $this->getTestUser()->getUser(), $ip )->getTarget();
- $this->assertSame(
- $blockTarget instanceof User ? $blockTarget->getName() : $blockTarget,
- $expectedTarget
- );
-
- foreach ( $targets as $target ) {
- $block = Block::newFromTarget( $target );
- $block->delete();
- }
- }
-
- function provideNewFromTargetRangeBlocks() {
- return [
- 'Blocks to IPv4 ranges' => [
- [ '0.0.0.0/20', '0.0.0.0/30', '0.0.0.0/25' ],
- '0.0.0.0',
- '0.0.0.0/30'
- ],
- 'Blocks to IPv6 ranges' => [
- [ '0:0:0:0:0:0:0:0/20', '0:0:0:0:0:0:0:0/30', '0:0:0:0:0:0:0:0/25' ],
- '0:0:0:0:0:0:0:0',
- '0:0:0:0:0:0:0:0/30'
- ],
- 'Blocks to wide IPv4 range and IP' => [
- [ '0.0.0.0/16', '0.0.0.0' ],
- '0.0.0.0',
- '0.0.0.0'
- ],
- 'Blocks to narrow IPv4 range and IP' => [
- [ '0.0.0.0/31', '0.0.0.0' ],
- '0.0.0.0',
- '0.0.0.0'
- ],
- 'Blocks to wide IPv6 range and IP' => [
- [ '0:0:0:0:0:0:0:0/19', '0:0:0:0:0:0:0:0' ],
- '0:0:0:0:0:0:0:0',
- '0:0:0:0:0:0:0:0'
- ],
- 'Blocks to narrow IPv6 range and IP' => [
- [ '0:0:0:0:0:0:0:0/127', '0:0:0:0:0:0:0:0' ],
- '0:0:0:0:0:0:0:0',
- '0:0:0:0:0:0:0:0'
- ],
- 'Blocks to wide IPv6 range and IP, large numbers' => [
- [ '2000:DEAD:BEEF:A:0:0:0:0/19', '2000:DEAD:BEEF:A:0:0:0:0' ],
- '2000:DEAD:BEEF:A:0:0:0:0',
- '2000:DEAD:BEEF:A:0:0:0:0'
- ],
- 'Blocks to narrow IPv6 range and IP, large numbers' => [
- [ '2000:DEAD:BEEF:A:0:0:0:0/127', '2000:DEAD:BEEF:A:0:0:0:0' ],
- '2000:DEAD:BEEF:A:0:0:0:0',
- '2000:DEAD:BEEF:A:0:0:0:0'
- ],
- ];
- }
-
- /**
- * @covers Block::appliesToRight
- */
- public function testBlockedUserCanNotCreateAccount() {
- $username = 'BlockedUserToCreateAccountWith';
- $u = User::newFromName( $username );
- $u->addToDatabase();
- $userId = $u->getId();
- $this->assertNotEquals( 0, $userId, 'sanity' );
- TestUser::setPasswordForUser( $u, 'NotRandomPass' );
- unset( $u );
-
- // Sanity check
- $this->assertNull(
- Block::newFromTarget( $username ),
- "$username should not be blocked"
- );
-
- // Reload user
- $u = User::newFromName( $username );
- $this->assertFalse(
- $u->isBlockedFromCreateAccount(),
- "Our sandbox user should be able to create account before being blocked"
- );
-
- // Foreign perspective (blockee not on current wiki)...
- $blockOptions = [
- 'address' => $username,
- 'user' => $userId,
- 'reason' => 'crosswiki block...',
- 'timestamp' => wfTimestampNow(),
- 'expiry' => $this->db->getInfinity(),
- 'createAccount' => true,
- 'enableAutoblock' => true,
- 'hideName' => true,
- 'blockEmail' => true,
- 'byText' => 'm>MetaWikiUser',
- ];
- $block = new Block( $blockOptions );
- $block->insert();
-
- // Reload block from DB
- $userBlock = Block::newFromTarget( $username );
- $this->assertTrue(
- (bool)$block->appliesToRight( 'createaccount' ),
- "Block object in DB should block right 'createaccount'"
- );
-
- $this->assertInstanceOf(
- Block::class,
- $userBlock,
- "'$username' block block object should be existent"
- );
-
- // Reload user
- $u = User::newFromName( $username );
- $this->assertTrue(
- (bool)$u->isBlockedFromCreateAccount(),
- "Our sandbox user '$username' should NOT be able to create account"
- );
- }
-
- /**
- * @covers Block::insert
- */
- public function testCrappyCrossWikiBlocks() {
- // Delete the last round's block if it's still there
- $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' );
- if ( $oldBlock ) {
- // An old block will prevent our new one from saving.
- $oldBlock->delete();
- }
-
- // Local perspective (blockee on current wiki)...
- $user = User::newFromName( 'UserOnForeignWiki' );
- $user->addToDatabase();
- $userId = $user->getId();
- $this->assertNotEquals( 0, $userId, 'sanity' );
-
- // Foreign perspective (blockee not on current wiki)...
- $blockOptions = [
- 'address' => 'UserOnForeignWiki',
- 'user' => $user->getId(),
- 'reason' => 'crosswiki block...',
- 'timestamp' => wfTimestampNow(),
- 'expiry' => $this->db->getInfinity(),
- 'createAccount' => true,
- 'enableAutoblock' => true,
- 'hideName' => true,
- 'blockEmail' => true,
- 'byText' => 'Meta>MetaWikiUser',
- ];
- $block = new Block( $blockOptions );
-
- $res = $block->insert( $this->db );
- $this->assertTrue( (bool)$res['id'], 'Block succeeded' );
-
- $user = null; // clear
-
- $block = Block::newFromID( $res['id'] );
- $this->assertEquals(
- 'UserOnForeignWiki',
- $block->getTarget()->getName(),
- 'Correct blockee name'
- );
- $this->assertEquals( $userId, $block->getTarget()->getId(), 'Correct blockee id' );
- $this->assertEquals( 'Meta>MetaWikiUser', $block->getBlocker()->getName(),
- 'Correct blocker name' );
- $this->assertEquals( 'Meta>MetaWikiUser', $block->getByName(), 'Correct blocker name' );
- $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' );
- }
-
- protected function addXffBlocks() {
- static $inited = false;
-
- if ( $inited ) {
- return;
- }
-
- $inited = true;
-
- $blockList = [
- [ 'target' => '70.2.0.0/16',
- 'type' => Block::TYPE_RANGE,
- 'desc' => 'Range Hardblock',
- 'ACDisable' => false,
- 'isHardblock' => true,
- 'isAutoBlocking' => false,
- ],
- [ 'target' => '2001:4860:4001::/48',
- 'type' => Block::TYPE_RANGE,
- 'desc' => 'Range6 Hardblock',
- 'ACDisable' => false,
- 'isHardblock' => true,
- 'isAutoBlocking' => false,
- ],
- [ 'target' => '60.2.0.0/16',
- 'type' => Block::TYPE_RANGE,
- 'desc' => 'Range Softblock with AC Disabled',
- 'ACDisable' => true,
- 'isHardblock' => false,
- 'isAutoBlocking' => false,
- ],
- [ 'target' => '50.2.0.0/16',
- 'type' => Block::TYPE_RANGE,
- 'desc' => 'Range Softblock',
- 'ACDisable' => false,
- 'isHardblock' => false,
- 'isAutoBlocking' => false,
- ],
- [ 'target' => '50.1.1.1',
- 'type' => Block::TYPE_IP,
- 'desc' => 'Exact Softblock',
- 'ACDisable' => false,
- 'isHardblock' => false,
- 'isAutoBlocking' => false,
- ],
- ];
-
- $blocker = $this->getTestUser()->getUser();
- foreach ( $blockList as $insBlock ) {
- $target = $insBlock['target'];
-
- if ( $insBlock['type'] === Block::TYPE_IP ) {
- $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName();
- } elseif ( $insBlock['type'] === Block::TYPE_RANGE ) {
- $target = IP::sanitizeRange( $target );
- }
-
- $block = new Block();
- $block->setTarget( $target );
- $block->setBlocker( $blocker );
- $block->setReason( $insBlock['desc'] );
- $block->setExpiry( 'infinity' );
- $block->isCreateAccountBlocked( $insBlock['ACDisable'] );
- $block->isHardblock( $insBlock['isHardblock'] );
- $block->isAutoblocking( $insBlock['isAutoBlocking'] );
- $block->insert();
- }
- }
-
- public static function providerXff() {
- return [
- [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range Hardblock'
- ],
- [ 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range Softblock with AC Disabled'
- ],
- [ 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Exact Softblock'
- ],
- [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5',
- 'count' => 3,
- 'result' => 'Exact Softblock'
- ],
- [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range Hardblock'
- ],
- [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range Hardblock'
- ],
- [ 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range Softblock with AC Disabled'
- ],
- [ 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Exact Softblock'
- ],
- [ 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5',
- 'count' => 1,
- 'result' => 'Range Softblock with AC Disabled'
- ],
- [ 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5',
- 'count' => 2,
- 'result' => 'Range6 Hardblock'
- ],
- ];
- }
-
- /**
- * @dataProvider providerXff
- * @covers Block::getBlocksForIPList
- * @covers Block::chooseBlock
- */
- public function testBlocksOnXff( $xff, $exCount, $exResult ) {
- $user = $this->getUserForBlocking();
- $this->addBlockForUser( $user );
-
- $list = array_map( 'trim', explode( ',', $xff ) );
- $xffblocks = Block::getBlocksForIPList( $list, true );
- $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff );
- $block = Block::chooseBlock( $xffblocks, $list );
- $this->assertEquals(
- $exResult, $block->getReason(), 'Correct block type for XFF header ' . $xff
- );
- }
-
- /**
- * @covers Block::newFromRow
- */
- public function testNewFromRow() {
- $badActor = $this->getTestUser()->getUser();
- $sysop = $this->getTestSysop()->getUser();
-
- $block = new Block( [
- 'address' => $badActor->getName(),
- 'user' => $badActor->getId(),
- 'by' => $sysop->getId(),
- 'expiry' => 'infinity',
- ] );
- $block->insert();
-
- $blockQuery = Block::getQueryInfo();
- $row = $this->db->select(
- $blockQuery['tables'],
- $blockQuery['fields'],
- [
- 'ipb_id' => $block->getId(),
- ],
- __METHOD__,
- [],
- $blockQuery['joins']
- )->fetchObject();
-
- $block = Block::newFromRow( $row );
- $this->assertInstanceOf( Block::class, $block );
- $this->assertEquals( $block->getBy(), $sysop->getId() );
- $this->assertEquals( $block->getTarget()->getName(), $badActor->getName() );
- $block->delete();
- }
-
- /**
- * @covers Block::equals
- */
- public function testEquals() {
- $block = new Block();
-
- $this->assertTrue( $block->equals( $block ) );
-
- $partial = new Block( [
- 'sitewide' => false,
- ] );
- $this->assertFalse( $block->equals( $partial ) );
- }
-
- /**
- * @covers Block::isSitewide
- */
- public function testIsSitewide() {
- $block = new Block();
- $this->assertTrue( $block->isSitewide() );
-
- $block = new Block( [
- 'sitewide' => true,
- ] );
- $this->assertTrue( $block->isSitewide() );
-
- $block = new Block( [
- 'sitewide' => false,
- ] );
- $this->assertFalse( $block->isSitewide() );
-
- $block = new Block( [
- 'sitewide' => false,
- ] );
- $block->isSitewide( true );
- $this->assertTrue( $block->isSitewide() );
- }
-
- /**
- * @covers Block::getRestrictions
- * @covers Block::setRestrictions
- */
- public function testRestrictions() {
- $block = new Block();
- $restrictions = [
- new PageRestriction( 0, 1 )
- ];
- $block->setRestrictions( $restrictions );
-
- $this->assertSame( $restrictions, $block->getRestrictions() );
- }
-
- /**
- * @covers Block::getRestrictions
- * @covers Block::insert
- */
- public function testRestrictionsFromDatabase() {
- $badActor = $this->getTestUser()->getUser();
- $sysop = $this->getTestSysop()->getUser();
-
- $block = new Block( [
- 'address' => $badActor->getName(),
- 'user' => $badActor->getId(),
- 'by' => $sysop->getId(),
- 'expiry' => 'infinity',
- ] );
- $page = $this->getExistingTestPage( 'Foo' );
- $restriction = new PageRestriction( 0, $page->getId() );
- $block->setRestrictions( [ $restriction ] );
- $block->insert();
-
- // Refresh the block from the database.
- $block = Block::newFromID( $block->getId() );
- $restrictions = $block->getRestrictions();
- $this->assertCount( 1, $restrictions );
- $this->assertTrue( $restriction->equals( $restrictions[0] ) );
- $block->delete();
- }
-
- /**
- * @covers Block::insert
- */
- public function testInsertExistingBlock() {
- $badActor = $this->getTestUser()->getUser();
- $sysop = $this->getTestSysop()->getUser();
-
- $block = new Block( [
- 'address' => $badActor->getName(),
- 'user' => $badActor->getId(),
- 'by' => $sysop->getId(),
- 'expiry' => 'infinity',
- ] );
- $page = $this->getExistingTestPage( 'Foo' );
- $restriction = new PageRestriction( 0, $page->getId() );
- $block->setRestrictions( [ $restriction ] );
- $block->insert();
-
- // Insert the block again, which should result in a failur
- $result = $block->insert();
-
- $this->assertFalse( $result );
-
- // Ensure that there are no restrictions where the blockId is 0.
- $count = $this->db->selectRowCount(
- 'ipblocks_restrictions',
- '*',
- [ 'ir_ipb_id' => 0 ],
- __METHOD__
- );
- $this->assertSame( 0, $count );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToTitle
- */
- public function testAppliesToTitleReturnsTrueOnSitewideBlock() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $user = $this->getTestUser()->getUser();
- $block = new Block( [
- 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
- 'allowUsertalk' => true,
- 'sitewide' => true
- ] );
-
- $block->setTarget( $user );
- $block->setBlocker( $this->getTestSysop()->getUser() );
- $block->insert();
-
- $title = $this->getExistingTestPage( 'Foo' )->getTitle();
-
- $this->assertTrue( $block->appliesToTitle( $title ) );
-
- // appliesToTitle() ignores allowUsertalk
- $title = $user->getTalkPage();
- $this->assertTrue( $block->appliesToTitle( $title ) );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToTitle
- */
- public function testAppliesToTitleOnPartialBlock() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $user = $this->getTestUser()->getUser();
- $block = new Block( [
- 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
- 'allowUsertalk' => true,
- 'sitewide' => false
- ] );
-
- $block->setTarget( $user );
- $block->setBlocker( $this->getTestSysop()->getUser() );
- $block->insert();
-
- $pageFoo = $this->getExistingTestPage( 'Foo' );
- $pageBar = $this->getExistingTestPage( 'Bar' );
- $pageJohn = $this->getExistingTestPage( 'User:John' );
-
- $pageRestriction = new PageRestriction( $block->getId(), $pageFoo->getId() );
- $namespaceRestriction = new NamespaceRestriction( $block->getId(), NS_USER );
- $this->getBlockRestrictionStore()->insert( [ $pageRestriction, $namespaceRestriction ] );
-
- $this->assertTrue( $block->appliesToTitle( $pageFoo->getTitle() ) );
- $this->assertFalse( $block->appliesToTitle( $pageBar->getTitle() ) );
- $this->assertTrue( $block->appliesToTitle( $pageJohn->getTitle() ) );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToNamespace
- * @covers Block::appliesToPage
- */
- public function testAppliesToReturnsTrueOnSitewideBlock() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $user = $this->getTestUser()->getUser();
- $block = new Block( [
- 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
- 'allowUsertalk' => true,
- 'sitewide' => true
- ] );
-
- $block->setTarget( $user );
- $block->setBlocker( $this->getTestSysop()->getUser() );
- $block->insert();
-
- $title = $this->getExistingTestPage()->getTitle();
-
- $this->assertTrue( $block->appliesToPage( $title->getArticleID() ) );
- $this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
- $this->assertTrue( $block->appliesToNamespace( NS_USER_TALK ) );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToPage
- */
- public function testAppliesToPageOnPartialPageBlock() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $user = $this->getTestUser()->getUser();
- $block = new Block( [
- 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
- 'allowUsertalk' => true,
- 'sitewide' => false
- ] );
-
- $block->setTarget( $user );
- $block->setBlocker( $this->getTestSysop()->getUser() );
- $block->insert();
-
- $title = $this->getExistingTestPage()->getTitle();
-
- $pageRestriction = new PageRestriction(
- $block->getId(),
- $title->getArticleID()
- );
- $this->getBlockRestrictionStore()->insert( [ $pageRestriction ] );
-
- $this->assertTrue( $block->appliesToPage( $title->getArticleID() ) );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToNamespace
- */
- public function testAppliesToNamespaceOnPartialNamespaceBlock() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $user = $this->getTestUser()->getUser();
- $block = new Block( [
- 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
- 'allowUsertalk' => true,
- 'sitewide' => false
- ] );
-
- $block->setTarget( $user );
- $block->setBlocker( $this->getTestSysop()->getUser() );
- $block->insert();
-
- $namespaceRestriction = new NamespaceRestriction( $block->getId(), NS_MAIN );
- $this->getBlockRestrictionStore()->insert( [ $namespaceRestriction ] );
-
- $this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
- $this->assertFalse( $block->appliesToNamespace( NS_USER ) );
-
- $block->delete();
- }
-
- /**
- * @covers Block::appliesToRight
- */
- public function testBlockAllowsPurge() {
- $this->setMwGlobals( [
- 'wgBlockDisablesLogin' => false,
- ] );
- $block = new Block();
- $this->assertFalse( $block->appliesToRight( 'purge' ) );
- }
-
- /**
- * Get an instance of BlockRestrictionStore
- *
- * @return BlockRestrictionStore
- */
- protected function getBlockRestrictionStore() : BlockRestrictionStore {
- return MediaWikiServices::getInstance()->getBlockRestrictionStore();
- }
-}
namespace MediaWiki\Tests\Permissions;
use Action;
-use Block;
use MediaWikiLangTestCase;
use RequestContext;
use Title;
use User;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\SystemBlock;
$prev = time();
$now = time() + 120;
$this->user->mBlockedby = $this->user->getId();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
global $wgLocalTZoffset;
$wgLocalTZoffset = -60;
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
// partial block message test
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
$now = time();
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
// Block the user
$blocker = $this->getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'hideName' => true,
'allowUsertalk' => false,
'reason' => 'Because',
// Clear cache and confirm it loaded the block properly
$user->clearInstanceCache();
- $this->assertInstanceOf( Block::class, $user->getBlock( false ) );
+ $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock( false ) );
//$this->assertSame( $blocker->getName(), $user->blockedBy() );
//$this->assertSame( 'Because', $user->blockedFor() );
//$this->assertTrue( (bool)$user->isHidden() );
* @param bool $expect Expected result from User::isBlockedFrom()
* @param array $options Additional test options:
* - 'blockAllowsUTEdit': (bool, default true) Value for $wgBlockAllowsUTEdit
- * - 'allowUsertalk': (bool, default false) Passed to Block::__construct()
+ * - 'allowUsertalk': (bool, default false) Passed to DatabaseBlock::__construct()
* - 'pageRestrictions': (array|null) If non-empty, page restriction titles for the block.
*/
public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
$restrictions[] = new NamespaceRestriction( 0, $ns );
}
- $block = new Block( [
+ $block = new DatabaseBlock( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
'allowUsertalk' => $options['allowUsertalk'] ?? false,
'sitewide' => !$restrictions,
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\SystemBlock;
use MediaWiki\MediaWikiServices;
$prev = time();
$now = time() + 120;
$this->user->mBlockedby = $this->user->getId();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
global $wgLocalTZoffset;
$wgLocalTZoffset = -60;
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
// partial block message test
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
$now = time();
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( [
+ $this->user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
/**
$page = $this->getExistingTestPage();
$action = Action::factory( 'unblock', $page, $this->getContext() );
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $user,
'by' => $this->getTestSysop()->getUser()->getId(),
'expiry' => 'infinity',
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use Wikimedia\TestingAccessWrapper;
// Has a blocked $user, so special block handling
$user = $this->getMutableTestUser()->getUser();
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $user->getName(),
'user' => $user->getID(),
'by' => $this->getTestSysop()->getUser()->getId(),
// Has a blocked $user, so special block handling
$user = $this->getMutableTestUser()->getUser();
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $user->getName(),
'user' => $user->getID(),
'by' => $this->getTestSysop()->getUser()->getId(),
<?php
use Wikimedia\TestingAccessWrapper;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\SystemBlock;
/**
public static function provideGetBlockDetails() {
return [
'Sitewide block' => [
- new Block(),
+ new DatabaseBlock(),
[ 'blockpartial' => false ],
],
'Partial block' => [
- new Block( [ 'sitewide' => false ] ),
+ new DatabaseBlock( [ 'sitewide' => false ] ),
[ 'blockpartial' => true ],
],
'System block' => [
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
$ret = $this->doApiRequest( array_merge( $params, $extraParams ), null,
false, $blocker );
- $block = Block::newFromTarget( $this->mUser->getName() );
+ $block = DatabaseBlock::newFromTarget( $this->mUser->getName() );
$this->assertTrue( !is_null( $block ), 'Block is valid' );
'You cannot block or unblock other users because you are yourself blocked.' );
$blocked = $this->getMutableTestUser( [ 'sysop' ] )->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $blocked->getName(),
'by' => self::$users['sysop']->getUser()->getId(),
'reason' => 'Capriciousness',
$this->doBlock();
- $block = Block::newFromTarget( $this->mUser->getName() );
+ $block = DatabaseBlock::newFromTarget( $this->mUser->getName() );
$this->assertTrue( $block->isSitewide() );
$this->assertCount( 0, $block->getRestrictions() );
'namespacerestrictions' => $namespace,
] );
- $block = Block::newFromTarget( $this->mUser->getName() );
+ $block = DatabaseBlock::newFromTarget( $this->mUser->getName() );
$this->assertFalse( $block->isSitewide() );
$this->assertCount( 2, $block->getRestrictions() );
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* Tests for MediaWiki api.php?action=edit.
*
public function testEditWhileBlocked() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
- $this->assertNull( Block::newFromTarget( '127.0.0.1' ), 'Sanity check' );
+ $this->assertNull( DatabaseBlock::newFromTarget( '127.0.0.1' ), 'Sanity check' );
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => self::$users['sysop']->getUser()->getName(),
'by' => self::$users['sysop']->getUser()->getId(),
'reason' => 'Capriciousness',
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
- $this->assertNotNull( Block::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
+ $this->assertNotNull( DatabaseBlock::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
} finally {
$block->delete();
self::$users['sysop']->getUser()->clearInstanceCache();
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @group API
* @group Database
}
public function testMoveWhileBlocked() {
- $this->assertNull( Block::newFromTarget( '127.0.0.1' ), 'Sanity check' );
+ $this->assertNull( DatabaseBlock::newFromTarget( '127.0.0.1' ), 'Sanity check' );
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => self::$users['sysop']->getUser()->getName(),
'by' => self::$users['sysop']->getUser()->getId(),
'reason' => 'Capriciousness',
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertSame( 'You have been blocked from editing.', $ex->getMessage() );
- $this->assertNotNull( Block::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
+ $this->assertNotNull( DatabaseBlock::newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
} finally {
$block->delete();
self::$users['sysop']->getUser()->clearInstanceCache();
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @group API
* @group medium
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @group medium
* @covers ApiQueryUserInfo
'userinfo'
);
- $block = new Block();
+ $block = new DatabaseBlock();
$info = $apiQueryUserInfo->getBlockInfo( $block );
$subset = [
'blockid' => null,
'userinfo'
);
- $block = new Block( [
+ $block = new DatabaseBlock( [
'sitewide' => false,
] );
$info = $apiQueryUserInfo->getBlockInfo( $block );
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @group API
* @group Database
$this->blockee = $this->getMutableTestUser()->getUser();
// Initialize a blocked user (used by most tests, although not all)
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $this->blockee->getName(),
'by' => $this->blocker->getId(),
] );
$result = $block->insert();
$this->assertNotFalse( $result, 'Could not insert block' );
- $blockFromDB = Block::newFromID( $result['id'] );
+ $blockFromDB = DatabaseBlock::newFromID( $result['id'] );
$this->assertTrue( !is_null( $blockFromDB ), 'Could not retrieve block' );
}
private function getBlockFromParams( array $params ) {
if ( array_key_exists( 'user', $params ) ) {
- return Block::newFromTarget( $params['user'] );
+ return DatabaseBlock::newFromTarget( $params['user'] );
}
if ( array_key_exists( 'userid', $params ) ) {
- return Block::newFromTarget( User::newFromId( $params['userid'] ) );
+ return DatabaseBlock::newFromTarget( User::newFromId( $params['userid'] ) );
}
- return Block::newFromId( $params['id'] );
+ return DatabaseBlock::newFromId( $params['id'] );
}
/**
// We only check later on whether the block existed to begin with, because maybe the caller
// expects doApiRequestWithToken to throw, in which case the block might not be expected to
// exist to begin with.
- $this->assertInstanceOf( Block::class, $originalBlock, 'Block should initially exist' );
+ $this->assertInstanceOf( DatabaseBlock::class, $originalBlock, 'Block should initially exist' );
$this->assertNull( $this->getBlockFromParams( $params ), 'Block should have been removed' );
}
public function testUnblockWhenBlocked() {
$this->setExpectedApiException( 'ipbblocked' );
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $this->blocker->getName(),
'by' => $this->getTestUser( 'sysop' )->getUser()->getId(),
] );
}
public function testUnblockSelfWhenBlocked() {
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $this->blocker->getName(),
'by' => $this->getTestUser( 'sysop' )->getUser()->getId(),
] );
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @group API
* @group Database
public function testBlockedWithUserrights() {
global $wgUser;
- $block = new Block( [ 'address' => $wgUser, 'by' => $wgUser->getId(), ] );
+ $block = new DatabaseBlock( [ 'address' => $wgUser, 'by' => $wgUser->getId(), ] );
$block->insert();
try {
$this->setPermissions( true, true );
- $block = new Block( [ 'address' => $user, 'by' => $user->getId() ] );
+ $block = new DatabaseBlock( [ 'address' => $user, 'by' => $user->getId() ] );
$block->insert();
try {
namespace MediaWiki\Auth;
use Config;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Session\SessionInfo;
use MediaWiki\Session\UserInfo;
use Psr\Log\LoggerInterface;
\TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
$user->saveSettings();
}
- $oldBlock = \Block::newFromTarget( 'UTBlockee' );
+ $oldBlock = DatabaseBlock::newFromTarget( 'UTBlockee' );
if ( $oldBlock ) {
// An old block will prevent our new one from saving.
$oldBlock->delete();
'expiry' => time() + 100500,
'createAccount' => true,
];
- $block = new \Block( $blockOptions );
+ $block = new DatabaseBlock( $blockOptions );
$block->insert();
$status = $this->manager->checkAccountCreatePermissions( $user );
$this->assertFalse( $status->isOK() );
'expiry' => time() + 100500,
'createAccount' => true,
];
- $block = new \Block( $blockOptions );
+ $block = new DatabaseBlock( $blockOptions );
$block->insert();
$scopeVariable = new ScopedCallback( [ $block, 'delete' ] );
$status = $this->manager->checkAccountCreatePermissions( new \User );
namespace MediaWiki\Auth;
+use MediaWiki\Block\DatabaseBlock;
use Wikimedia\TestingAccessWrapper;
/**
\TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
$user->saveSettings();
}
- $oldBlock = \Block::newFromTarget( 'UTBlockee' );
+ $oldBlock = DatabaseBlock::newFromTarget( 'UTBlockee' );
if ( $oldBlock ) {
// An old block will prevent our new one from saving.
$oldBlock->delete();
'expiry' => time() + 100500,
'createAccount' => true,
];
- $block = new \Block( $blockOptions );
+ $block = new DatabaseBlock( $blockOptions );
$block->insert();
return $user;
}
'expiry' => time() + 100500,
'createAccount' => true,
];
- $block = new \Block( $blockOptions );
+ $block = new DatabaseBlock( $blockOptions );
$block->insert();
$scopeVariable = new \Wikimedia\ScopedCallback( [ $block, 'delete' ] );
<?php
use MediaWiki\Block\BlockManager;
+use MediaWiki\Block\DatabaseBlock;
/**
* @group Blocking
'wgCookieSetOnIpBlock' => true,
] );
- $block = new Block( array_merge( [
+ $block = new DatabaseBlock( array_merge( [
'address' => $options[ 'target' ] ?: $this->user,
'by' => $this->sysopId,
], $options[ 'blockOptions' ] ) );
namespace MediaWiki\Tests\Block;
use MediaWiki\Block\BlockRestrictionStore;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\Restriction;
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
--- /dev/null
+<?php
+
+use MediaWiki\Block\BlockRestrictionStore;
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\Restriction\NamespaceRestriction;
+use MediaWiki\MediaWikiServices;
+
+/**
+ * @group Database
+ * @group Blocking
+ * @coversDefaultClass \MediaWiki\Block\DatabaseBlock
+ */
+class DatabaseBlockTest extends MediaWikiLangTestCase {
+
+ /**
+ * @return User
+ */
+ private function getUserForBlocking() {
+ $testUser = $this->getMutableTestUser();
+ $user = $testUser->getUser();
+ $user->addToDatabase();
+ TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
+ $user->saveSettings();
+ return $user;
+ }
+
+ /**
+ * @param User $user
+ *
+ * @return DatabaseBlock
+ * @throws MWException
+ */
+ private function addBlockForUser( User $user ) {
+ // Delete the last round's block if it's still there
+ $oldBlock = DatabaseBlock::newFromTarget( $user->getName() );
+ if ( $oldBlock ) {
+ // An old block will prevent our new one from saving.
+ $oldBlock->delete();
+ }
+
+ $blockOptions = [
+ 'address' => $user->getName(),
+ 'user' => $user->getId(),
+ 'by' => $this->getTestSysop()->getUser()->getId(),
+ 'reason' => 'Parce que',
+ 'expiry' => time() + 100500,
+ ];
+ $block = new DatabaseBlock( $blockOptions );
+
+ $block->insert();
+ // save up ID for use in assertion. Since ID is an autoincrement,
+ // its value might change depending on the order the tests are run.
+ // ApiBlockTest insert its own blocks!
+ if ( !$block->getId() ) {
+ throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
+ }
+
+ $this->addXffBlocks();
+
+ return $block;
+ }
+
+ /**
+ * @covers ::newFromTarget
+ */
+ public function testINewFromTargetReturnsCorrectBlock() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+
+ $this->assertTrue(
+ $block->equals( DatabaseBlock::newFromTarget( $user->getName() ) ),
+ "newFromTarget() returns the same block as the one that was made"
+ );
+ }
+
+ /**
+ * @covers ::newFromID
+ */
+ public function testINewFromIDReturnsCorrectBlock() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+
+ $this->assertTrue(
+ $block->equals( DatabaseBlock::newFromID( $block->getId() ) ),
+ "newFromID() returns the same block as the one that was made"
+ );
+ }
+
+ /**
+ * per T28425
+ * @covers ::__construct
+ */
+ public function testT28425BlockTimestampDefaultsToTime() {
+ $user = $this->getUserForBlocking();
+ $block = $this->addBlockForUser( $user );
+ $madeAt = wfTimestamp( TS_MW );
+
+ // delta to stop one-off errors when things happen to go over a second mark.
+ $delta = abs( $madeAt - $block->getTimestamp() );
+ $this->assertLessThan(
+ 2,
+ $delta,
+ "If no timestamp is specified, the block is recorded as time()"
+ );
+ }
+
+ /**
+ * CheckUser since being changed to use DatabaseBlock::newFromTarget started failing
+ * because the new function didn't accept empty strings like DatabaseBlock::load()
+ * had. Regression T31116.
+ *
+ * @dataProvider provideT31116Data
+ * @covers ::newFromTarget
+ */
+ public function testT31116NewFromTargetWithEmptyIp( $vagueTarget ) {
+ $user = $this->getUserForBlocking();
+ $initialBlock = $this->addBlockForUser( $user );
+ $block = DatabaseBlock::newFromTarget( $user->getName(), $vagueTarget );
+
+ $this->assertTrue(
+ $initialBlock->equals( $block ),
+ "newFromTarget() returns the same block as the one that was made when "
+ . "given empty vagueTarget param " . var_export( $vagueTarget, true )
+ );
+ }
+
+ public static function provideT31116Data() {
+ return [
+ [ null ],
+ [ '' ],
+ [ false ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewFromTargetRangeBlocks
+ * @covers ::newFromTarget
+ */
+ public function testNewFromTargetRangeBlocks( $targets, $ip, $expectedTarget ) {
+ $blocker = $this->getTestSysop()->getUser();
+
+ foreach ( $targets as $target ) {
+ $block = new DatabaseBlock();
+ $block->setTarget( $target );
+ $block->setBlocker( $blocker );
+ $block->insert();
+ }
+
+ // Should find the block with the narrowest range
+ $blockTarget = DatabaseBlock::newFromTarget( $this->getTestUser()->getUser(), $ip )->getTarget();
+ $this->assertSame(
+ $blockTarget instanceof User ? $blockTarget->getName() : $blockTarget,
+ $expectedTarget
+ );
+
+ foreach ( $targets as $target ) {
+ $block = DatabaseBlock::newFromTarget( $target );
+ $block->delete();
+ }
+ }
+
+ function provideNewFromTargetRangeBlocks() {
+ return [
+ 'Blocks to IPv4 ranges' => [
+ [ '0.0.0.0/20', '0.0.0.0/30', '0.0.0.0/25' ],
+ '0.0.0.0',
+ '0.0.0.0/30'
+ ],
+ 'Blocks to IPv6 ranges' => [
+ [ '0:0:0:0:0:0:0:0/20', '0:0:0:0:0:0:0:0/30', '0:0:0:0:0:0:0:0/25' ],
+ '0:0:0:0:0:0:0:0',
+ '0:0:0:0:0:0:0:0/30'
+ ],
+ 'Blocks to wide IPv4 range and IP' => [
+ [ '0.0.0.0/16', '0.0.0.0' ],
+ '0.0.0.0',
+ '0.0.0.0'
+ ],
+ 'Blocks to narrow IPv4 range and IP' => [
+ [ '0.0.0.0/31', '0.0.0.0' ],
+ '0.0.0.0',
+ '0.0.0.0'
+ ],
+ 'Blocks to wide IPv6 range and IP' => [
+ [ '0:0:0:0:0:0:0:0/19', '0:0:0:0:0:0:0:0' ],
+ '0:0:0:0:0:0:0:0',
+ '0:0:0:0:0:0:0:0'
+ ],
+ 'Blocks to narrow IPv6 range and IP' => [
+ [ '0:0:0:0:0:0:0:0/127', '0:0:0:0:0:0:0:0' ],
+ '0:0:0:0:0:0:0:0',
+ '0:0:0:0:0:0:0:0'
+ ],
+ 'Blocks to wide IPv6 range and IP, large numbers' => [
+ [ '2000:DEAD:BEEF:A:0:0:0:0/19', '2000:DEAD:BEEF:A:0:0:0:0' ],
+ '2000:DEAD:BEEF:A:0:0:0:0',
+ '2000:DEAD:BEEF:A:0:0:0:0'
+ ],
+ 'Blocks to narrow IPv6 range and IP, large numbers' => [
+ [ '2000:DEAD:BEEF:A:0:0:0:0/127', '2000:DEAD:BEEF:A:0:0:0:0' ],
+ '2000:DEAD:BEEF:A:0:0:0:0',
+ '2000:DEAD:BEEF:A:0:0:0:0'
+ ],
+ ];
+ }
+
+ /**
+ * @covers ::appliesToRight
+ */
+ public function testBlockedUserCanNotCreateAccount() {
+ $username = 'BlockedUserToCreateAccountWith';
+ $u = User::newFromName( $username );
+ $u->addToDatabase();
+ $userId = $u->getId();
+ $this->assertNotEquals( 0, $userId, 'sanity' );
+ TestUser::setPasswordForUser( $u, 'NotRandomPass' );
+ unset( $u );
+
+ // Sanity check
+ $this->assertNull(
+ DatabaseBlock::newFromTarget( $username ),
+ "$username should not be blocked"
+ );
+
+ // Reload user
+ $u = User::newFromName( $username );
+ $this->assertFalse(
+ $u->isBlockedFromCreateAccount(),
+ "Our sandbox user should be able to create account before being blocked"
+ );
+
+ // Foreign perspective (blockee not on current wiki)...
+ $blockOptions = [
+ 'address' => $username,
+ 'user' => $userId,
+ 'reason' => 'crosswiki block...',
+ 'timestamp' => wfTimestampNow(),
+ 'expiry' => $this->db->getInfinity(),
+ 'createAccount' => true,
+ 'enableAutoblock' => true,
+ 'hideName' => true,
+ 'blockEmail' => true,
+ 'byText' => 'm>MetaWikiUser',
+ ];
+ $block = new DatabaseBlock( $blockOptions );
+ $block->insert();
+
+ // Reload block from DB
+ $userBlock = DatabaseBlock::newFromTarget( $username );
+ $this->assertTrue(
+ (bool)$block->appliesToRight( 'createaccount' ),
+ "Block object in DB should block right 'createaccount'"
+ );
+
+ $this->assertInstanceOf(
+ DatabaseBlock::class,
+ $userBlock,
+ "'$username' block block object should be existent"
+ );
+
+ // Reload user
+ $u = User::newFromName( $username );
+ $this->assertTrue(
+ (bool)$u->isBlockedFromCreateAccount(),
+ "Our sandbox user '$username' should NOT be able to create account"
+ );
+ }
+
+ /**
+ * @covers ::insert
+ */
+ public function testCrappyCrossWikiBlocks() {
+ // Delete the last round's block if it's still there
+ $oldBlock = DatabaseBlock::newFromTarget( 'UserOnForeignWiki' );
+ if ( $oldBlock ) {
+ // An old block will prevent our new one from saving.
+ $oldBlock->delete();
+ }
+
+ // Local perspective (blockee on current wiki)...
+ $user = User::newFromName( 'UserOnForeignWiki' );
+ $user->addToDatabase();
+ $userId = $user->getId();
+ $this->assertNotEquals( 0, $userId, 'sanity' );
+
+ // Foreign perspective (blockee not on current wiki)...
+ $blockOptions = [
+ 'address' => 'UserOnForeignWiki',
+ 'user' => $user->getId(),
+ 'reason' => 'crosswiki block...',
+ 'timestamp' => wfTimestampNow(),
+ 'expiry' => $this->db->getInfinity(),
+ 'createAccount' => true,
+ 'enableAutoblock' => true,
+ 'hideName' => true,
+ 'blockEmail' => true,
+ 'byText' => 'Meta>MetaWikiUser',
+ ];
+ $block = new DatabaseBlock( $blockOptions );
+
+ $res = $block->insert( $this->db );
+ $this->assertTrue( (bool)$res['id'], 'Block succeeded' );
+
+ $user = null; // clear
+
+ $block = DatabaseBlock::newFromID( $res['id'] );
+ $this->assertEquals(
+ 'UserOnForeignWiki',
+ $block->getTarget()->getName(),
+ 'Correct blockee name'
+ );
+ $this->assertEquals( $userId, $block->getTarget()->getId(), 'Correct blockee id' );
+ $this->assertEquals( 'Meta>MetaWikiUser', $block->getBlocker()->getName(),
+ 'Correct blocker name' );
+ $this->assertEquals( 'Meta>MetaWikiUser', $block->getByName(), 'Correct blocker name' );
+ $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' );
+ }
+
+ protected function addXffBlocks() {
+ static $inited = false;
+
+ if ( $inited ) {
+ return;
+ }
+
+ $inited = true;
+
+ $blockList = [
+ [ 'target' => '70.2.0.0/16',
+ 'type' => DatabaseBlock::TYPE_RANGE,
+ 'desc' => 'Range Hardblock',
+ 'ACDisable' => false,
+ 'isHardblock' => true,
+ 'isAutoBlocking' => false,
+ ],
+ [ 'target' => '2001:4860:4001::/48',
+ 'type' => DatabaseBlock::TYPE_RANGE,
+ 'desc' => 'Range6 Hardblock',
+ 'ACDisable' => false,
+ 'isHardblock' => true,
+ 'isAutoBlocking' => false,
+ ],
+ [ 'target' => '60.2.0.0/16',
+ 'type' => DatabaseBlock::TYPE_RANGE,
+ 'desc' => 'Range Softblock with AC Disabled',
+ 'ACDisable' => true,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ],
+ [ 'target' => '50.2.0.0/16',
+ 'type' => DatabaseBlock::TYPE_RANGE,
+ 'desc' => 'Range Softblock',
+ 'ACDisable' => false,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ],
+ [ 'target' => '50.1.1.1',
+ 'type' => DatabaseBlock::TYPE_IP,
+ 'desc' => 'Exact Softblock',
+ 'ACDisable' => false,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ],
+ ];
+
+ $blocker = $this->getTestUser()->getUser();
+ foreach ( $blockList as $insBlock ) {
+ $target = $insBlock['target'];
+
+ if ( $insBlock['type'] === DatabaseBlock::TYPE_IP ) {
+ $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName();
+ } elseif ( $insBlock['type'] === DatabaseBlock::TYPE_RANGE ) {
+ $target = IP::sanitizeRange( $target );
+ }
+
+ $block = new DatabaseBlock();
+ $block->setTarget( $target );
+ $block->setBlocker( $blocker );
+ $block->setReason( $insBlock['desc'] );
+ $block->setExpiry( 'infinity' );
+ $block->isCreateAccountBlocked( $insBlock['ACDisable'] );
+ $block->isHardblock( $insBlock['isHardblock'] );
+ $block->isAutoblocking( $insBlock['isAutoBlocking'] );
+ $block->insert();
+ }
+ }
+
+ public static function providerXff() {
+ return [
+ [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ],
+ [ 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Softblock with AC Disabled'
+ ],
+ [ 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Exact Softblock'
+ ],
+ [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5',
+ 'count' => 3,
+ 'result' => 'Exact Softblock'
+ ],
+ [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ],
+ [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ],
+ [ 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Softblock with AC Disabled'
+ ],
+ [ 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Exact Softblock'
+ ],
+ [ 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5',
+ 'count' => 1,
+ 'result' => 'Range Softblock with AC Disabled'
+ ],
+ [ 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range6 Hardblock'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider providerXff
+ * @covers ::getBlocksForIPList
+ * @covers ::chooseBlock
+ */
+ public function testBlocksOnXff( $xff, $exCount, $exResult ) {
+ $user = $this->getUserForBlocking();
+ $this->addBlockForUser( $user );
+
+ $list = array_map( 'trim', explode( ',', $xff ) );
+ $xffblocks = DatabaseBlock::getBlocksForIPList( $list, true );
+ $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff );
+ $block = DatabaseBlock::chooseBlock( $xffblocks, $list );
+ $this->assertEquals(
+ $exResult, $block->getReason(), 'Correct block type for XFF header ' . $xff
+ );
+ }
+
+ /**
+ * @covers ::newFromRow
+ */
+ public function testNewFromRow() {
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+
+ $block = new DatabaseBlock( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ ] );
+ $block->insert();
+
+ $blockQuery = DatabaseBlock::getQueryInfo();
+ $row = $this->db->select(
+ $blockQuery['tables'],
+ $blockQuery['fields'],
+ [
+ 'ipb_id' => $block->getId(),
+ ],
+ __METHOD__,
+ [],
+ $blockQuery['joins']
+ )->fetchObject();
+
+ $block = DatabaseBlock::newFromRow( $row );
+ $this->assertInstanceOf( DatabaseBlock::class, $block );
+ $this->assertEquals( $block->getBy(), $sysop->getId() );
+ $this->assertEquals( $block->getTarget()->getName(), $badActor->getName() );
+ $block->delete();
+ }
+
+ /**
+ * @covers ::equals
+ */
+ public function testEquals() {
+ $block = new DatabaseBlock();
+
+ $this->assertTrue( $block->equals( $block ) );
+
+ $partial = new DatabaseBlock( [
+ 'sitewide' => false,
+ ] );
+ $this->assertFalse( $block->equals( $partial ) );
+ }
+
+ /**
+ * @covers ::isSitewide
+ */
+ public function testIsSitewide() {
+ $block = new DatabaseBlock();
+ $this->assertTrue( $block->isSitewide() );
+
+ $block = new DatabaseBlock( [
+ 'sitewide' => true,
+ ] );
+ $this->assertTrue( $block->isSitewide() );
+
+ $block = new DatabaseBlock( [
+ 'sitewide' => false,
+ ] );
+ $this->assertFalse( $block->isSitewide() );
+
+ $block = new DatabaseBlock( [
+ 'sitewide' => false,
+ ] );
+ $block->isSitewide( true );
+ $this->assertTrue( $block->isSitewide() );
+ }
+
+ /**
+ * @covers ::getRestrictions
+ * @covers ::setRestrictions
+ */
+ public function testRestrictions() {
+ $block = new DatabaseBlock();
+ $restrictions = [
+ new PageRestriction( 0, 1 )
+ ];
+ $block->setRestrictions( $restrictions );
+
+ $this->assertSame( $restrictions, $block->getRestrictions() );
+ }
+
+ /**
+ * @covers ::getRestrictions
+ * @covers ::insert
+ */
+ public function testRestrictionsFromDatabase() {
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+
+ $block = new DatabaseBlock( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ ] );
+ $page = $this->getExistingTestPage( 'Foo' );
+ $restriction = new PageRestriction( 0, $page->getId() );
+ $block->setRestrictions( [ $restriction ] );
+ $block->insert();
+
+ // Refresh the block from the database.
+ $block = DatabaseBlock::newFromID( $block->getId() );
+ $restrictions = $block->getRestrictions();
+ $this->assertCount( 1, $restrictions );
+ $this->assertTrue( $restriction->equals( $restrictions[0] ) );
+ $block->delete();
+ }
+
+ /**
+ * @covers ::insert
+ */
+ public function testInsertExistingBlock() {
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+
+ $block = new DatabaseBlock( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ ] );
+ $page = $this->getExistingTestPage( 'Foo' );
+ $restriction = new PageRestriction( 0, $page->getId() );
+ $block->setRestrictions( [ $restriction ] );
+ $block->insert();
+
+ // Insert the block again, which should result in a failur
+ $result = $block->insert();
+
+ $this->assertFalse( $result );
+
+ // Ensure that there are no restrictions where the blockId is 0.
+ $count = $this->db->selectRowCount(
+ 'ipblocks_restrictions',
+ '*',
+ [ 'ir_ipb_id' => 0 ],
+ __METHOD__
+ );
+ $this->assertSame( 0, $count );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToTitle
+ */
+ public function testAppliesToTitleReturnsTrueOnSitewideBlock() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $user = $this->getTestUser()->getUser();
+ $block = new DatabaseBlock( [
+ 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
+ 'allowUsertalk' => true,
+ 'sitewide' => true
+ ] );
+
+ $block->setTarget( $user );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
+ $block->insert();
+
+ $title = $this->getExistingTestPage( 'Foo' )->getTitle();
+
+ $this->assertTrue( $block->appliesToTitle( $title ) );
+
+ // appliesToTitle() ignores allowUsertalk
+ $title = $user->getTalkPage();
+ $this->assertTrue( $block->appliesToTitle( $title ) );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToTitle
+ */
+ public function testAppliesToTitleOnPartialBlock() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $user = $this->getTestUser()->getUser();
+ $block = new DatabaseBlock( [
+ 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
+ 'allowUsertalk' => true,
+ 'sitewide' => false
+ ] );
+
+ $block->setTarget( $user );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
+ $block->insert();
+
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+ $pageJohn = $this->getExistingTestPage( 'User:John' );
+
+ $pageRestriction = new PageRestriction( $block->getId(), $pageFoo->getId() );
+ $namespaceRestriction = new NamespaceRestriction( $block->getId(), NS_USER );
+ $this->getBlockRestrictionStore()->insert( [ $pageRestriction, $namespaceRestriction ] );
+
+ $this->assertTrue( $block->appliesToTitle( $pageFoo->getTitle() ) );
+ $this->assertFalse( $block->appliesToTitle( $pageBar->getTitle() ) );
+ $this->assertTrue( $block->appliesToTitle( $pageJohn->getTitle() ) );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToNamespace
+ * @covers ::appliesToPage
+ */
+ public function testAppliesToReturnsTrueOnSitewideBlock() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $user = $this->getTestUser()->getUser();
+ $block = new DatabaseBlock( [
+ 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
+ 'allowUsertalk' => true,
+ 'sitewide' => true
+ ] );
+
+ $block->setTarget( $user );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
+ $block->insert();
+
+ $title = $this->getExistingTestPage()->getTitle();
+
+ $this->assertTrue( $block->appliesToPage( $title->getArticleID() ) );
+ $this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
+ $this->assertTrue( $block->appliesToNamespace( NS_USER_TALK ) );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToPage
+ */
+ public function testAppliesToPageOnPartialPageBlock() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $user = $this->getTestUser()->getUser();
+ $block = new DatabaseBlock( [
+ 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
+ 'allowUsertalk' => true,
+ 'sitewide' => false
+ ] );
+
+ $block->setTarget( $user );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
+ $block->insert();
+
+ $title = $this->getExistingTestPage()->getTitle();
+
+ $pageRestriction = new PageRestriction(
+ $block->getId(),
+ $title->getArticleID()
+ );
+ $this->getBlockRestrictionStore()->insert( [ $pageRestriction ] );
+
+ $this->assertTrue( $block->appliesToPage( $title->getArticleID() ) );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToNamespace
+ */
+ public function testAppliesToNamespaceOnPartialNamespaceBlock() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $user = $this->getTestUser()->getUser();
+ $block = new DatabaseBlock( [
+ 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
+ 'allowUsertalk' => true,
+ 'sitewide' => false
+ ] );
+
+ $block->setTarget( $user );
+ $block->setBlocker( $this->getTestSysop()->getUser() );
+ $block->insert();
+
+ $namespaceRestriction = new NamespaceRestriction( $block->getId(), NS_MAIN );
+ $this->getBlockRestrictionStore()->insert( [ $namespaceRestriction ] );
+
+ $this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
+ $this->assertFalse( $block->appliesToNamespace( NS_USER ) );
+
+ $block->delete();
+ }
+
+ /**
+ * @covers ::appliesToRight
+ */
+ public function testBlockAllowsPurge() {
+ $this->setMwGlobals( [
+ 'wgBlockDisablesLogin' => false,
+ ] );
+ $block = new DatabaseBlock();
+ $this->assertFalse( $block->appliesToRight( 'purge' ) );
+ }
+
+ /**
+ * Get an instance of BlockRestrictionStore
+ *
+ * @return BlockRestrictionStore
+ */
+ protected function getBlockRestrictionStore() : BlockRestrictionStore {
+ return MediaWikiServices::getInstance()->getBlockRestrictionStore();
+ }
+}
<?php
+
+use MediaWiki\Block\DatabaseBlock;
+
/**
* Factory for handling the special page list and generating SpecialPage objects.
*
$user = clone $this->getTestUser()->getUser();
$user->mBlockedby = $user->getName();
- $user->mBlock = new Block( [
+ $user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $user->getId(),
'reason' => 'sitewide block',
$user = clone $this->getTestUser()->getUser();
$user->mBlockedby = $user->getName();
- $user->mBlock = new Block( [
+ $user->mBlock = new DatabaseBlock( [
'address' => '127.0.8.1',
'by' => $user->getId(),
'reason' => 'partial block',
<?php
use MediaWiki\Block\BlockRestrictionStore;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use Wikimedia\TestingAccessWrapper;
$block = $this->insertBlock();
// Refresh the block from the database.
- $block = Block::newFromTarget( $block->getTarget() );
+ $block = DatabaseBlock::newFromTarget( $block->getTarget() );
$page = $this->newSpecialPage();
$pageSaturn = $this->getExistingTestPage( 'Saturn' );
$pageMars = $this->getExistingTestPage( 'Mars' );
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
$block->insert();
// Refresh the block from the database.
- $block = Block::newFromTarget( $block->getTarget() );
+ $block = DatabaseBlock::newFromTarget( $block->getTarget() );
$page = $this->newSpecialPage();
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
}
$context = RequestContext::getMain();
// Create a block that will be updated.
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertSame( '1', $block->isAutoblocking() );
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertCount( 2, $block->getRestrictions() );
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertFalse( $block->isSitewide() );
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertFalse( $block->isSitewide() );
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertFalse( $block->isSitewide() );
$this->assertTrue( $result );
- $block = Block::newFromTarget( $badActor );
+ $block = DatabaseBlock::newFromTarget( $badActor );
$this->assertSame( $reason, $block->getReason() );
$this->assertSame( $expiry, $block->getExpiry() );
$this->assertTrue( $block->isSitewide() );
$$var = $users[$$var];
}
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $blockedUser->getName(),
'user' => $blockedUser->getId(),
'by' => $blockPerformer->getId(),
$badActor = $this->getTestUser()->getUser();
$sysop = $this->getTestSysop()->getUser();
- $block = new \Block( [
+ $block = new DatabaseBlock( [
'address' => $badActor->getName(),
'user' => $badActor->getId(),
'by' => $sysop->getId(),
<?php
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\MediaWikiServices;
$target = '127.0.0.1';
// Test Partial Blocks Blocks.
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $target,
'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => 'Parce que',
<?php
+use MediaWiki\Block\DatabaseBlock;
+
/**
* @covers LocalIdLookup
* @group Database
$sysop = static::getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $this->localUsers[2]->getName(),
'by' => $sysop->getId(),
'reason' => __METHOD__,
] );
$block->insert();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'address' => $this->localUsers[3]->getName(),
'by' => $sysop->getId(),
'reason' => __METHOD__,
<?php
use MediaWiki\Auth\AuthManager;
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\SystemBlock;
/**
'enableEmail' => true,
'allowsAuthenticationDataChange' => true,
'canEditPrivate' => true,
- 'block' => new Block( [ 'createAccount' => true ] ),
+ 'block' => new DatabaseBlock( [ 'createAccount' => true ] ),
'globalBlock' => null,
'isAllowed' => false,
],
'enableEmail' => true,
'allowsAuthenticationDataChange' => true,
'canEditPrivate' => true,
- 'block' => new Block( [] ),
+ 'block' => new DatabaseBlock( [] ),
'globalBlock' => null,
'isAllowed' => true,
],
define( 'NS_UNITTEST', 5600 );
define( 'NS_UNITTEST_TALK', 5601 );
+use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\Restriction\PageRestriction;
use MediaWiki\Block\Restriction\NamespaceRestriction;
use MediaWiki\Block\SystemBlock;
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
$expiryFiveHours = wfTimestamp() + ( 5 * 60 * 60 );
- $block = new Block( [
+ $block = new DatabaseBlock( [
'enableAutoblock' => true,
'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
] );
// Confirm that the block has been applied as required.
$this->assertTrue( $user1->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user1->getBlock() );
- $this->assertEquals( Block::TYPE_USER, $block->getType() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
+ $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
$this->assertTrue( $block->isAutoblocking() );
$this->assertGreaterThanOrEqual( 1, $block->getId() );
$cookies = $request1->response()->getCookies();
$this->assertArrayHasKey( 'wmsitetitleBlockID', $cookies );
$this->assertEquals( $expiryFiveHours, $cookies['wmsitetitleBlockID']['expire'] );
- $cookieValue = Block::getIdFromCookieValue( $cookies['wmsitetitleBlockID']['value'] );
+ $cookieValue = DatabaseBlock::getIdFromCookieValue( $cookies['wmsitetitleBlockID']['value'] );
$this->assertEquals( $block->getId(), $cookieValue );
// 2. Create a new request, set the cookies, and see if the (anon) user is blocked.
$this->assertNotEquals( $user1->getToken(), $user2->getToken() );
$this->assertTrue( $user2->isAnon() );
$this->assertFalse( $user2->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user2->getBlock() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
// Non-strict type-check.
$this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
// Can't directly compare the objects because of member type differences.
$user3 = User::newFromSession( $request3 );
$user3->load();
$this->assertTrue( $user3->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user3->getBlock() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user3->getBlock() );
$this->assertEquals( true, $user3->getBlock()->isAutoblocking() ); // Non-strict type-check.
// Clean up.
$testUser = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $testUser );
- $block = new Block( [ 'enableAutoblock' => true ] );
+ $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $testUser );
$res = $block->insert();
// 2. Test that the cookie IS NOT present.
$this->assertTrue( $user->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user->getBlock() );
- $this->assertEquals( Block::TYPE_USER, $block->getType() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock() );
+ $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
$this->assertTrue( $block->isAutoblocking() );
$this->assertGreaterThanOrEqual( 1, $user->getBlockId() );
$this->assertGreaterThanOrEqual( $block->getId(), $user->getBlockId() );
$user1Tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1Tmp );
- $block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
+ $block = new DatabaseBlock( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1Tmp );
$res = $block->insert();
// 2. Test the cookie's expiry timestamp.
$this->assertTrue( $user1->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user1->getBlock() );
- $this->assertEquals( Block::TYPE_USER, $block->getType() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
+ $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
$this->assertTrue( $block->isAutoblocking() );
$this->assertGreaterThanOrEqual( 1, $user1->getBlockId() );
$cookies = $request1->response()->getCookies();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
- $block = new Block( [ 'enableAutoblock' => true ] );
+ $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
$res = $block->insert();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
- $block = new Block( [ 'enableAutoblock' => true ] );
+ $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
$res = $block->insert();
$user1 = User::newFromSession( $request1 );
$user1->mBlock = $block;
$user1->load();
- $this->assertInstanceOf( Block::class, $user1->getBlock() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
// 2. Create a new request, set the cookie to just the block ID, and the user should
// still get blocked when they log in again.
$this->assertNotEquals( $user1->getToken(), $user2->getToken() );
$this->assertTrue( $user2->isAnon() );
$this->assertFalse( $user2->isLoggedIn() );
- $this->assertInstanceOf( Block::class, $user2->getBlock() );
+ $this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
$this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
// Clean up.
// Block the user
$blocker = $this->getTestSysop()->getUser();
- $block = new Block( [
+ $block = new DatabaseBlock( [
'hideName' => true,
'allowUsertalk' => false,
'reason' => 'Because',
// Clear cache and confirm it loaded the block properly
$user->clearInstanceCache();
- $this->assertInstanceOf( Block::class, $user->getBlock( false ) );
+ $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock( false ) );
$this->assertSame( $blocker->getName(), $user->blockedBy() );
$this->assertSame( 'Because', $user->blockedFor() );
$this->assertTrue( (bool)$user->isHidden() );
* @param bool $expect Expected result from User::isBlockedFrom()
* @param array $options Additional test options:
* - 'blockAllowsUTEdit': (bool, default true) Value for $wgBlockAllowsUTEdit
- * - 'allowUsertalk': (bool, default false) Passed to Block::__construct()
+ * - 'allowUsertalk': (bool, default false) Passed to DatabaseBlock::__construct()
* - 'pageRestrictions': (array|null) If non-empty, page restriction titles for the block.
*/
public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
$restrictions[] = new NamespaceRestriction( 0, $ns );
}
- $block = new Block( [
+ $block = new DatabaseBlock( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
'allowUsertalk' => $options['allowUsertalk'] ?? false,
'sitewide' => !$restrictions,
] );
// setup block
- $block = new Block( [
+ $block = new DatabaseBlock( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
] );
$block->setTarget( '1.2.3.4' );
] );
// setup block
- $block = new Block( [
+ $block = new DatabaseBlock( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
] );
$block->setTarget( '1.2.3.4' );
] );
// setup block
- $block = new Block( [
+ $block = new DatabaseBlock( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
] );
$block->setTarget( '1.2.3.4' );