Update Special:BlockList to present Partial Block details from the database
authorDavid Barratt <dbarratt@wikimedia.org>
Tue, 19 Jun 2018 19:57:34 +0000 (15:57 -0400)
committerDbarratt <dbarratt@wikimedia.org>
Wed, 24 Oct 2018 16:26:06 +0000 (16:26 +0000)
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
languages/i18n/en.json
languages/i18n/qqq.json
tests/phpunit/includes/specials/pagers/BlockListPagerTest.php [new file with mode: 0644]

index 5789c28..74ec6b5 100644 (file)
@@ -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();
index 053fc68..91f259e 100644 (file)
        "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",
index c834c4d..b6764e3 100644 (file)
        "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 (file)
index 0000000..a05cbbd
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+
+use MediaWiki\Block\Restriction\PageRestriction;
+use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ * @coversDefaultClass BlockListPager
+ */
+class BlockListPagerTest extends MediaWikiTestCase {
+
+       /**
+        * @covers ::formatValue
+        * @dataProvider formatValueEmptyProvider
+        * @dataProvider formatValueDefaultProvider
+        * @param string $name
+        * @param string $value
+        * @param string $expected
+        */
+       public function testFormatValue( $name, $value, $expected, $row = null ) {
+               $this->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<br />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<br />0 minutes left',
+                               $row,
+                       ],
+                       [
+                               'ipb_by',
+                               '',
+                               $row->ipb_by_text,
+                               $row,
+                       ],
+                       [
+                               'ipb_params',
+                               '',
+                               '<ul><li>account creation disabled</li><li>cannot edit own talk page</li></ul>',
+                               $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( '<ul><li>'
+                       . wfMessage( 'blocklist-editing' )->text()
+                       . '<ul><li><a href="/index.php/'
+                       . $title->getDBKey()
+                       . '" title="'
+                       . $pageName
+                       . '">'
+                       . $pageName
+                       . '</a></li></ul></li></ul>',
+                       $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();
+       }
+}