From: Thalia Date: Fri, 14 Dec 2018 21:08:40 +0000 (+0000) Subject: Add namespace restrictions to Special:Block and API X-Git-Tag: 1.34.0-rc.0~3021^2 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=f589214d0fc897312de523c0f1c6e5920648b891 Add namespace restrictions to Special:Block and API This adds a UI for blocking namespaces to Special:Block and a namespacerestrictions parameter to the block API. The number of namespace restrictions in a single block is not limited as page restrictions are. The checkbox allowing the blocker to specify whether the target can edit their own user page is normally disabled for a partial block, but is re-enabled if the block is to the user talk namespace. If the config $wgBlockAllowsUTEdit is set to false, the checkbox will not appear, and the target will not be able to edit their own user talk page if they are sitewide-blocked, namespace-blocked from the user talk namespace, or page-blocked from their user talk page. Bug: T204986 Change-Id: I9e231ad109d7285486ec332b26780339592b8df7 --- diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index ed3d01ce8e..14177edb89 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -56,12 +56,14 @@ class ApiBlock extends ApiBase { $editingRestriction = 'sitewide'; $pageRestrictions = ''; + $namespaceRestrictions = ''; if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) { if ( $params['partial'] ) { $editingRestriction = 'partial'; } $pageRestrictions = implode( "\n", (array)$params['pagerestrictions'] ); + $namespaceRestrictions = implode( "\n", (array)$params['namespacerestrictions'] ); } if ( $params['userid'] !== null ) { @@ -119,6 +121,7 @@ class ApiBlock extends ApiBase { 'Tags' => $params['tags'], 'EditingRestriction' => $editingRestriction, 'PageRestrictions' => $pageRestrictions, + 'NamespaceRestrictions' => $namespaceRestrictions, ]; $retval = SpecialBlock::processForm( $data, $this->getContext() ); @@ -152,6 +155,7 @@ class ApiBlock extends ApiBase { if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) { $res['partial'] = $params['partial']; $res['pagerestrictions'] = $params['pagerestrictions']; + $res['namespacerestrictions'] = $params['namespacerestrictions']; } $this->getResult()->addValue( null, $this->getModuleName(), $res ); @@ -196,6 +200,10 @@ class ApiBlock extends ApiBase { ApiBase::PARAM_ISMULTI_LIMIT1 => 10, ApiBase::PARAM_ISMULTI_LIMIT2 => 10, ]; + $params['namespacerestrictions'] = [ + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => 'namespace', + ]; } return $params; diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index 8f5ba8d492..23bd814633 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -41,7 +41,8 @@ "apihelp-block-param-watchuser": "Watch the user's or IP address's user and talk pages.", "apihelp-block-param-tags": "Change tags to apply to the entry in the block log.", "apihelp-block-param-partial": "Block user from specific pages or namespaces rather than the entire site.", - "apihelp-block-param-pagerestrictions": "List of titles to block the user from editing. Only applies when 'partial' is set to true.", + "apihelp-block-param-pagerestrictions": "List of titles to block the user from editing. Only applies when partial is set to true.", + "apihelp-block-param-namespacerestrictions": "List of namespace IDs to block the user from editing. Only applies when partial is set to true.", "apihelp-block-example-ip-simple": "Block IP address 192.0.2.5 for three days with reason First strike.", "apihelp-block-example-user-complex": "Block user Vandal indefinitely with reason Vandalism, and prevent new account creation and email sending.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 6437adfcd0..bdd0afb8a6 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -50,6 +50,7 @@ "apihelp-block-param-tags": "{{doc-apihelp-param|block|tags}}", "apihelp-block-param-partial": "{{doc-apihelp-param|block|partial}}", "apihelp-block-param-pagerestrictions": "{{doc-apihelp-param|block|pagerestrictions}}", + "apihelp-block-param-namespacerestrictions": "{{doc-apihelp-param|block|namespacerestrictions}}", "apihelp-block-example-ip-simple": "{{doc-apihelp-example|block}}", "apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}", "apihelp-changeauthenticationdata-summary": "{{doc-apihelp-summary|changeauthenticationdata}}", diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php index a482720ef3..4fd263f664 100644 --- a/includes/specials/SpecialBlock.php +++ b/includes/specials/SpecialBlock.php @@ -23,6 +23,7 @@ use MediaWiki\Block\BlockRestriction; use MediaWiki\Block\Restriction\PageRestriction; +use MediaWiki\Block\Restriction\NamespaceRestriction; /** * A special page that allows users with 'block' right to block users from @@ -187,13 +188,23 @@ class SpecialBlock extends FormSpecialPage { 'label' => $this->msg( 'ipb-pages-label' )->text(), 'exists' => true, 'max' => 10, - 'cssclass' => 'mw-block-page-restrictions', + 'cssclass' => 'mw-block-restriction', 'showMissing' => false, 'input' => [ 'autocomplete' => false ], 'section' => 'actions', ]; + $a['NamespaceRestrictions'] = [ + 'type' => 'namespacesmultiselect', + 'label' => $this->msg( 'ipb-namespaces-label' )->text(), + 'exists' => true, + 'cssclass' => 'mw-block-restriction', + 'input' => [ + 'autocomplete' => false + ], + 'section' => 'actions', + ]; } $a['CreateAccount'] = [ @@ -391,21 +402,31 @@ class SpecialBlock extends FormSpecialPage { if ( $block instanceof Block ) { $pageRestrictions = []; + $namespaceRestrictions = []; foreach ( $block->getRestrictions() as $restriction ) { - if ( $restriction->getType() !== 'page' ) { - continue; + switch ( $restriction->getType() ) { + case PageRestriction::TYPE: + $pageRestrictions[] = $restriction->getTitle()->getPrefixedText(); + break; + case NamespaceRestriction::TYPE: + $namespaceRestrictions[] = $restriction->getValue(); + break; } - - $pageRestrictions[] = $restriction->getTitle()->getPrefixedText(); } - if ( !$block->isSitewide() && empty( $pageRestrictions ) ) { + if ( + !$block->isSitewide() && + empty( $pageRestrictions ) && + empty( $namespaceRestrictions ) + ) { $fields['Editing']['default'] = false; } // Sort the restrictions so they are in alphabetical order. sort( $pageRestrictions ); $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions ); + sort( $namespaceRestrictions ); + $fields['NamespaceRestrictions']['default'] = implode( "\n", $namespaceRestrictions ); } } } @@ -839,10 +860,11 @@ class SpecialBlock extends FormSpecialPage { return $reason; } - $restrictions = []; + $pageRestrictions = []; + $namespaceRestrictions = []; if ( $enablePartialBlocks ) { if ( $data['PageRestrictions'] !== '' ) { - $restrictions = array_map( function ( $text ) { + $pageRestrictions = array_map( function ( $text ) { $title = Title::newFromText( $text ); // Use the link cache since the title has already been loaded when // the field was validated. @@ -851,7 +873,13 @@ class SpecialBlock extends FormSpecialPage { return $restriction; }, explode( "\n", $data['PageRestrictions'] ) ); } + if ( $data['NamespaceRestrictions'] !== '' ) { + $namespaceRestrictions = array_map( function ( $id ) { + return new NamespaceRestriction( 0, $id ); + }, explode( "\n", $data['NamespaceRestrictions'] ) ); + } + $restrictions = ( array_merge( $pageRestrictions, $namespaceRestrictions ) ); $block->setRestrictions( $restrictions ); } @@ -1167,6 +1195,7 @@ class SpecialBlock extends FormSpecialPage { if ( isset( $data['Editing'] ) && $data['Editing'] === false ) { $data['EditingRestriction'] = 'partial'; $data['PageRestrictions'] = ''; + $data['NamespaceRestrictions'] = ''; } return self::processForm( $data, $form->getContext() ); } diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 18f41f10dc..460095ab21 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -2606,6 +2606,7 @@ "ipb-sitewide": "Sitewide", "ipb-partial": "Partial", "ipb-pages-label": "Pages", + "ipb-namespaces-label": "Namespaces", "badipaddress": "Invalid IP address", "blockipsuccesssub": "Block succeeded", "blockipsuccesstext": "[[Special:Contributions/$1|$1]] has been blocked.
\nSee the [[Special:BlockList|block list]] to review blocks.", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 1984fd508d..6f006c9330 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -2811,6 +2811,7 @@ "ipb-sitewide": "A type of block the user can select from on [[Special:Block]].", "ipb-partial": "A type of block the user can select from on [[Special:Block]].", "ipb-pages-label": "The label for an autocomplete text field to specify pages to block a user from editing on [[Special:Block]].", + "ipb-namespaces-label": "The label for an autocomplete text field to specify namespaces to block a user from editing on [[Special:Block]].", "badipaddress": "An error message shown when one entered an invalid IP address in blocking page.", "blockipsuccesssub": "Used as page title in [[Special:Block]].\n\nThis message is the subject for the following message:\n* {{msg-mw|Blockipsuccesstext}}", "blockipsuccesstext": "Used in [[Special:Block]].\nThe title (subject) for this message is {{msg-mw|Blockipsuccesssub}}.\n\nParameters:\n* $1 - username, can be used for GENDER", diff --git a/resources/src/mediawiki.special.block.js b/resources/src/mediawiki.special.block.js index b6d9b48164..b777c88ba7 100644 --- a/resources/src/mediawiki.special.block.js +++ b/resources/src/mediawiki.special.block.js @@ -22,7 +22,8 @@ editingWidget = infuseIfExists( $( '#mw-input-wpEditing' ) ), editingRestrictionWidget = infuseIfExists( $( '#mw-input-wpEditingRestriction' ) ), preventTalkPageEdit = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) ), - pageRestrictionsWidget = infuseIfExists( $( '#mw-input-wpPageRestrictions' ) ); + pageRestrictionsWidget = infuseIfExists( $( '#mw-input-wpPageRestrictions' ) ), + namespaceRestrictionsWidget = infuseIfExists( $( '#mw-input-wpNamespaceRestrictions' ) ); function updateBlockOptions() { var blocktarget = blockTargetWidget.getValue().trim(), @@ -49,15 +50,25 @@ if ( watchUserField ) { watchUserField.toggle( !isIpRange || isEmpty ); } - if ( pageRestrictionsWidget ) { + if ( editingRestrictionWidget ) { editingRestrictionWidget.setDisabled( !editingIsSelected ); + } + if ( pageRestrictionsWidget ) { pageRestrictionsWidget.setDisabled( !editingIsSelected || editingRestrictionValue === 'sitewide' ); } - if ( preventTalkPageEdit ) { - // TODO: (T210475) this option is disabled for partial blocks unless - // a namespace restriction for User_talk namespace is in place. - // This needs to be updated once Namespace restrictions is available - preventTalkPageEdit.setDisabled( editingRestrictionValue === 'partial' && editingIsSelected ); + if ( namespaceRestrictionsWidget ) { + namespaceRestrictionsWidget.setDisabled( !editingIsSelected || editingRestrictionValue === 'sitewide' ); + } + if ( preventTalkPageEdit && namespaceRestrictionsWidget ) { + // This option is disabled for partial blocks unless a namespace restriction + // for the User_talk namespace is in place. + preventTalkPageEdit.setDisabled( + editingIsSelected && + editingRestrictionValue === 'partial' && + namespaceRestrictionsWidget.getValue().indexOf( + String( mw.config.get( 'wgNamespaceIds' ).user_talk ) + ) === -1 + ); } } @@ -66,11 +77,14 @@ // Bind functions so they're checked whenever stuff changes blockTargetWidget.on( 'change', updateBlockOptions ); expiryWidget.on( 'change', updateBlockOptions ); + if ( editingWidget ) { + editingWidget.on( 'change', updateBlockOptions ); + } if ( editingRestrictionWidget ) { editingRestrictionWidget.on( 'change', updateBlockOptions ); } - if ( editingWidget ) { - editingWidget.on( 'change', updateBlockOptions ); + if ( namespaceRestrictionsWidget ) { + namespaceRestrictionsWidget.on( 'change', updateBlockOptions ); } // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours) diff --git a/resources/src/mediawiki.special/block.less b/resources/src/mediawiki.special/block.less index 30cafe5cdb..d7ee3da0e3 100644 --- a/resources/src/mediawiki.special/block.less +++ b/resources/src/mediawiki.special/block.less @@ -11,7 +11,7 @@ body.mw-special-Block { margin-left: @ooui-spacing-radio-label; } - .mw-block-page-restrictions.oo-ui-fieldLayout { + .mw-block-restriction.oo-ui-fieldLayout { margin-left: 2 * @ooui-spacing-radio-label; .oo-ui-widget { diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index feafdef7e0..a26f8a8188 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -1,5 +1,8 @@ getExistingTestPage( $title ); + $namespace = NS_TALK; $this->doBlock( [ 'partial' => true, 'pagerestrictions' => $title, + 'namespacerestrictions' => $namespace, ] ); $block = Block::newFromTarget( $this->mUser->getName() ); $this->assertFalse( $block->isSitewide() ); - $this->assertCount( 1, $block->getRestrictions() ); + $this->assertCount( 2, $block->getRestrictions() ); + $this->assertInstanceOf( PageRestriction::class, $block->getRestrictions()[0] ); $this->assertEquals( $title, $block->getRestrictions()[0]->getTitle()->getText() ); + $this->assertInstanceOf( NamespaceRestriction::class, $block->getRestrictions()[1] ); + $this->assertEquals( $namespace, $block->getRestrictions()[1]->getValue() ); } /** @@ -290,7 +298,7 @@ class ApiBlockTest extends ApiTestCase { * @expectedExceptionMessage Too many values supplied for parameter "pagerestrictions". The * limit is 10. */ - public function testBlockingToManyRestrictions() { + public function testBlockingToManyPageRestrictions() { $this->setMwGlobals( [ 'wgEnablePartialBlocks' => true, ] ); diff --git a/tests/phpunit/includes/specials/SpecialBlockTest.php b/tests/phpunit/includes/specials/SpecialBlockTest.php index e2c0084b21..55a8b66e57 100644 --- a/tests/phpunit/includes/specials/SpecialBlockTest.php +++ b/tests/phpunit/includes/specials/SpecialBlockTest.php @@ -2,6 +2,7 @@ use MediaWiki\Block\BlockRestriction; use MediaWiki\Block\Restriction\PageRestriction; +use MediaWiki\Block\Restriction\NamespaceRestriction; use Wikimedia\TestingAccessWrapper; /** @@ -45,6 +46,7 @@ class SpecialBlockTest extends SpecialPageTestBase { $this->assertArrayNotHasKey( 'EditingRestriction', $fields ); $this->assertArrayNotHasKey( 'PageRestrictions', $fields ); + $this->assertArrayNotHasKey( 'NamespaceRestrictions', $fields ); } /** @@ -60,6 +62,7 @@ class SpecialBlockTest extends SpecialPageTestBase { $this->assertArrayHasKey( 'EditingRestriction', $fields ); $this->assertArrayHasKey( 'PageRestrictions', $fields ); + $this->assertArrayHasKey( 'NamespaceRestrictions', $fields ); } /** @@ -115,6 +118,7 @@ class SpecialBlockTest extends SpecialPageTestBase { $block->setRestrictions( [ new PageRestriction( 0, $pageSaturn->getId() ), new PageRestriction( 0, $pageMars->getId() ), + new NamespaceRestriction( 0, NS_TALK ), ] ); $block->insert(); @@ -228,7 +232,7 @@ class SpecialBlockTest extends SpecialPageTestBase { /** * @covers ::processForm() */ - public function testProcessFormRestictions() { + public function testProcessFormRestrictions() { $this->setMwGlobals( [ 'wgEnablePartialBlocks' => true, ] ); @@ -262,6 +266,7 @@ class SpecialBlockTest extends SpecialPageTestBase { 'Watch' => '0', 'EditingRestriction' => 'partial', 'PageRestrictions' => implode( "\n", $titles ), + 'NamespaceRestrictions' => '', ]; $result = $page->processForm( $data, $context ); @@ -315,6 +320,7 @@ class SpecialBlockTest extends SpecialPageTestBase { 'Watch' => '0', 'EditingRestriction' => 'partial', 'PageRestrictions' => implode( "\n", $titles ), + 'NamespaceRestrictions' => '', ]; $result = $page->processForm( $data, $context );