From b3014df3b662bb7eb92586536b7dab59643d110f Mon Sep 17 00:00:00 2001 From: David Barratt Date: Tue, 26 Jun 2018 19:12:05 -0400 Subject: [PATCH] List Partial Block details in ApiQueryBlocks and ApiQueryUserinfo. The ApiQueryBlocks and ApiQueryUserinfo endpoints will now return whether or not the block is sitewide or partial. Partial block restrictions can be returned with ApiQueryBlocks. Bug: T197141 Change-Id: I76eb4cac4dc989c621a00a39996faebd0eb9892c --- includes/api/ApiQueryBlocks.php | 70 +++++++- includes/api/ApiQueryUserInfo.php | 1 + includes/api/i18n/en.json | 1 + includes/api/i18n/qqq.json | 1 + .../includes/api/ApiQueryBlocksTest.php | 157 ++++++++++++++++++ .../includes/api/ApiQueryUserInfoTest.php | 47 ++++++ 6 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 tests/phpunit/includes/api/ApiQueryBlocksTest.php create mode 100644 tests/phpunit/includes/api/ApiQueryUserInfoTest.php diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index 08c13e7aab..3cd2aceceb 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -20,6 +20,9 @@ * @file */ +use Wikimedia\Rdbms\IResultWrapper; +use MediaWiki\Block\BlockRestriction; + /** * Query module to enumerate all user blocks * @@ -48,6 +51,7 @@ class ApiQueryBlocks extends ApiQueryBase { $fld_reason = isset( $prop['reason'] ); $fld_range = isset( $prop['range'] ); $fld_flags = isset( $prop['flags'] ); + $fld_restrictions = isset( $prop['restrictions'] ); $result = $this->getResult(); @@ -64,8 +68,9 @@ class ApiQueryBlocks extends ApiQueryBase { $this->addFieldsIf( 'ipb_expiry', $fld_expiry ); $this->addFieldsIf( [ 'ipb_range_start', 'ipb_range_end' ], $fld_range ); $this->addFieldsIf( [ 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', - 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ], + 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk', 'ipb_sitewide' ], $fld_flags ); + $this->addFieldsIf( 'ipb_sitewide', $fld_restrictions ); if ( $fld_reason ) { $commentQuery = $commentStore->getJoin( 'ipb_reason' ); @@ -180,6 +185,11 @@ class ApiQueryBlocks extends ApiQueryBase { $res = $this->select( __METHOD__ ); + $restrictions = []; + if ( $fld_restrictions ) { + $restrictions = $this->getRestrictionData( $res, $params['limit'] ); + } + $count = 0; foreach ( $res as $row ) { if ( ++$count > $params['limit'] ) { @@ -227,7 +237,16 @@ class ApiQueryBlocks extends ApiQueryBase { $block['noemail'] = (bool)$row->ipb_block_email; $block['hidden'] = (bool)$row->ipb_deleted; $block['allowusertalk'] = (bool)$row->ipb_allow_usertalk; + $block['partial'] = !(bool)$row->ipb_sitewide; + } + + if ( $fld_restrictions ) { + $block['restrictions'] = []; + if ( !$row->ipb_sitewide && isset( $restrictions[$row->ipb_id] ) ) { + $block['restrictions'] = $restrictions[$row->ipb_id]; + } } + $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $block ); if ( !$fit ) { $this->setContinueEnumParameter( 'continue', "$row->ipb_timestamp|$row->ipb_id" ); @@ -256,6 +275,52 @@ class ApiQueryBlocks extends ApiQueryBase { return $name; } + /** + * Retrieves the restrictions based on the query result. + * + * @param IResultWrapper $result + * @param int $limit + * + * @return array + */ + private static function getRestrictionData( IResultWrapper $result, $limit ) { + $partialIds = []; + $count = 0; + foreach ( $result as $row ) { + if ( ++$count <= $limit && !$row->ipb_sitewide ) { + $partialIds[] = (int)$row->ipb_id; + } + } + + $restrictions = BlockRestriction::loadByBlockId( $partialIds ); + + $data = []; + $keys = [ + 'page' => 'pages', + 'ns' => 'namespaces', + ]; + foreach ( $restrictions as $restriction ) { + $key = $keys[$restriction->getType()]; + $id = $restriction->getBlockId(); + switch ( $restriction->getType() ) { + case 'page': + $value = [ 'id' => $restriction->getValue() ]; + self::addTitleInfo( $value, $restriction->getTitle() ); + break; + default: + $value = $restriction->getValue(); + } + + if ( !isset( $data[$id][$key] ) ) { + $data[$id][$key] = []; + ApiResult::setIndexedTagName( $data[$id][$key], $restriction->getType() ); + } + $data[$id][$key][] = $value; + } + + return $data; + } + public function getAllowedParams() { $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' ); @@ -308,7 +373,8 @@ class ApiQueryBlocks extends ApiQueryBase { 'expiry', 'reason', 'range', - 'flags' + 'flags', + 'restrictions', ], ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_HELP_MSG_PER_VALUE => [], diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index fa151c9851..44e2703a8d 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -70,6 +70,7 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['blockreason'] = $block->mReason; $vals['blockedtimestamp'] = wfTimestamp( TS_ISO_8601, $block->mTimestamp ); $vals['blockexpiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' ); + $vals['blockpartial'] = !$block->isSitewide(); if ( $block->getSystemBlockType() !== null ) { $vals['systemblocktype'] = $block->getSystemBlockType(); } diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index b0ef13d2de..588dbef1ed 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -684,6 +684,7 @@ "apihelp-query+blocks-paramvalue-prop-reason": "Adds the reason given for the block.", "apihelp-query+blocks-paramvalue-prop-range": "Adds the range of IP addresses affected by the block.", "apihelp-query+blocks-paramvalue-prop-flags": "Tags the ban with (autoblock, anononly, etc.).", + "apihelp-query+blocks-paramvalue-prop-restrictions": "Adds the partial block restrictions if the block is not sitewide.", "apihelp-query+blocks-param-show": "Show only items that meet these criteria.\nFor example, to see only indefinite blocks on IP addresses, set $1show=ip|!temp.", "apihelp-query+blocks-example-simple": "List blocks.", "apihelp-query+blocks-example-users": "List blocks of users Alice and Bob.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 9a812aa1c2..1d5485b5e6 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -644,6 +644,7 @@ "apihelp-query+blocks-paramvalue-prop-reason": "{{doc-apihelp-paramvalue|query+blocks|prop|reason}}", "apihelp-query+blocks-paramvalue-prop-range": "{{doc-apihelp-paramvalue|query+blocks|prop|range}}", "apihelp-query+blocks-paramvalue-prop-flags": "{{doc-apihelp-paramvalue|query+blocks|prop|flags}}", + "apihelp-query+blocks-paramvalue-prop-restrictions": "{{doc-apihelp-paramvalue|query+blocks|prop|flags}}", "apihelp-query+blocks-param-show": "{{doc-apihelp-param|query+blocks|show}}", "apihelp-query+blocks-example-simple": "{{doc-apihelp-example|query+blocks}}", "apihelp-query+blocks-example-users": "{{doc-apihelp-example|query+blocks}}", diff --git a/tests/phpunit/includes/api/ApiQueryBlocksTest.php b/tests/phpunit/includes/api/ApiQueryBlocksTest.php new file mode 100644 index 0000000000..dc7d450dba --- /dev/null +++ b/tests/phpunit/includes/api/ApiQueryBlocksTest.php @@ -0,0 +1,157 @@ +doApiRequest( [ + 'action' => 'query', + 'list' => 'blocks', + ] ); + $this->assertEquals( [ 'batchcomplete' => true, 'query' => [ 'blocks' => [] ] ], $data ); + } + + public function testExecuteBlock() { + $badActor = $this->getTestUser()->getUser(); + $sysop = $this->getTestSysop()->getUser(); + + $block = new Block( [ + 'address' => $badActor->getName(), + 'user' => $badActor->getId(), + 'by' => $sysop->getId(), + 'expiry' => 'infinity', + ] ); + + $block->insert(); + + list( $data ) = $this->doApiRequest( [ + 'action' => 'query', + 'list' => 'blocks', + ] ); + $this->arrayHasKey( 'query', $data ); + $this->arrayHasKey( 'blocks', $data['query'] ); + $this->assertCount( 1, $data['query']['blocks'] ); + $subset = [ + 'id' => $block->getId(), + 'user' => $badActor->getName(), + 'expiry' => $block->getExpiry(), + ]; + $this->assertArraySubset( $subset, $data['query']['blocks'][0] ); + } + + public function testExecuteSitewide() { + $badActor = $this->getTestUser()->getUser(); + $sysop = $this->getTestSysop()->getUser(); + + $block = new Block( [ + 'address' => $badActor->getName(), + 'user' => $badActor->getId(), + 'by' => $sysop->getId(), + 'ipb_expiry' => 'infinity', + 'ipb_sitewide' => 1, + ] ); + + $block->insert(); + + list( $data ) = $this->doApiRequest( [ + 'action' => 'query', + 'list' => 'blocks', + ] ); + $this->arrayHasKey( 'query', $data ); + $this->arrayHasKey( 'blocks', $data['query'] ); + $this->assertCount( 1, $data['query']['blocks'] ); + $subset = [ + 'id' => $block->getId(), + 'user' => $badActor->getName(), + 'expiry' => $block->getExpiry(), + 'partial' => !$block->isSitewide(), + ]; + $this->assertArraySubset( $subset, $data['query']['blocks'][0] ); + } + + public function testExecuteRestrictions() { + $badActor = $this->getTestUser()->getUser(); + $sysop = $this->getTestSysop()->getUser(); + + $block = new Block( [ + 'address' => $badActor->getName(), + 'user' => $badActor->getId(), + 'by' => $sysop->getId(), + 'expiry' => 'infinity', + 'sitewide' => 0, + ] ); + + $block->insert(); + + $subset = [ + 'id' => $block->getId(), + 'user' => $badActor->getName(), + 'expiry' => $block->getExpiry(), + ]; + + $title = 'Lady Macbeth'; + $pageData = $this->insertPage( $title ); + $pageId = $pageData['id']; + + $this->db->insert( 'ipblocks_restrictions', [ + 'ir_ipb_id' => $block->getId(), + 'ir_type' => PageRestriction::TYPE_ID, + 'ir_value' => $pageId, + ] ); + $this->db->insert( 'ipblocks_restrictions', [ + 'ir_ipb_id' => $block->getId(), + 'ir_type' => 2, + 'ir_value' => 3, + ] ); + + // Test without requesting restrictions. + list( $data ) = $this->doApiRequest( [ + 'action' => 'query', + 'list' => 'blocks', + ] ); + $this->arrayHasKey( 'query', $data ); + $this->arrayHasKey( 'blocks', $data['query'] ); + $this->assertCount( 1, $data['query']['blocks'] ); + $flagSubset = array_merge( $subset, [ + 'partial' => !$block->isSitewide(), + ] ); + $this->assertArraySubset( $flagSubset, $data['query']['blocks'][0] ); + $this->assertArrayNotHasKey( 'restrictions', $data['query']['blocks'][0] ); + + // Test requesting the restrictions. + list( $data ) = $this->doApiRequest( [ + 'action' => 'query', + 'list' => 'blocks', + 'bkprop' => 'id|user|expiry|restrictions' + ] ); + $this->arrayHasKey( 'query', $data ); + $this->arrayHasKey( 'blocks', $data['query'] ); + $this->assertCount( 1, $data['query']['blocks'] ); + $restrictionsSubset = array_merge( $subset, [ + 'restrictions' => [ + 'pages' => [ + [ + 'id' => $pageId, + 'ns' => 0, + 'title' => $title, + ], + ], + ], + ] ); + $this->assertArraySubset( $restrictionsSubset, $data['query']['blocks'][0] ); + $this->assertArrayNotHasKey( 'partial', $data['query']['blocks'][0] ); + } +} diff --git a/tests/phpunit/includes/api/ApiQueryUserInfoTest.php b/tests/phpunit/includes/api/ApiQueryUserInfoTest.php new file mode 100644 index 0000000000..7dcb75c486 --- /dev/null +++ b/tests/phpunit/includes/api/ApiQueryUserInfoTest.php @@ -0,0 +1,47 @@ +apiContext ), 'userinfo' ), + 'userinfo' + ); + + $block = new Block(); + $info = $apiQueryUserInfo->getBlockInfo( $block ); + $subset = [ + 'blockid' => null, + 'blockedby' => '', + 'blockedbyid' => 0, + 'blockreason' => '', + 'blockexpiry' => 'infinite', + 'blockpartial' => false, + ]; + $this->assertArraySubset( $subset, $info ); + } + + public function testGetBlockInfoPartial() { + $apiQueryUserInfo = new ApiQueryUserInfo( + new ApiQuery( new ApiMain( $this->apiContext ), 'userinfo' ), + 'userinfo' + ); + + $block = new Block( [ + 'sitewide' => false, + ] ); + $info = $apiQueryUserInfo->getBlockInfo( $block ); + $subset = [ + 'blockid' => null, + 'blockedby' => '', + 'blockedbyid' => 0, + 'blockreason' => '', + 'blockexpiry' => 'infinite', + 'blockpartial' => true, + ]; + $this->assertArraySubset( $subset, $info ); + } +} -- 2.20.1