public static function getAutoloadNamespaces() {
return [
'MediaWiki\\Auth\\' => __DIR__ . '/auth/',
+ 'MediaWiki\\Block\\' => __DIR__ . '/block/',
'MediaWiki\\Edit\\' => __DIR__ . '/edit/',
'MediaWiki\\EditPage\\' => __DIR__ . '/editpage/',
'MediaWiki\\Linker\\' => __DIR__ . '/linker/',
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\Block\BlockRestriction;
+use MediaWiki\Block\Restriction\Restriction;
use MediaWiki\MediaWikiServices;
class Block {
/** @var string|null */
private $systemBlockType;
+ /** @var bool */
+ private $isSitewide;
+
+ /** @var Restriction[] */
+ private $restrictions;
+
# TYPE constants
const TYPE_USER = 1;
const TYPE_IP = 2;
'allowUsertalk' => false,
'byText' => '',
'systemBlock' => null,
+ 'sitewide' => true,
];
if ( func_num_args() > 1 || !is_array( $options ) ) {
$this->mHideName = (bool)$options['hideName'];
$this->isHardblock( !$options['anonOnly'] );
$this->isAutoblocking( (bool)$options['enableAutoblock'] );
+ $this->isSitewide( (bool)$options['sitewide'] );
# Prevention measures
$this->prevents( 'sendemail', (bool)$options['blockEmail'] );
'ipb_block_email',
'ipb_allow_usertalk',
'ipb_parent_block_id',
+ 'ipb_sitewide',
] + CommentStore::getStore()->getFields( 'ipb_reason' );
}
'ipb_block_email',
'ipb_allow_usertalk',
'ipb_parent_block_id',
+ 'ipb_sitewide',
] + $commentQuery['fields'] + $actorQuery['fields'],
'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
&& $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' )
&& $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' )
&& $this->mReason == $block->mReason
+ && $this->isSitewide() == $block->isSitewide()
+ // Block::getRestrictions() may perform a database query, so keep it at
+ // the end.
+ && BlockRestriction::equals( $this->getRestrictions(), $block->getRestrictions() )
);
}
$this->isHardblock( !$row->ipb_anon_only );
$this->isAutoblocking( $row->ipb_enable_autoblock );
+ $this->isSitewide( (bool)$row->ipb_sitewide );
$this->prevents( 'createaccount', $row->ipb_create_account );
$this->prevents( 'sendemail', $row->ipb_block_email );
}
$dbw = wfGetDB( DB_MASTER );
+
+ BlockRestriction::deleteByParentBlockId( $this->getId() );
$dbw->delete( 'ipblocks', [ 'ipb_parent_block_id' => $this->getId() ], __METHOD__ );
+
+ BlockRestriction::deleteByBlockId( $this->getId() );
$dbw->delete( 'ipblocks', [ 'ipb_id' => $this->getId() ], __METHOD__ );
return $dbw->affectedRows() > 0;
$dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
$affected = $dbw->affectedRows();
- $this->mId = $dbw->insertId();
+ if ( $affected ) {
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ BlockRestriction::insert( $this->restrictions );
+ }
+ }
# Don't collide with expired blocks.
# Do this after trying to insert to avoid locking.
);
if ( $ids ) {
$dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
+ BlockRestriction::deleteByBlockId( $ids );
$dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
$affected = $dbw->affectedRows();
- $this->mId = $dbw->insertId();
+ $this->setId( $dbw->insertId() );
+ if ( $this->restrictions ) {
+ BlockRestriction::insert( $this->restrictions );
+ }
}
}
$dbw->startAtomic( __METHOD__ );
- $dbw->update(
+ $result = $dbw->update(
'ipblocks',
$this->getDatabaseArray( $dbw ),
[ 'ipb_id' => $this->getId() ],
__METHOD__
);
- $affected = $dbw->affectedRows();
+ // 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 = BlockRestriction::deleteByBlockId( $this->getId() );
+ } else {
+ $success = BlockRestriction::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)
[ 'ipb_parent_block_id' => $this->getId() ],
__METHOD__
);
+
+ // Only update the restrictions if they have been modified.
+ if ( $this->restrictions !== null ) {
+ BlockRestriction::updateByParentBlockId( $this->getId(), $this->restrictions );
+ }
} else {
// autoblock no longer required, delete corresponding autoblock(s)
+ BlockRestriction::deleteByParentBlockId( $this->getId() );
$dbw->delete(
'ipblocks',
[ 'ipb_parent_block_id' => $this->getId() ],
$dbw->endAtomic( __METHOD__ );
- if ( $affected ) {
+ if ( $result ) {
$auto_ipd_ids = $this->doRetroactiveAutoblock();
return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ];
}
- return false;
+ return $result;
}
/**
'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
'ipb_block_email' => $this->prevents( 'sendemail' ),
'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ),
- 'ipb_parent_block_id' => $this->mParentBlockId
+ 'ipb_parent_block_id' => $this->mParentBlockId,
+ 'ipb_sitewide' => $this->isSitewide(),
] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+ ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
$autoblock->mHideName = $this->mHideName;
$autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) );
$autoblock->mParentBlockId = $this->mId;
+ $autoblock->isSitewide( $this->isSitewide() );
+ $autoblock->setRestrictions( $this->getRestrictions() );
if ( $this->mExpiry == 'infinity' ) {
# Original block was indefinite, start an autoblock now
return $this->mId;
}
+ /**
+ * Set the block ID
+ *
+ * @param int $blockId
+ * @return int
+ */
+ private function setId( $blockId ) {
+ $this->mId = (int)$blockId;
+
+ if ( is_array( $this->restrictions ) ) {
+ $this->restrictions = BlockRestriction::setBlockId( $blockId, $this->restrictions );
+ }
+
+ return $this;
+ }
+
/**
* Get the system block type, if any
* @since 1.29
: false;
}
+ /**
+ * Indicates that the block is a sitewide block. This means the user is
+ * prohibited from editing any page on the site (other than their own talk
+ * page).
+ *
+ * @param null|bool $x
+ * @return bool
+ */
+ public function isSitewide( $x = null ) {
+ return wfSetVar( $this->isSitewide, $x );
+ }
+
/**
* Get/set whether the Block prevents a given action
*
$fname
);
if ( $ids ) {
+ BlockRestriction::deleteByBlockId( $ids );
$dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
}
}
$lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),
];
}
+
+ /**
+ * Get Restrictions.
+ *
+ * Getting the restrictions will perform a database query if the restrictions
+ * are not already loaded.
+ *
+ * @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 = BlockRestriction::loadByBlockId( $this->mId );
+ }
+
+ return $this->restrictions;
+ }
+
+ /**
+ * Set Restrictions.
+ *
+ * @param Restriction[] $restrictions
+ *
+ * @return self
+ */
+ public function setRestrictions( array $restrictions ) {
+ $this->restrictions = array_filter( $restrictions, function ( $restriction ) {
+ return $restriction instanceof Restriction;
+ } );
+
+ return $this;
+ }
}
*/
$wgTagStatisticsNewTable = false;
+/**
+ * Flag to enable Partial Blocks. This allows an admin to prevent a user from editing specific pages
+ * or namespaces.
+ *
+ * @since 1.32
+ * @deprecated 1.32
+ * @var bool
+ */
+$wgEnablePartialBlocks = false;
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
--- /dev/null
+<?php
+/**
+ * Block restriction interface.
+ *
+ * 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 MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\Restriction\Restriction;
+use Wikimedia\Rdbms\IResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+class BlockRestriction {
+
+ /**
+ * Retrieves the restrictions from the database by block id.
+ *
+ * @param int|array $blockId
+ * @param IDatabase|null $db
+ * @param array $options Options to pass to the select query.
+ * @return Restriction[]
+ */
+ public static function loadByBlockId( $blockId, IDatabase $db = null ) {
+ if ( is_null( $blockId ) || $blockId === [] ) {
+ return [];
+ }
+
+ $db = $db ?: wfGetDb( DB_REPLICA );
+
+ $result = $db->select(
+ [ 'ipblocks_restrictions', 'page' ],
+ [ 'ir_ipb_id', 'ir_type', 'ir_value', 'page_namespace', 'page_title' ],
+ [ 'ir_ipb_id' => $blockId ],
+ __METHOD__,
+ [],
+ [ 'page' => [ 'LEFT JOIN', [ 'ir_type' => PageRestriction::TYPE_ID, 'ir_value=page_id' ] ] ]
+ );
+
+ return self::resultToRestrictions( $result );
+ }
+
+ /**
+ * Inserts the restrictions into the database.
+ *
+ * @param Restriction[] $restrictions
+ * @return bool
+ */
+ public static function insert( array $restrictions ) {
+ if ( empty( $restrictions ) ) {
+ return false;
+ }
+
+ $rows = [];
+ foreach ( $restrictions as $restriction ) {
+ if ( !$restriction instanceof Restriction ) {
+ continue;
+ }
+ $rows[] = $restriction->toRow();
+ }
+
+ if ( empty( $rows ) ) {
+ return false;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ return $dbw->insert(
+ 'ipblocks_restrictions',
+ $rows,
+ __METHOD__,
+ [ 'IGNORE' ]
+ );
+ }
+
+ /**
+ * Updates the list of restrictions. This method does not allow removing all
+ * of the restrictions. To do that, use ::deleteByBlockId().
+ *
+ * @param Restriction[] $restrictions
+ * @return bool
+ */
+ public static function update( array $restrictions ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->startAtomic( __METHOD__ );
+
+ // Organize the restrictions by blockid.
+ $restrictionList = self::restrictionsByBlockId( $restrictions );
+
+ // Load the existing restrictions and organize by block id. Any block ids
+ // that were passed into this function will be used to load all of the
+ // existing restrictions. This list might be the same, or may be completely
+ // different.
+ $existingList = [];
+ $blockIds = array_keys( $restrictionList );
+ if ( !empty( $blockIds ) ) {
+ $result = $dbw->select(
+ [ 'ipblocks_restrictions', 'page' ],
+ [ 'ir_ipb_id', 'ir_type', 'ir_value' ],
+ [ 'ir_ipb_id' => $blockIds ],
+ __METHOD__,
+ [ 'FOR UPDATE' ]
+ );
+
+ $existingList = self::restrictionsByBlockId(
+ self::resultToRestrictions( $result )
+ );
+ }
+
+ $result = true;
+ // Perform the actions on a per block-id basis.
+ foreach ( $restrictionList as $blockId => $blockRestrictions ) {
+ // Insert all of the restrictions first, ignoring ones that already exist.
+ $success = self::insert( $blockRestrictions );
+
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $success && $result;
+
+ $restrictionsToRemove = self::restrictionsToRemove(
+ $existingList[$blockId] ?? [],
+ $restrictions
+ );
+
+ // Nothing to remove.
+ if ( empty( $restrictionsToRemove ) ) {
+ continue;
+ }
+
+ $success = self::delete( $restrictionsToRemove );
+
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $success && $result;
+ }
+
+ $dbw->endAtomic( __METHOD__ );
+
+ return $result;
+ }
+
+ /**
+ * Updates the list of restrictions by parent id.
+ *
+ * @param int $parentBlockId
+ * @param Restriction[] $restrictions
+ * @return bool
+ */
+ public static function updateByParentBlockId( $parentBlockId, array $restrictions ) {
+ // If removing all of the restrictions, then just delete them all.
+ if ( empty( $restrictions ) ) {
+ return self::deleteByParentBlockId( $parentBlockId );
+ }
+
+ $parentBlockId = (int)$parentBlockId;
+
+ $db = wfGetDb( DB_MASTER );
+
+ $db->startAtomic( __METHOD__ );
+
+ $blockIds = $db->selectFieldValues(
+ 'ipblocks',
+ 'ipb_id',
+ [ 'ipb_parent_block_id' => $parentBlockId ],
+ __METHOD__,
+ [ 'FOR UPDATE' ]
+ );
+
+ $result = true;
+ foreach ( $blockIds as $id ) {
+ $success = self::update( self::setBlockId( $id, $restrictions ) );
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $success && $result;
+ }
+
+ $db->endAtomic( __METHOD__ );
+
+ return $result;
+ }
+
+ /**
+ * Delete the restrictions.
+ *
+ * @param Restriction[]|null $restrictions
+ * @throws MWException
+ * @return bool
+ */
+ public static function delete( array $restrictions ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $result = true;
+ foreach ( $restrictions as $restriction ) {
+ if ( !$restriction instanceof Restriction ) {
+ continue;
+ }
+
+ $success = $dbw->delete(
+ 'ipblocks_restrictions',
+ // The restriction row is made up of a compound primary key. Therefore,
+ // the row and the delete conditions are the same.
+ $restriction->toRow(),
+ __METHOD__
+ );
+ // Update the result. The first false is the result, otherwise, true.
+ $result = $success && $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Delete the restrictions by Block ID.
+ *
+ * @param int|array $blockId
+ * @throws MWException
+ * @return bool
+ */
+ public static function deleteByBlockId( $blockId ) {
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->delete(
+ 'ipblocks_restrictions',
+ [ 'ir_ipb_id' => $blockId ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * Delete the restrictions by Parent Block ID.
+ *
+ * @param int|array $parentBlockId
+ * @throws MWException
+ * @return bool
+ */
+ public static function deleteByParentBlockId( $parentBlockId ) {
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->deleteJoin(
+ 'ipblocks_restrictions',
+ 'ipblocks',
+ 'ir_ipb_id',
+ 'ipb_id',
+ [ 'ipb_parent_block_id' => $parentBlockId ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * Checks if two arrays of Restrictions are effectively equal. This is a loose
+ * equality check as the restrictions do not have to contain the same block
+ * ids.
+ *
+ * @param Restriction[] $a
+ * @param Restriction[] $b
+ * @return bool
+ */
+ public static function equals( array $a, array $b ) {
+ $filter = function ( $restriction ) {
+ return $restriction instanceof Restriction;
+ };
+
+ // Ensure that every item in the array is a Restriction. This prevents a
+ // fatal error from calling Restriction::getHash if something in the array
+ // is not a restriction.
+ $a = array_filter( $a, $filter );
+ $b = array_filter( $b, $filter );
+
+ $aCount = count( $a );
+ $bCount = count( $b );
+
+ // If the count is different, then they are obviously a different set.
+ if ( $aCount !== $bCount ) {
+ return false;
+ }
+
+ // If both sets contain no items, then they are the same set.
+ if ( $aCount === 0 && $bCount === 0 ) {
+ return true;
+ }
+
+ $hasher = function ( $r ) {
+ return $r->getHash();
+ };
+
+ $aHashes = array_map( $hasher, $a );
+ $bHashes = array_map( $hasher, $b );
+
+ sort( $aHashes );
+ sort( $bHashes );
+
+ return $aHashes === $bHashes;
+ }
+
+ /**
+ * Set the blockId on a set of restrictions and return a new set.
+ *
+ * @param int $blockId
+ * @param Restriction[] $restrictions
+ * @return Restriction[]
+ */
+ public static function setBlockId( $blockId, array $restrictions ) {
+ $blockRestrictions = [];
+
+ foreach ( $restrictions as $restriction ) {
+ if ( !$restriction instanceof Restriction ) {
+ continue;
+ }
+
+ // Clone the restriction so any references to the current restriction are
+ // not suddenly changed to a different blockId.
+ $restriction = clone $restriction;
+ $restriction->setBlockId( $blockId );
+
+ $blockRestrictions[] = $restriction;
+ }
+
+ return $blockRestrictions;
+ }
+
+ /**
+ * Get the restrictions that should be removed, which are existing
+ * restrictions that are not in the new list of restrictions.
+ *
+ * @param Restriction[] $existing
+ * @param Restriction[] $new
+ * @return array
+ */
+ private static function restrictionsToRemove( array $existing, array $new ) {
+ return array_filter( $existing, function ( $e ) use ( $new ) {
+ foreach ( $new as $restriction ) {
+ if ( !$restriction instanceof Restriction ) {
+ continue;
+ }
+
+ if ( $restriction->equals( $e ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ } );
+ }
+
+ /**
+ * Converts an array of restrictions to an associative array of restrictions
+ * where the keys are the block ids.
+ *
+ * @param Restriction[] $restrictions
+ * @return array
+ */
+ private static function restrictionsByBlockId( array $restrictions ) {
+ $blockRestrictions = [];
+
+ foreach ( $restrictions as $restriction ) {
+ // Ensure that all of the items in the array are restrictions.
+ if ( !$restriction instanceof Restriction ) {
+ continue;
+ }
+
+ if ( !isset( $blockRestrictions[$restriction->getBlockId()] ) ) {
+ $blockRestrictions[$restriction->getBlockId()] = [];
+ }
+
+ $blockRestrictions[$restriction->getBlockId()][] = $restriction;
+ }
+
+ return $blockRestrictions;
+ }
+
+ /**
+ * Convert an Result Wrapper to an array of restrictions.
+ *
+ * @param IResultWrapper $result
+ * @return Restriction[]
+ */
+ private static function resultToRestrictions( IResultWrapper $result ) {
+ $restrictions = [];
+ foreach ( $result as $row ) {
+ $restriction = self::rowToRestriction( $row );
+
+ if ( !$restriction ) {
+ continue;
+ }
+
+ $restrictions[] = $restriction;
+ }
+
+ return $restrictions;
+ }
+
+ /**
+ * Convert a result row from the database into a restriction object.
+ *
+ * @param \stdClass $row
+ * @return Restriction|null
+ */
+ private static function rowToRestriction( \stdClass $row ) {
+ switch ( $row->ir_type ) {
+ case PageRestriction::TYPE_ID:
+ return PageRestriction::newFromRow( $row );
+ default:
+ return null;
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * Abstract block restriction.
+ *
+ * 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\Restriction;
+
+abstract class AbstractRestriction implements Restriction {
+
+ /**
+ * @var int
+ */
+ protected $blockId;
+
+ /**
+ * @var int
+ */
+ protected $value;
+
+ /**
+ * Create Restriction.
+ *
+ * @param int $blockId
+ * @param int $value
+ */
+ public function __construct( $blockId, $value ) {
+ $this->blockId = (int)$blockId;
+ $this->value = (int)$value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBlockId() {
+ return $this->blockId;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setBlockId( $blockId ) {
+ $this->blockId = (int)$blockId;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function newFromRow( \stdClass $row ) {
+ return new static( $row->ir_ipb_id, $row->ir_value );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function toRow() {
+ return [
+ 'ir_ipb_id' => $this->getBlockId(),
+ 'ir_type' => $this->getTypeId(),
+ 'ir_value' => $this->getValue(),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function equals( Restriction $other ) {
+ return $this->getHash() === $other->getHash();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHash() {
+ return $this->getType() . '-' . $this->getValue();
+ }
+}
--- /dev/null
+<?php
+/**
+ * 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
+ * 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\Restriction;
+
+class PageRestriction extends AbstractRestriction {
+
+ const TYPE = 'page';
+ const TYPE_ID = 1;
+
+ /**
+ * @var \Title
+ */
+ protected $title;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function matches( \Title $title ) {
+ return $title->equals( $this->getTitle() );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getType() {
+ return self::TYPE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypeId() {
+ return self::TYPE_ID;
+ }
+
+ /**
+ * Set the title.
+ *
+ * @param \Title $title
+ * @return self
+ */
+ public function setTitle( \Title $title ) {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get Title.
+ *
+ * @return \Title|null
+ */
+ public function getTitle() {
+ if ( !$this->title ) {
+ $this->title = \Title::newFromID( $this->value );
+ }
+
+ return $this->title;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function newFromRow( \stdClass $row ) {
+ $restriction = parent::newFromRow( $row );
+
+ // If the page_namespace and the page_title were provided, add the title to
+ // the restriction.
+ if ( isset( $row->page_namespace ) && isset( $row->page_title ) ) {
+ // Clone the row so it is not mutated.
+ $row = clone $row;
+ $row->page_id = $row->ir_value;
+ $title = \Title::newFromRow( $row );
+ $restriction->setTitle( $title );
+ }
+
+ return $restriction;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Block restriction interface.
+ *
+ * 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\Restriction;
+
+interface Restriction {
+
+ /**
+ * Gets the id of the block.
+ *
+ * @return int
+ */
+ public function getBlockId();
+
+ /**
+ * Sets the id of the block.
+ *
+ * @param int $blockId
+ * @return self
+ */
+ public function setBlockId( $blockId );
+
+ /**
+ * Gets the value of the restriction.
+ *
+ * @return int
+ */
+ public function getValue();
+
+ /**
+ * Gets the type of restriction
+ *
+ * @return string
+ */
+ public function getType();
+
+ /**
+ * Gets the id of the type of restriction. This id is used in the database.
+ *
+ * @return string
+ */
+ public function getTypeId();
+
+ /**
+ * Creates a new Restriction from a database row.
+ *
+ * @return self
+ */
+ public static function newFromRow( \stdClass $row );
+
+ /**
+ * Convert a restriction object into a row array for insertion.
+ *
+ * @return array
+ */
+ public function toRow();
+
+ /**
+ * Determine if a restriction matches a given title.
+ *
+ * @param \Title $title
+ * @return bool
+ */
+ public function matches( \Title $title );
+
+ /**
+ * Determine if a restriction equals another restriction.
+ *
+ * @param Restriction $other
+ * @return bool
+ */
+ public function equals( Restriction $other );
+
+ /**
+ * Create a unique hash of the block restriction based on the type and value.
+ *
+ * @return string
+ */
+ public function getHash();
+
+}
$currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
$currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
$currentBlock->mReason = $block->mReason;
+ $currentBlock->isSitewide( $block->isSitewide() );
$status = $currentBlock->update();
'MediaWiki\\Auth\\AuthenticationRequestTestCase' =>
"$testDir/phpunit/includes/auth/AuthenticationRequestTestCase.php",
+ # tests/phpunit/includes/block
+ 'MediaWiki\\Tests\\Block\\Restriction\\RestrictionTestCase' => "$testDir/phpunit/includes/block/Restriction/RestrictionTestCase.php",
+
# tests/phpunit/includes/changes
'TestRecentChangesHelper' => "$testDir/phpunit/includes/changes/TestRecentChangesHelper.php",
<?php
+use MediaWiki\Block\Restriction\PageRestriction;
+
/**
* @group Database
* @group Blocking
}
}
+ /**
+ * @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();
+ }
+
}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Block;
+
+use MediaWiki\Block\BlockRestriction;
+use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\Block\Restriction\Restriction;
+
+/**
+ * @group Database
+ * @group Blocking
+ * @coversDefaultClass \MediaWiki\Block\BlockRestriction
+ */
+class BlockRestrictionTest extends \MediaWikiLangTestCase {
+
+ public function tearDown() {
+ parent::tearDown();
+ $this->resetTables();
+ }
+
+ /**
+ * @covers ::loadByBlockId
+ * @covers ::resultToRestrictions
+ * @covers ::rowToRestriction
+ */
+ public function testLoadMultipleRestrictions() {
+ $block = $this->insertBlock();
+
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $pageFoo->getId() ),
+ new PageRestriction( $block->getId(), $pageBar->getId() ),
+ ] );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+
+ $this->assertCount( 2, $restrictions );
+ }
+
+ /**
+ * @covers ::loadByBlockId
+ * @covers ::resultToRestrictions
+ * @covers ::rowToRestriction
+ */
+ public function testWithNoRestrictions() {
+ $block = $this->insertBlock();
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertEmpty( $restrictions );
+ }
+
+ /**
+ * @covers ::loadByBlockId
+ * @covers ::resultToRestrictions
+ * @covers ::rowToRestriction
+ */
+ public function testWithEmptyParam() {
+ $restrictions = BlockRestriction::loadByBlockId( [] );
+
+ $this->assertEmpty( $restrictions );
+ }
+
+ /**
+ * @covers ::loadByBlockId
+ * @covers ::resultToRestrictions
+ * @covers ::rowToRestriction
+ */
+ public function testIgnoreNotSupportedTypes() {
+ $block = $this->insertBlock();
+
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+
+ // valid type
+ $this->insertRestriction( $block->getId(), PageRestriction::TYPE_ID, $pageFoo->getId() );
+
+ // invalid type
+ $this->insertRestriction( $block->getId(), 9, $pageBar->getId() );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+ }
+
+ /**
+ * @covers ::loadByBlockId
+ * @covers ::resultToRestrictions
+ * @covers ::rowToRestriction
+ */
+ public function testMappingRestrictionObject() {
+ $block = $this->insertBlock();
+ $title = 'Lady Macbeth';
+ $page = $this->getExistingTestPage( $title );
+
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+
+ list( $pageRestriction ) = $restrictions;
+ $this->assertInstanceOf( PageRestriction::class, $pageRestriction );
+ $this->assertEquals( $block->getId(), $pageRestriction->getBlockId() );
+ $this->assertEquals( $page->getId(), $pageRestriction->getValue() );
+ $this->assertEquals( $pageRestriction->getType(), PageRestriction::TYPE );
+ $this->assertEquals( $pageRestriction->getTitle()->getText(), $title );
+ }
+
+ /**
+ * @covers ::insert
+ */
+ public function testInsert() {
+ $block = $this->insertBlock();
+
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+
+ $restrictions = [
+ new \stdClass(),
+ new PageRestriction( $block->getId(), $pageFoo->getId() ),
+ new PageRestriction( $block->getId(), $pageBar->getId() ),
+ ];
+
+ $result = BlockRestriction::insert( $restrictions );
+ $this->assertTrue( $result );
+
+ $restrictions = [
+ new \stdClass(),
+ ];
+
+ $result = BlockRestriction::insert( $restrictions );
+ $this->assertFalse( $result );
+
+ $result = BlockRestriction::insert( [] );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers ::insert
+ */
+ public function testInsertTypes() {
+ $block = $this->insertBlock();
+
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+
+ $namespace = $this->createMock( Restriction::class );
+ $namespace->method( 'toRow' )
+ ->willReturn( [
+ 'ir_ipb_id' => $block->getId(),
+ 'ir_type' => 2,
+ 'ir_value' => 0,
+ ] );
+
+ $invalid = $this->createMock( Restriction::class );
+ $invalid->method( 'toRow' )
+ ->willReturn( [
+ 'ir_ipb_id' => $block->getId(),
+ 'ir_type' => 9,
+ 'ir_value' => 42,
+ ] );
+
+ $restrictions = [
+ new \stdClass(),
+ new PageRestriction( $block->getId(), $pageFoo->getId() ),
+ new PageRestriction( $block->getId(), $pageBar->getId() ),
+ $namespace,
+ $invalid,
+ ];
+
+ $result = BlockRestriction::insert( $restrictions );
+ $this->assertTrue( $result );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 2, $restrictions );
+ }
+
+ /**
+ * @covers ::update
+ * @covers ::restrictionsByBlockId
+ * @covers ::restrictionsToRemove
+ */
+ public function testUpdateInsert() {
+ $block = $this->insertBlock();
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $pageFoo->getId() ),
+ ] );
+
+ BlockRestriction::update( [
+ new \stdClass(),
+ new PageRestriction( $block->getId(), $pageBar->getId() ),
+ ] );
+
+ $db = wfGetDb( DB_REPLICA );
+ $result = $db->select(
+ [ 'ipblocks_restrictions' ],
+ [ '*' ],
+ [ 'ir_ipb_id' => $block->getId() ]
+ );
+
+ $this->assertEquals( 1, $result->numRows() );
+ $row = $result->fetchObject();
+ $this->assertEquals( $block->getId(), $row->ir_ipb_id );
+ $this->assertEquals( $pageBar->getId(), $row->ir_value );
+ }
+
+ /**
+ * @covers ::update
+ * @covers ::restrictionsByBlockId
+ * @covers ::restrictionsToRemove
+ */
+ public function testUpdateChange() {
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+
+ BlockRestriction::update( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ $db = wfGetDb( DB_REPLICA );
+ $result = $db->select(
+ [ 'ipblocks_restrictions' ],
+ [ '*' ],
+ [ 'ir_ipb_id' => $block->getId() ]
+ );
+
+ $this->assertEquals( 1, $result->numRows() );
+ $row = $result->fetchObject();
+ $this->assertEquals( $block->getId(), $row->ir_ipb_id );
+ $this->assertEquals( $page->getId(), $row->ir_value );
+ }
+
+ /**
+ * @covers ::update
+ * @covers ::restrictionsByBlockId
+ * @covers ::restrictionsToRemove
+ */
+ public function testUpdateNoRestrictions() {
+ $block = $this->insertBlock();
+
+ BlockRestriction::update( [] );
+
+ $db = wfGetDb( DB_REPLICA );
+ $result = $db->select(
+ [ 'ipblocks_restrictions' ],
+ [ '*' ],
+ [ 'ir_ipb_id' => $block->getId() ]
+ );
+
+ $this->assertEquals( 0, $result->numRows() );
+ }
+
+ /**
+ * @covers ::update
+ * @covers ::restrictionsByBlockId
+ * @covers ::restrictionsToRemove
+ */
+ public function testUpdateSame() {
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ BlockRestriction::update( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ $db = wfGetDb( DB_REPLICA );
+ $result = $db->select(
+ [ 'ipblocks_restrictions' ],
+ [ '*' ],
+ [ 'ir_ipb_id' => $block->getId() ]
+ );
+
+ $this->assertEquals( 1, $result->numRows() );
+ $row = $result->fetchObject();
+ $this->assertEquals( $block->getId(), $row->ir_ipb_id );
+ $this->assertEquals( $page->getId(), $row->ir_value );
+ }
+
+ /**
+ * @covers ::updateByParentBlockId
+ */
+ public function testDeleteAllUpdateByParentBlockId() {
+ // Create a block and an autoblock (a child block)
+ $block = $this->insertBlock();
+ $pageFoo = $this->getExistingTestPage( 'Foo' );
+ $pageBar = $this->getExistingTestPage( 'Bar' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $pageFoo->getId() ),
+ ] );
+ $autoblockId = $block->doAutoblock( '127.0.0.1' );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+ $this->assertEquals( $pageFoo->getId(), $restrictions[0]->getValue() );
+
+ // Ensure that the restrictions on the autoblock are the same as the block.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 1, $restrictions );
+ $this->assertEquals( $pageFoo->getId(), $restrictions[0]->getValue() );
+
+ // Update the restrictions on the autoblock (but leave the block unchanged)
+ BlockRestriction::updateByParentBlockId( $block->getId(), [
+ new PageRestriction( $block->getId(), $pageBar->getId() ),
+ ] );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+ $this->assertEquals( $pageFoo->getId(), $restrictions[0]->getValue() );
+
+ // Ensure that the restrictions on the autoblock have been updated.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 1, $restrictions );
+ $this->assertEquals( $pageBar->getId(), $restrictions[0]->getValue() );
+ }
+
+ /**
+ * @covers ::updateByParentBlockId
+ */
+ public function testUpdateByParentBlockId() {
+ // Create a block and an autoblock (a child block)
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+ $autoblockId = $block->doAutoblock( '127.0.0.1' );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ // Ensure that the restrictions on the autoblock have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 1, $restrictions );
+
+ // Remove the restrictions on the autoblock (but leave the block unchanged)
+ BlockRestriction::updateByParentBlockId( $block->getId(), [] );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ // Ensure that the restrictions on the autoblock have been updated.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 0, $restrictions );
+ }
+
+ /**
+ * @covers ::updateByParentBlockId
+ */
+ public function testNoAutoblocksUpdateByParentBlockId() {
+ // Create a block with no autoblock.
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ // Update the restrictions on any autoblocks (there are none).
+ BlockRestriction::updateByParentBlockId( $block->getId(), $restrictions );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+ }
+
+ /**
+ * @covers ::delete
+ */
+ public function testDelete() {
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ $result = BlockRestriction::delete( array_merge( $restrictions, [ new \stdClass() ] ) );
+ $this->assertTrue( $result );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 0, $restrictions );
+ }
+
+ /**
+ * @covers ::deleteByBlockId
+ */
+ public function testDeleteByBlockId() {
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ $result = BlockRestriction::deleteByBlockId( $block->getId() );
+ $this->assertNotFalse( $result );
+
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 0, $restrictions );
+ }
+
+ /**
+ * @covers ::deleteByParentBlockId
+ */
+ public function testDeleteByParentBlockId() {
+ // Create a block with no autoblock.
+ $block = $this->insertBlock();
+ $page = $this->getExistingTestPage( 'Foo' );
+ BlockRestriction::insert( [
+ new PageRestriction( $block->getId(), $page->getId() ),
+ ] );
+ $autoblockId = $block->doAutoblock( '127.0.0.1' );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ // Ensure that the restrictions on the autoblock are the same as the block.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 1, $restrictions );
+
+ // Remove all of the restrictions on the autoblock (but leave the block unchanged).
+ $result = BlockRestriction::deleteByParentBlockId( $block->getId() );
+ // NOTE: commented out until https://gerrit.wikimedia.org/r/c/mediawiki/core/+/469324 is merged
+ //$this->assertTrue( $result );
+
+ // Ensure that the restrictions on the block have not changed.
+ $restrictions = BlockRestriction::loadByBlockId( $block->getId() );
+ $this->assertCount( 1, $restrictions );
+
+ // Ensure that the restrictions on the autoblock have been removed.
+ $restrictions = BlockRestriction::loadByBlockId( $autoblockId );
+ $this->assertCount( 0, $restrictions );
+ }
+
+ /**
+ * @covers ::equals
+ * @dataProvider equalsDataProvider
+ *
+ * @param array $a
+ * @param array $b
+ * @param bool $expected
+ */
+ public function testEquals( array $a, array $b, $expected ) {
+ $this->assertSame( $expected, BlockRestriction::equals( $a, $b ) );
+ }
+
+ public function equalsDataProvider() {
+ return [
+ [
+ [
+ new \stdClass(),
+ new PageRestriction( 1, 1 ),
+ ],
+ [
+ new \stdClass(),
+ new PageRestriction( 1, 2 )
+ ],
+ false,
+ ],
+ [
+ [
+ new PageRestriction( 1, 1 ),
+ ],
+ [
+ new PageRestriction( 1, 1 ),
+ new PageRestriction( 1, 2 )
+ ],
+ false,
+ ],
+ [
+ [],
+ [],
+ true,
+ ],
+ [
+ [
+ new PageRestriction( 1, 1 ),
+ new PageRestriction( 1, 2 ),
+ new PageRestriction( 2, 3 ),
+ ],
+ [
+ new PageRestriction( 2, 3 ),
+ new PageRestriction( 1, 2 ),
+ new PageRestriction( 1, 1 ),
+ ],
+ true
+ ],
+ ];
+ }
+
+ /**
+ * @covers ::setBlockId
+ */
+ public function testSetBlockId() {
+ $restrictions = [
+ new \stdClass(),
+ new PageRestriction( 1, 1 ),
+ new PageRestriction( 1, 2 ),
+ ];
+
+ $result = BlockRestriction::setBlockId( 2, $restrictions );
+
+ $this->assertSame( 1, $restrictions[1]->getBlockId() );
+ $this->assertSame( 1, $restrictions[2]->getBlockId() );
+ $this->assertSame( 2, $result[0]->getBlockId() );
+ $this->assertSame( 2, $result[1]->getBlockId() );
+ }
+
+ protected function insertBlock() {
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+
+ $block = new \Block( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ 'sitewide' => 0,
+ 'enableAutoblock' => true,
+ ] );
+
+ $block->insert();
+
+ return $block;
+ }
+
+ protected function insertRestriction( $blockId, $type, $value ) {
+ $this->db->insert( 'ipblocks_restrictions', [
+ 'ir_ipb_id' => $blockId,
+ 'ir_type' => $type,
+ 'ir_value' => $value,
+ ] );
+ }
+
+ protected function resetTables() {
+ $this->db->delete( 'ipblocks', '*', __METHOD__ );
+ $this->db->delete( 'ipblocks_restrictions', '*', __METHOD__ );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Block\Restriction;
+
+use MediaWiki\Block\Restriction\PageRestriction;
+
+/**
+ * @group Database
+ * @group Blocking
+ * @covers \MediaWiki\Block\Restriction\AbstractRestriction
+ * @covers \MediaWiki\Block\Restriction\PageRestriction
+ */
+class PageRestrictionTest extends RestrictionTestCase {
+
+ public function testMatches() {
+ $class = $this->getClass();
+ $page = $this->getExistingTestPage( 'Saturn' );
+ $restriction = new $class( 1, $page->getId() );
+ $this->assertTrue( $restriction->matches( $page->getTitle() ) );
+
+ $page = $this->getExistingTestPage( 'Mars' );
+ $this->assertFalse( $restriction->matches( $page->getTitle() ) );
+ }
+
+ public function testGetType() {
+ $class = $this->getClass();
+ $restriction = new $class( 1, 2 );
+ $this->assertEquals( 'page', $restriction->getType() );
+ }
+
+ public function testGetTitle() {
+ $class = $this->getClass();
+ $restriction = new $class( 1, 2 );
+ $title = \Title::newFromText( 'Pluto' );
+ $title->mArticleID = 2;
+ $restriction->setTitle( $title );
+ $this->assertSame( $title, $restriction->getTitle() );
+
+ $restriction = new $class( 1, 1 );
+ $title = \Title::newFromId( 1 );
+ $this->assertEquals( $title->getArticleId(), $restriction->getTitle()->getArticleId() );
+ }
+
+ public function testNewFromRow() {
+ $class = $this->getClass();
+ $restriction = $class::newFromRow( (object)[
+ 'ir_ipb_id' => 1,
+ 'ir_value' => 2,
+ 'page_namespace' => 0,
+ 'page_title' => 'Saturn',
+ ] );
+
+ $this->assertSame( 1, $restriction->getBlockId() );
+ $this->assertSame( 2, $restriction->getValue() );
+ $this->assertSame( 'Saturn', $restriction->getTitle()->getText() );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getClass() {
+ return PageRestriction::class;
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Block\Restriction;
+
+/**
+ * @group Blocking
+ */
+abstract class RestrictionTestCase extends \MediaWikiTestCase {
+ public function testConstruct() {
+ $class = $this->getClass();
+ $restriction = new $class( 1, 2 );
+
+ $this->assertSame( $restriction->getBlockId(), 1 );
+ $this->assertSame( $restriction->getValue(), 2 );
+ }
+
+ public function testSetBlockId() {
+ $class = $this->getClass();
+ $restriction = new $class( 1, 2 );
+
+ $restriction->setBlockId( 10 );
+ $this->assertSame( $restriction->getBlockId(), 10 );
+ }
+
+ public function testEquals() {
+ $class = $this->getClass();
+
+ // Test two restrictions with the same data.
+ $restriction = new $class( 1, 2 );
+ $second = new $class( 1, 2 );
+ $this->assertTrue( $restriction->equals( $second ) );
+
+ // Test two restrictions that implement different classes.
+ $second = $this->createMock( $this->getClass() );
+ $this->assertFalse( $restriction->equals( $second ) );
+
+ // Not the same block id.
+ $second = new $class( 2, 2 );
+ $this->assertTrue( $restriction->equals( $second ) );
+
+ // Not the same value.
+ $second = new $class( 1, 3 );
+ $this->assertFalse( $restriction->equals( $second ) );
+ }
+
+ public function testNewFromRow() {
+ $class = $this->getClass();
+
+ $restriction = $class::newFromRow( (object)[
+ 'ir_ipb_id' => 1,
+ 'ir_value' => 2,
+ ] );
+
+ $this->assertSame( 1, $restriction->getBlockId() );
+ $this->assertSame( 2, $restriction->getValue() );
+ }
+
+ public function testToRow() {
+ $class = $this->getClass();
+
+ $restriction = new $class( 1, 2 );
+ $row = $restriction->toRow();
+
+ $this->assertSame( 1, $row['ir_ipb_id'] );
+ $this->assertSame( 2, $row['ir_value'] );
+ }
+
+ /**
+ * Get the class name of the class that is being tested.
+ *
+ * @return string
+ */
+ abstract protected function getClass();
+}