From a562611e5b1a998c09dee0cf3022a1ba3935397a Mon Sep 17 00:00:00 2001 From: Dayllan Maza Date: Thu, 16 Aug 2018 00:55:55 -0400 Subject: [PATCH] Add block restriction classes Partial blocks logic will be used in multiple places. This classes will group block restriction functionality to avoid code duplication Bug: T202036 Change-Id: I675316dddf272fd0d6172ecad3882160752bf780 --- includes/AutoLoader.php | 1 + includes/Block.php | 128 +++- includes/DefaultSettings.php | 10 + includes/block/BlockRestriction.php | 417 +++++++++++++ .../block/Restriction/AbstractRestriction.php | 102 ++++ .../block/Restriction/PageRestriction.php | 99 ++++ includes/block/Restriction/Restriction.php | 100 ++++ includes/specials/SpecialBlock.php | 1 + tests/common/TestsAutoLoader.php | 3 + tests/phpunit/includes/BlockTest.php | 150 +++++ .../includes/block/BlockRestrictionTest.php | 556 ++++++++++++++++++ .../block/Restriction/PageRestrictionTest.php | 64 ++ .../block/Restriction/RestrictionTestCase.php | 74 +++ 13 files changed, 1698 insertions(+), 7 deletions(-) create mode 100644 includes/block/BlockRestriction.php create mode 100644 includes/block/Restriction/AbstractRestriction.php create mode 100644 includes/block/Restriction/PageRestriction.php create mode 100644 includes/block/Restriction/Restriction.php create mode 100644 tests/phpunit/includes/block/BlockRestrictionTest.php create mode 100644 tests/phpunit/includes/block/Restriction/PageRestrictionTest.php create mode 100644 tests/phpunit/includes/block/Restriction/RestrictionTestCase.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index e4e59da983..677fd01b54 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -130,6 +130,7 @@ class AutoLoader { 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/', diff --git a/includes/Block.php b/includes/Block.php index bf8bad169f..d1de85b9cb 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -22,6 +22,8 @@ use Wikimedia\Rdbms\Database; use Wikimedia\Rdbms\IDatabase; +use MediaWiki\Block\BlockRestriction; +use MediaWiki\Block\Restriction\Restriction; use MediaWiki\MediaWikiServices; class Block { @@ -79,6 +81,12 @@ 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; @@ -129,6 +137,7 @@ class Block { 'allowUsertalk' => false, 'byText' => '', 'systemBlock' => null, + 'sitewide' => true, ]; if ( func_num_args() > 1 || !is_array( $options ) ) { @@ -165,6 +174,7 @@ class Block { $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'] ); @@ -236,6 +246,7 @@ class Block { 'ipb_block_email', 'ipb_allow_usertalk', 'ipb_parent_block_id', + 'ipb_sitewide', ] + CommentStore::getStore()->getFields( 'ipb_reason' ); } @@ -266,6 +277,7 @@ class Block { 'ipb_block_email', 'ipb_allow_usertalk', 'ipb_parent_block_id', + 'ipb_sitewide', ] + $commentQuery['fields'] + $actorQuery['fields'], 'joins' => $commentQuery['joins'] + $actorQuery['joins'], ]; @@ -292,6 +304,10 @@ class Block { && $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() ) ); } @@ -477,6 +493,7 @@ class Block { $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 ); @@ -510,7 +527,11 @@ class Block { } $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; @@ -546,7 +567,12 @@ class Block { $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. @@ -564,9 +590,13 @@ class Block { ); 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 ); + } } } @@ -598,14 +628,24 @@ class Block { $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) @@ -615,8 +655,14 @@ class Block { [ '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() ], @@ -626,12 +672,12 @@ class Block { $dbw->endAtomic( __METHOD__ ); - if ( $affected ) { + if ( $result ) { $auto_ipd_ids = $this->doRetroactiveAutoblock(); return [ 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ]; } - return false; + return $result; } /** @@ -662,7 +708,8 @@ class Block { '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() ); @@ -865,6 +912,8 @@ class Block { $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 @@ -1014,6 +1063,22 @@ class Block { 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 @@ -1061,6 +1126,18 @@ class Block { : 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 * @@ -1145,6 +1222,7 @@ class Block { $fname ); if ( $ids ) { + BlockRestriction::deleteByBlockId( $ids ); $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname ); } } @@ -1648,4 +1726,40 @@ class Block { $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; + } } diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 15d2627214..40aa22ff30 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -9027,6 +9027,16 @@ $wgChangeTagsSchemaMigrationStage = MIGRATION_WRITE_BOTH; */ $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 diff --git a/includes/block/BlockRestriction.php b/includes/block/BlockRestriction.php new file mode 100644 index 0000000000..3ce682bdd4 --- /dev/null +++ b/includes/block/BlockRestriction.php @@ -0,0 +1,417 @@ +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; + } + } +} diff --git a/includes/block/Restriction/AbstractRestriction.php b/includes/block/Restriction/AbstractRestriction.php new file mode 100644 index 0000000000..88a6a0ff2d --- /dev/null +++ b/includes/block/Restriction/AbstractRestriction.php @@ -0,0 +1,102 @@ +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(); + } +} diff --git a/includes/block/Restriction/PageRestriction.php b/includes/block/Restriction/PageRestriction.php new file mode 100644 index 0000000000..209b14893d --- /dev/null +++ b/includes/block/Restriction/PageRestriction.php @@ -0,0 +1,99 @@ +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; + } +} diff --git a/includes/block/Restriction/Restriction.php b/includes/block/Restriction/Restriction.php new file mode 100644 index 0000000000..f1cc1b0a22 --- /dev/null +++ b/includes/block/Restriction/Restriction.php @@ -0,0 +1,100 @@ +prevents( 'sendemail', $block->prevents( 'sendemail' ) ); $currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) ); $currentBlock->mReason = $block->mReason; + $currentBlock->isSitewide( $block->isSitewide() ); $status = $currentBlock->update(); diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index 41f3192396..f8431239b7 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -93,6 +93,9 @@ $wgAutoloadClasses += [ '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", diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index a921ee0e27..8760d5e01c 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -1,5 +1,7 @@ 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(); + } + } diff --git a/tests/phpunit/includes/block/BlockRestrictionTest.php b/tests/phpunit/includes/block/BlockRestrictionTest.php new file mode 100644 index 0000000000..7889f368c2 --- /dev/null +++ b/tests/phpunit/includes/block/BlockRestrictionTest.php @@ -0,0 +1,556 @@ +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__ ); + } +} diff --git a/tests/phpunit/includes/block/Restriction/PageRestrictionTest.php b/tests/phpunit/includes/block/Restriction/PageRestrictionTest.php new file mode 100644 index 0000000000..95cb3b7b72 --- /dev/null +++ b/tests/phpunit/includes/block/Restriction/PageRestrictionTest.php @@ -0,0 +1,64 @@ +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; + } +} diff --git a/tests/phpunit/includes/block/Restriction/RestrictionTestCase.php b/tests/phpunit/includes/block/Restriction/RestrictionTestCase.php new file mode 100644 index 0000000000..51e004c937 --- /dev/null +++ b/tests/phpunit/includes/block/Restriction/RestrictionTestCase.php @@ -0,0 +1,74 @@ +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(); +} -- 2.20.1