From 170c49d61cb8441189cfad7f2c67b73057f6bc09 Mon Sep 17 00:00:00 2001 From: David Barratt Date: Tue, 19 Jun 2018 15:57:34 -0400 Subject: [PATCH] Update Special:BlockList to present Partial Block details from the database The Special:BlockList page will be updated to show details from the Partial Block within the "Block parameters" column. The format of the column will change to make the details more readable. Bug: T197143 Change-Id: Ibd79b049d93e427c2d541f8ef93005847482ef59 --- includes/specials/pagers/BlockListPager.php | 88 ++++++- languages/i18n/en.json | 2 + languages/i18n/qqq.json | 2 + .../specials/pagers/BlockListPagerTest.php | 236 ++++++++++++++++++ 4 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/includes/specials/pagers/BlockListPagerTest.php diff --git a/includes/specials/pagers/BlockListPager.php b/includes/specials/pagers/BlockListPager.php index 5789c283be..74ec6b55d3 100644 --- a/includes/specials/pagers/BlockListPager.php +++ b/includes/specials/pagers/BlockListPager.php @@ -22,6 +22,8 @@ /** * @ingroup Pager */ +use MediaWiki\Block\BlockRestriction; +use MediaWiki\Block\Restriction\Restriction; use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\IResultWrapper; @@ -30,6 +32,13 @@ class BlockListPager extends TablePager { protected $conds; protected $page; + /** + * Array of restrictions. + * + * @var Restriction[] + */ + protected $restrictions = []; + /** * @param SpecialPage $page * @param array $conds @@ -72,6 +81,8 @@ class BlockListPager extends TablePager { 'blocklist-nousertalk', 'unblocklink', 'change-blocklink', + 'blocklist-editing', + 'blocklist-editing-sitewide', ]; foreach ( $keys as $key ) { @@ -179,6 +190,18 @@ class BlockListPager extends TablePager { case 'ipb_params': $properties = []; + + if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) { + if ( $row->ipb_sitewide ) { + $properties[] = htmlspecialchars( $msg['blocklist-editing-sitewide'] ); + } + } + + if ( !$row->ipb_sitewide && $this->restrictions ) { + $list = $this->getRestrictionListHTML( $this->restrictions, $row ); + $properties[] = htmlspecialchars( $msg['blocklist-editing'] ) . $list; + } + if ( $row->ipb_anon_only ) { $properties[] = htmlspecialchars( $msg['anononlyblock'] ); } @@ -197,7 +220,17 @@ class BlockListPager extends TablePager { $properties[] = htmlspecialchars( $msg['blocklist-nousertalk'] ); } - $formatted = $language->commaList( $properties ); + $formatted = Html::rawElement( + 'ul', + [], + implode( '', array_map( function ( $prop ) { + return HTML::rawElement( + 'li', + [], + $prop + ); + }, $properties ) ) + ); break; default: @@ -208,6 +241,47 @@ class BlockListPager extends TablePager { return $formatted; } + /** + * Get Restriction List HTML + * + * @param Restriction[] $restrictions + * @param stdClass $row + * + * @return string + */ + private static function getRestrictionListHTML( + array $restrictions, + stdClass $row + ) { + $items = []; + + foreach ( $restrictions as $restriction ) { + if ( $restriction->getBlockId() !== (int)$row->ipb_id ) { + continue; + } + + if ( $restriction->getType() !== 'page' ) { + continue; + } + + $items[] = HTML::rawElement( + 'li', + [], + Linker::link( $restriction->getTitle() ) + ); + } + + if ( empty( $items ) ) { + return ''; + } + + return Html::rawElement( + 'ul', + [], + implode( '', $items ) + ); + } + function getQueryInfo() { $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' ); $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' ); @@ -232,6 +306,7 @@ class BlockListPager extends TablePager { 'ipb_deleted', 'ipb_block_email', 'ipb_allow_usertalk', + 'ipb_sitewide', ] + $commentQuery['fields'] + $actorQuery['fields'], 'conds' => $this->conds, 'join_conds' => [ @@ -296,6 +371,7 @@ class BlockListPager extends TablePager { $lb = new LinkBatch; $lb->setCaller( __METHOD__ ); + $partialBlocks = []; foreach ( $result as $row ) { $lb->add( NS_USER, $row->ipb_address ); $lb->add( NS_USER_TALK, $row->ipb_address ); @@ -304,6 +380,16 @@ class BlockListPager extends TablePager { $lb->add( NS_USER, $row->by_user_name ); $lb->add( NS_USER_TALK, $row->by_user_name ); } + + if ( !$row->ipb_sitewide ) { + $partialBlocks[] = $row->ipb_id; + } + } + + if ( $partialBlocks ) { + // Mutations to the $row object are not persisted. The restrictions will + // need be stored in a separate store. + $this->restrictions = BlockRestriction::loadByBlockId( $partialBlocks ); } $lb->execute(); diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 053fc68aee..91f259ee33 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2646,6 +2646,8 @@ "createaccountblock": "account creation disabled", "emailblock": "email disabled", "blocklist-nousertalk": "cannot edit own talk page", + "blocklist-editing": "editing", + "blocklist-editing-sitewide": "editing (sitewide)", "ipblocklist-empty": "The block list is empty.", "ipblocklist-no-results": "The requested IP address or username is not blocked.", "blocklink": "block", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index c834c4d79f..b6764e3784 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2848,6 +2848,8 @@ "createaccountblock": "Part of the log entry of user block in [[Special:BlockList]].\n\nSee also:\n* {{msg-mw|Block-log-flags-nocreate}}\n{{Related|Blocklist}}", "emailblock": "Part of the log entry of user block in [[Special:BlockList]].\n{{Related|Blocklist}}\n{{Identical|E-mail blocked}}", "blocklist-nousertalk": "Used in [[Special:IPBlockList]] when \"Allow this user to edit own talk page while blocked\" option hasn't been flagged.\n\nSee also {{msg-mw|Block-log-flags-nousertalk}}.\n\nPart of the log entry of user block in [[Special:BlockList]].\n\n{{Related|Blocklist}}", + "blocklist-editing-sitewide": "Used in [[Special:IPBlockList]] when a block is a sitewide block.", + "blocklist-editing": "Used in [[Special:IPBlockList]] when a block is not a sitewide block.", "ipblocklist-empty": "Used in [[Special:BlockList]], if the target is not specified.\n\nSee also:\n* {{msg-mw|Ipblocklist-no-results}}", "ipblocklist-no-results": "Used in [[Special:BlockList]], if the target is specified.\n\nSee also:\n* {{msg-mw|Ipblocklist-empty}}", "blocklink": "Display name for a link that, when selected, leads to a form where a user can be blocked. Used in page history and recent changes pages. Example: \"''UserName (Talk | contribs | '''block''')''\".\n\nUsed as link title in [[Special:Contributions]] and in [[Special:DeletedContributions]].\n\nSee also:\n* {{msg-mw|Sp-contributions-talk}}\n* {{msg-mw|Change-blocklink}}\n* {{msg-mw|Unblocklink}}\n* {{msg-mw|Sp-contributions-blocklog}}\n* {{msg-mw|Sp-contributions-uploads}}\n* {{msg-mw|Sp-contributions-logs}}\n* {{msg-mw|Sp-contributions-deleted}}\n* {{msg-mw|Sp-contributions-userrights}}\n{{Identical|Block}}", diff --git a/tests/phpunit/includes/specials/pagers/BlockListPagerTest.php b/tests/phpunit/includes/specials/pagers/BlockListPagerTest.php new file mode 100644 index 0000000000..a05cbbd545 --- /dev/null +++ b/tests/phpunit/includes/specials/pagers/BlockListPagerTest.php @@ -0,0 +1,236 @@ +setMwGlobals( [ + 'wgEnablePartialBlocks' => false, + ] ); + $row = $row ?: new stdClass; + $pager = new BlockListPager( new SpecialPage(), [] ); + $wrappedPager = TestingAccessWrapper::newFromObject( $pager ); + $wrappedPager->mCurrentRow = $row; + + $formatted = $pager->formatValue( $name, $value ); + $this->assertEquals( $expected, $formatted ); + } + + /** + * Test empty values. + */ + public function formatValueEmptyProvider() { + return [ + [ + 'test', + '', + 'Unable to format test', + ], + [ + 'ipb_timestamp', + wfTimestamp( TS_UNIX ), + date( 'H:i, j F Y' ), + ], + [ + 'ipb_expiry', + '', + 'infinite
0 minutes left', + ], + ]; + } + + /** + * Test the default row values. + */ + public function formatValueDefaultProvider() { + $row = (object)[ + 'ipb_user' => 0, + 'ipb_address' => '127.0.0.1', + 'ipb_by_text' => 'Admin', + 'ipb_create_account' => 1, + 'ipb_auto' => 0, + 'ipb_anon_only' => 0, + 'ipb_create_account' => 1, + 'ipb_enable_autoblock' => 1, + 'ipb_deleted' => 0, + 'ipb_block_email' => 0, + 'ipb_allow_usertalk' => 0, + 'ipb_sitewide' => 1, + ]; + + return [ + [ + 'test', + '', + 'Unable to format test', + $row, + ], + [ + 'ipb_timestamp', + wfTimestamp( TS_UNIX ), + date( 'H:i, j F Y' ), + $row, + ], + [ + 'ipb_expiry', + '', + 'infinite
0 minutes left', + $row, + ], + [ + 'ipb_by', + '', + $row->ipb_by_text, + $row, + ], + [ + 'ipb_params', + '', + '', + $row, + ] + ]; + } + + /** + * @covers ::formatValue + */ + public function testFormatValueRestrictions() { + $pager = new BlockListPager( new SpecialPage(), [] ); + + $row = (object)[ + 'ipb_id' => 0, + 'ipb_user' => 0, + 'ipb_anon_only' => 0, + 'ipb_enable_autoblock' => 0, + 'ipb_create_account' => 0, + 'ipb_block_email' => 0, + 'ipb_allow_usertalk' => 1, + 'ipb_sitewide' => 0, + ]; + $wrappedPager = TestingAccessWrapper::newFromObject( $pager ); + $wrappedPager->mCurrentRow = $row; + + $pageName = 'Victor Frankenstein'; + $page = $this->insertPage( $pageName ); + $title = $page['title']; + $pageId = $page['id']; + + $restrictions = [ + ( new PageRestriction( 0, $pageId ) )->setTitle( $title ) + ]; + + $wrappedPager = TestingAccessWrapper::newFromObject( $pager ); + $wrappedPager->restrictions = $restrictions; + + $formatted = $pager->formatValue( 'ipb_params', '' ); + $this->assertEquals( '', + $formatted + ); + } + + /** + * @covers ::preprocessResults + */ + public function testPreprocessResults() { + // Test the Link Cache. + $linkCache = MediaWikiServices::getInstance()->getLinkCache(); + $wrappedlinkCache = TestingAccessWrapper::newFromObject( $linkCache ); + + $links = [ + 'User:127.0.0.1', + 'User_talk:127.0.0.1', + 'User:Admin', + 'User_talk:Admin', + ]; + + foreach ( $links as $link ) { + $this->assertNull( $wrappedlinkCache->badLinks->get( $link ) ); + } + + $row = (object)[ + 'ipb_address' => '127.0.0.1', + 'by_user_name' => 'Admin', + 'ipb_sitewide' => 1, + 'ipb_timestamp' => $this->db->timestamp( wfTimestamp( TS_MW ) ), + ]; + $pager = new BlockListPager( new SpecialPage(), [] ); + $pager->preprocessResults( [ $row ] ); + + foreach ( $links as $link ) { + $this->assertSame( 1, $wrappedlinkCache->badLinks->get( $link ) ); + } + + // Test Sitewide Blocks. + $row = (object)[ + 'ipb_address' => '127.0.0.1', + 'by_user_name' => 'Admin', + 'ipb_sitewide' => 1, + ]; + $pager = new BlockListPager( new SpecialPage(), [] ); + $pager->preprocessResults( [ $row ] ); + + $this->assertObjectNotHasAttribute( 'ipb_restrictions', $row ); + + $pageName = 'Victor Frankenstein'; + $page = $this->getExistingTestPage( 'Victor Frankenstein' ); + $title = $page->getTitle(); + + $target = '127.0.0.1'; + + // Test Partial Blocks Blocks. + $block = new Block( [ + 'address' => $target, + 'by' => $this->getTestSysop()->getUser()->getId(), + 'reason' => 'Parce que', + 'expiry' => $this->db->getInfinity(), + 'sitewide' => false, + ] ); + $block->setRestrictions( [ + new PageRestriction( 0, $page->getId() ), + ] ); + $block->insert(); + + $result = $this->db->select( 'ipblocks', [ '*' ], [ 'ipb_id' => $block->getId() ] ); + + $pager = new BlockListPager( new SpecialPage(), [] ); + $pager->preprocessResults( $result ); + + $wrappedPager = TestingAccessWrapper::newFromObject( $pager ); + + $restrictions = $wrappedPager->restrictions; + $this->assertInternalType( 'array', $restrictions ); + + $restriction = $restrictions[0]; + $this->assertEquals( $page->getId(), $restriction->getValue() ); + $this->assertEquals( $page->getId(), $restriction->getTitle()->getArticleId() ); + $this->assertEquals( $title->getDBKey(), $restriction->getTitle()->getDBKey() ); + $this->assertEquals( $title->getNamespace(), $restriction->getTitle()->getNamespace() ); + + // Delete the block and the restrictions. + $block->delete(); + } +} -- 2.20.1