}
}
+ $editingRestriction = 'sitewide';
+ $pageRestrictions = '';
+ if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
+ if ( $params['pagerestrictions'] ) {
+ $count = count( $params['pagerestrictions'] );
+ if ( $count > 10 ) {
+ $this->dieWithError(
+ $this->msg(
+ 'apierror-integeroutofrange-abovebotmax',
+ 'pagerestrictions',
+ 10,
+ $count
+ )
+ );
+ }
+ }
+
+ if ( $params['partial'] ) {
+ $editingRestriction = 'partial';
+ }
+
+ $pageRestrictions = implode( "\n", $params['pagerestrictions'] );
+ }
+
if ( $params['userid'] !== null ) {
$username = User::whoIs( $params['userid'] );
'Watch' => $params['watchuser'],
'Confirm' => true,
'Tags' => $params['tags'],
+ 'EditingRestriction' => $editingRestriction,
+ 'PageRestrictions' => $pageRestrictions,
];
$retval = SpecialBlock::processForm( $data, $this->getContext() );
$res['allowusertalk'] = $params['allowusertalk'];
$res['watchuser'] = $params['watchuser'];
+ if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
+ $res['partial'] = $params['partial'];
+ $res['pagerestrictions'] = $params['pagerestrictions'];
+ }
+
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
}
public function getAllowedParams() {
- return [
+ $params = [
'user' => [
ApiBase::PARAM_TYPE => 'user',
],
ApiBase::PARAM_ISMULTI => true,
],
];
+
+ if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
+ $params['partial'] = false;
+ $params['pagerestrictions'] = [
+ ApiBase::PARAM_ISMULTI => true,
+ ];
+ }
+
+ return $params;
}
public function needsToken() {
"apihelp-block-param-reblock": "If the user is already blocked, overwrite the existing block.",
"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-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
"apihelp-block-example-user-complex": "Block user <kbd>Vandal</kbd> indefinitely with reason <kbd>Vandalism</kbd>, and prevent new account creation and email sending.",
"apihelp-block-param-reblock": "{{doc-apihelp-param|block|reblock}}",
"apihelp-block-param-watchuser": "{{doc-apihelp-param|block|watchuser}}",
"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-example-ip-simple": "{{doc-apihelp-example|block}}",
"apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}",
"apihelp-changeauthenticationdata-summary": "{{doc-apihelp-summary|changeauthenticationdata}}",
* @ingroup SpecialPage
*/
+use MediaWiki\Block\BlockRestriction;
+use MediaWiki\Block\Restriction\PageRestriction;
+
/**
* A special page that allows users with 'block' right to block users from
* editing pages and other actions
$conf = $this->getConfig();
$oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
+ $enablePartialBlocks = $conf->get( 'EnablePartialBlocks' );
- $a = [
- 'Target' => [
- 'type' => 'user',
- 'ipallowed' => true,
- 'iprange' => true,
- 'label-message' => 'ipaddressorusername',
- 'id' => 'mw-bi-target',
- 'size' => '45',
- 'autofocus' => true,
- 'required' => true,
- 'validation-callback' => [ __CLASS__, 'validateTargetField' ],
- ],
- 'Expiry' => [
- 'type' => 'expiry',
- 'label-message' => 'ipbexpiry',
- 'required' => true,
- 'options' => $suggestedDurations,
- 'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
- ],
- 'Reason' => [
- 'type' => 'selectandother',
- // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
- // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
- // Unicode codepoints (or 255 UTF-8 bytes for old schema).
- 'maxlength' => $oldCommentSchema ? 255 : CommentStore::COMMENT_CHARACTER_LIMIT,
- 'maxlength-unit' => 'codepoints',
- 'label-message' => 'ipbreason',
- 'options-message' => 'ipbreason-dropdown',
- ],
- 'CreateAccount' => [
- 'type' => 'check',
- 'label-message' => 'ipbcreateaccount',
- 'default' => true,
- ],
+ $a = [];
+
+ $a['Target'] = [
+ 'type' => 'user',
+ 'ipallowed' => true,
+ 'iprange' => true,
+ 'label-message' => 'ipaddressorusername',
+ 'id' => 'mw-bi-target',
+ 'size' => '45',
+ 'autofocus' => true,
+ 'required' => true,
+ 'validation-callback' => [ __CLASS__, 'validateTargetField' ],
+ ];
+
+ if ( $enablePartialBlocks ) {
+ $a['EditingRestriction'] = [
+ 'type' => 'radio',
+ 'label' => $this->msg( 'ipb-type-label' )->text(),
+ 'options' => [
+ $this->msg( 'ipb-sitewide' )->text() => 'sitewide',
+ $this->msg( 'ipb-partial' )->text() => 'partial',
+ ],
+ ];
+ $a['PageRestrictions'] = [
+ 'type' => 'titlesmultiselect',
+ 'label' => $this->msg( 'ipb-pages-label' )->text(),
+ 'exists' => true,
+ 'max' => 10,
+ 'cssclass' => 'mw-block-page-restrictions',
+ ];
+ }
+
+ $a['Expiry'] = [
+ 'type' => 'expiry',
+ 'label-message' => 'ipbexpiry',
+ 'required' => true,
+ 'options' => $suggestedDurations,
+ 'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
+ ];
+
+ $a['Reason'] = [
+ 'type' => 'selectandother',
+ // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
+ // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
+ // Unicode codepoints (or 255 UTF-8 bytes for old schema).
+ 'maxlength' => $oldCommentSchema ? 255 : CommentStore::COMMENT_CHARACTER_LIMIT,
+ 'maxlength-unit' => 'codepoints',
+ 'label-message' => 'ipbreason',
+ 'options-message' => 'ipbreason-dropdown',
+ ];
+
+ $a['CreateAccount'] = [
+ 'type' => 'check',
+ 'label-message' => 'ipbcreateaccount',
+ 'default' => true,
];
if ( self::canBlockEmail( $user ) ) {
unset( $fields['Confirm']['default'] );
$this->preErrors[] = [ 'ipb-blockingself', 'ipb-confirmaction' ];
}
+
+ if ( $this->getConfig()->get( 'EnablePartialBlocks' ) ) {
+ if ( $block instanceof Block && !$block->isSitewide() ) {
+ $fields['EditingRestriction']['default'] = 'partial';
+ } else {
+ $fields['EditingRestriction']['default'] = 'sitewide';
+ }
+
+ if ( $block instanceof Block ) {
+ $pageRestrictions = [];
+ foreach ( $block->getRestrictions() as $restriction ) {
+ if ( $restriction->getType() !== 'page' ) {
+ continue;
+ }
+
+ $pageRestrictions[] = $restriction->getTitle()->getPrefixedText();
+ }
+
+ // Sort the restrictions so they are in alphabetical order.
+ sort( $pageRestrictions );
+ $fields['PageRestrictions']['default'] = implode( "\n", $pageRestrictions );
+ }
+ }
}
/**
global $wgBlockAllowsUTEdit, $wgHideUserContribLimit;
$performer = $context->getUser();
+ $enablePartialBlocks = $context->getConfig()->get( 'EnablePartialBlocks' );
// Handled by field validator callback
// self::validateTargetField( $data['Target'] );
$block->isAutoblocking( $data['AutoBlock'] );
$block->mHideName = $data['HideUser'];
+ if (
+ $enablePartialBlocks &&
+ isset( $data['EditingRestriction'] ) &&
+ $data['EditingRestriction'] === 'partial'
+ ) {
+ $block->isSitewide( false );
+ }
+
$reason = [ 'hookaborted' ];
if ( !Hooks::run( 'BlockIp', [ &$block, &$performer, &$reason ] ) ) {
return $reason;
}
+ $restrictions = [];
+ if ( $enablePartialBlocks ) {
+ if ( !empty( $data['PageRestrictions'] ) ) {
+ $restrictions = array_map( function ( $text ) {
+ $title = Title::newFromText( $text );
+ // Use the link cache since the title has already been loaded when
+ // the field was validated.
+ $restriction = new PageRestriction( 0, $title->getArticleId() );
+ $restriction->setTitle( $title );
+ return $restriction;
+ }, explode( "\n", $data['PageRestrictions'] ) );
+ }
+
+ $block->setRestrictions( $restrictions );
+ }
+
$priorBlock = null;
# Try to insert block. Is there a conflicting block?
$status = $block->insert();
$currentBlock->prevents( 'sendemail', $block->prevents( 'sendemail' ) );
$currentBlock->prevents( 'editownusertalk', $block->prevents( 'editownusertalk' ) );
$currentBlock->mReason = $block->mReason;
- $currentBlock->isSitewide( $block->isSitewide() );
+
+ if ( $enablePartialBlocks ) {
+ // Maintain the sitewide status. If partial blocks is not enabled,
+ // saving the block will result in a sitewide block.
+ $currentBlock->isSitewide( $block->isSitewide() );
+
+ // Set the block id of the restrictions.
+ $currentBlock->setRestrictions(
+ BlockRestriction::setBlockId( $currentBlock->getId(), $restrictions )
+ );
+ }
$status = $currentBlock->update();
"ipb-disableusertalk": "Prevent this user from editing their own talk page while blocked",
"ipb-change-block": "Re-block the user with these settings",
"ipb-confirm": "Confirm block",
+ "ipb-sitewide": "Sitewide",
+ "ipb-partial": "Partial",
+ "ipb-type-label": "Type",
+ "ipb-pages-label": "Pages",
"badipaddress": "Invalid IP address",
"blockipsuccesssub": "Block succeeded",
"blockipsuccesstext": "[[Special:Contributions/$1|$1]] has been blocked.<br />\nSee the [[Special:BlockList|block list]] to review blocks.",
"ipb-disableusertalk": "{{doc-singularthey}}\nUsed as label for checkbox in [[Special:Block]].\n\nSee also:\n* {{msg-mw|ipbemailban}}\n* {{msg-mw|ipbenableautoblock}}\n* {{msg-mw|ipbhidename}}\n* {{msg-mw|ipbwatchuser}}\n* {{msg-mw|ipb-hardblock}}",
"ipb-change-block": "Confirmation checkbox required for blocks that would override an earlier block. Appears together with {{msg-mw|ipb-needreblock}}.",
"ipb-confirm": "Used as hidden field in the form on [[Special:Block]].",
+ "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-type-label": "The label of the type of editing restriction the admin would like to impose on [[Special:Block]].",
+ "ipb-pages-label": "The label for a autocomplete text field to specify pages 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",
],
'mediawiki.special.block' => [
'scripts' => 'resources/src/mediawiki.special.block.js',
+ 'styles' => 'resources/src/mediawiki.special.block.less',
'dependencies' => [
'oojs-ui-core',
'oojs-ui.styles.icons-editing-core',
'mediawiki.htmlform',
'moment',
],
+ 'targets' => [ 'desktop', 'mobile' ],
],
'mediawiki.special.changecredentials.js' => [
'scripts' => 'resources/src/mediawiki.special.changecredentials.js',
enableAutoblockField = infuseOrNull( $( '#mw-input-wpAutoBlock' ).closest( '.oo-ui-fieldLayout' ) ),
hideUserField = infuseOrNull( $( '#mw-input-wpHideUser' ).closest( '.oo-ui-fieldLayout' ) ),
watchUserField = infuseOrNull( $( '#mw-input-wpWatch' ).closest( '.oo-ui-fieldLayout' ) ),
- expiryWidget = infuseOrNull( 'mw-input-wpExpiry' );
+ expiryWidget = infuseOrNull( 'mw-input-wpExpiry' ),
+ editingRestrictionWidget = infuseOrNull( 'mw-input-wpEditingRestriction' ),
+ pageRestrictionsWidget = infuseOrNull( 'mw-input-wpPageRestrictions' );
function updateBlockOptions() {
var blocktarget = blockTargetWidget.getValue().trim(),
expiryValue = expiryWidget.getValue(),
// infinityValues are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ],
- isIndefinite = infinityValues.indexOf( expiryValue ) !== -1;
+ isIndefinite = infinityValues.indexOf( expiryValue ) !== -1,
+ editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : undefined;
if ( enableAutoblockField ) {
enableAutoblockField.toggle( !( isNonEmptyIp ) );
if ( watchUserField ) {
watchUserField.toggle( !( isIpRange && !isEmpty ) );
}
+ if ( pageRestrictionsWidget ) {
+ pageRestrictionsWidget.setDisabled( editingRestrictionValue === 'sitewide' );
+ }
}
if ( blockTargetWidget ) {
// Bind functions so they're checked whenever stuff changes
blockTargetWidget.on( 'change', updateBlockOptions );
expiryWidget.on( 'change', updateBlockOptions );
+ editingRestrictionWidget.on( 'change', updateBlockOptions );
// Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours)
updateBlockOptions();
--- /dev/null
+.mw-block-page-restrictions {
+ margin-left: 2em;
+ .oo-ui-widget {
+ max-width: 48em;
+ }
+}
$this->doBlock( [ 'expiry' => '' ] );
}
+ public function testBlockWithRestrictions() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+
+ $title = 'Foo';
+ $page = $this->getExistingTestPage( $title );
+
+ $this->doBlock( [
+ 'partial' => true,
+ 'pagerestrictions' => $title,
+ ] );
+
+ $block = Block::newFromTarget( $this->mUser->getName() );
+
+ $this->assertFalse( $block->isSitewide() );
+ $this->assertCount( 1, $block->getRestrictions() );
+ $this->assertEquals( $title, $block->getRestrictions()[0]->getTitle()->getText() );
+ }
+
/**
* @expectedException ApiUsageException
* @expectedExceptionMessage The "token" parameter must be set
self::$users['sysop']->getUser()
);
}
+
+ /**
+ * @expectedException ApiUsageException
+ * @expectedExceptionMessage Invalid value "127.0.0.1/64" for user parameter "user".
+ */
+ public function testBlockWithLargeRange() {
+ $tokens = $this->getTokens();
+
+ $this->doApiRequest(
+ [
+ 'action' => 'block',
+ 'user' => '127.0.0.1/64',
+ 'reason' => 'Some reason',
+ 'token' => $tokens['blocktoken'],
+ ],
+ null,
+ false,
+ self::$users['sysop']->getUser()
+ );
+ }
+
+ /**
+ * @expectedException ApiUsageException
+ * @expectedExceptionMessage "pagerestrictions" may not be over 10 (set to 11) for bots or sysops.
+ */
+ public function testBlockingToManyRestrictions() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+
+ $tokens = $this->getTokens();
+
+ $this->doApiRequest(
+ [
+ 'action' => 'block',
+ 'user' => $this->mUser->getName(),
+ 'reason' => 'Some reason',
+ 'partial' => true,
+ 'pagerestrictions' => 'One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Eleven',
+ 'token' => $tokens['blocktoken'],
+ ],
+ null,
+ false,
+ self::$users['sysop']->getUser()
+ );
+ }
}
--- /dev/null
+<?php
+
+use MediaWiki\Block\BlockRestriction;
+use MediaWiki\Block\Restriction\PageRestriction;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Blocking
+ * @group Database
+ * @coversDefaultClass SpecialBlock
+ */
+class SpecialBlockTest extends SpecialPageTestBase {
+ /**
+ * {@inheritdoc}
+ */
+ protected function newSpecialPage() {
+ return new SpecialBlock();
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ $this->resetTables();
+ }
+
+ /**
+ * @covers ::getFormFields()
+ */
+ public function testGetFormFields() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => false,
+ ] );
+ $page = $this->newSpecialPage();
+ $wrappedPage = TestingAccessWrapper::newFromObject( $page );
+ $fields = $wrappedPage->getFormFields();
+ $this->assertInternalType( 'array', $fields );
+ $this->assertArrayHasKey( 'Target', $fields );
+ $this->assertArrayHasKey( 'Expiry', $fields );
+ $this->assertArrayHasKey( 'Reason', $fields );
+ $this->assertArrayHasKey( 'CreateAccount', $fields );
+ $this->assertArrayHasKey( 'DisableUTEdit', $fields );
+ $this->assertArrayHasKey( 'DisableUTEdit', $fields );
+ $this->assertArrayHasKey( 'AutoBlock', $fields );
+ $this->assertArrayHasKey( 'HardBlock', $fields );
+ $this->assertArrayHasKey( 'PreviousTarget', $fields );
+ $this->assertArrayHasKey( 'Confirm', $fields );
+
+ $this->assertArrayNotHasKey( 'EditingRestriction', $fields );
+ $this->assertArrayNotHasKey( 'PageRestrictions', $fields );
+ }
+
+ /**
+ * @covers ::getFormFields()
+ */
+ public function testGetFormFieldsPartialBlocks() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+ $page = $this->newSpecialPage();
+ $wrappedPage = TestingAccessWrapper::newFromObject( $page );
+ $fields = $wrappedPage->getFormFields();
+
+ $this->assertArrayHasKey( 'EditingRestriction', $fields );
+ $this->assertArrayHasKey( 'PageRestrictions', $fields );
+ }
+
+ /**
+ * @covers ::maybeAlterFormDefaults()
+ */
+ public function testMaybeAlterFormDefaults() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => false,
+ ] );
+
+ $block = $this->insertBlock();
+
+ // Refresh the block from the database.
+ $block = Block::newFromTarget( $block->getTarget() );
+
+ $page = $this->newSpecialPage();
+
+ $wrappedPage = TestingAccessWrapper::newFromObject( $page );
+ $wrappedPage->target = $block->getTarget();
+ $fields = $wrappedPage->getFormFields();
+
+ $this->assertSame( (string)$block->getTarget(), $fields['Target']['default'] );
+ $this->assertSame( $block->isHardblock(), $fields['HardBlock']['default'] );
+ $this->assertSame( $block->prevents( 'createaccount' ), $fields['CreateAccount']['default'] );
+ $this->assertSame( $block->isAutoblocking(), $fields['AutoBlock']['default'] );
+ $this->assertSame( $block->prevents( 'editownusertalk' ), $fields['DisableUTEdit']['default'] );
+ $this->assertSame( $block->mReason, $fields['Reason']['default'] );
+ $this->assertSame( 'infinite', $fields['Expiry']['default'] );
+ }
+
+ /**
+ * @covers ::maybeAlterFormDefaults()
+ */
+ public function testMaybeAlterFormDefaultsPartial() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+ $pageSaturn = $this->getExistingTestPage( 'Saturn' );
+ $pageMars = $this->getExistingTestPage( 'Mars' );
+
+ $block = new \Block( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ 'sitewide' => 0,
+ 'enableAutoblock' => true,
+ ] );
+
+ $block->setRestrictions( [
+ new PageRestriction( 0, $pageSaturn->getId() ),
+ new PageRestriction( 0, $pageMars->getId() ),
+ ] );
+
+ $block->insert();
+
+ // Refresh the block from the database.
+ $block = Block::newFromTarget( $block->getTarget() );
+
+ $page = $this->newSpecialPage();
+
+ $wrappedPage = TestingAccessWrapper::newFromObject( $page );
+ $wrappedPage->target = $block->getTarget();
+ $fields = $wrappedPage->getFormFields();
+
+ $titles = [
+ $pageMars->getTitle()->getPrefixedText(),
+ $pageSaturn->getTitle()->getPrefixedText(),
+ ];
+
+ $this->assertSame( (string)$block->getTarget(), $fields['Target']['default'] );
+ $this->assertSame( 'partial', $fields['EditingRestriction']['default'] );
+ $this->assertSame( implode( "\n", $titles ), $fields['PageRestrictions']['default'] );
+ }
+
+ /**
+ * @covers ::processForm()
+ */
+ public function testProcessForm() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => false,
+ ] );
+ $badActor = $this->getTestUser()->getUser();
+ $context = RequestContext::getMain();
+
+ $page = $this->newSpecialPage();
+ $reason = 'test';
+ $expiry = 'infinity';
+ $data = [
+ 'Target' => (string)$badActor,
+ 'Expiry' => 'infinity',
+ 'Reason' => [
+ $reason,
+ ],
+ 'Confirm' => '1',
+ 'CreateAccount' => '0',
+ 'DisableUTEdit' => '0',
+ 'DisableEmail' => '0',
+ 'HardBlock' => '0',
+ 'AutoBlock' => '1',
+ 'HideUser' => '0',
+ 'Watch' => '0',
+ ];
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ }
+
+ /**
+ * @covers ::processForm()
+ */
+ public function testProcessFormExisting() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => false,
+ ] );
+ $badActor = $this->getTestUser()->getUser();
+ $sysop = $this->getTestSysop()->getUser();
+ $context = RequestContext::getMain();
+
+ // Create a block that will be updated.
+ $block = new \Block( [
+ 'address' => $badActor->getName(),
+ 'user' => $badActor->getId(),
+ 'by' => $sysop->getId(),
+ 'expiry' => 'infinity',
+ 'sitewide' => 0,
+ 'enableAutoblock' => false,
+ ] );
+ $block->insert();
+
+ $page = $this->newSpecialPage();
+ $reason = 'test';
+ $expiry = 'infinity';
+ $data = [
+ 'Target' => (string)$badActor,
+ 'Expiry' => 'infinity',
+ 'Reason' => [
+ $reason,
+ ],
+ 'Confirm' => '1',
+ 'CreateAccount' => '0',
+ 'DisableUTEdit' => '0',
+ 'DisableEmail' => '0',
+ 'HardBlock' => '0',
+ 'AutoBlock' => '1',
+ 'HideUser' => '0',
+ 'Watch' => '0',
+ ];
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertSame( '1', $block->isAutoblocking() );
+ }
+
+ /**
+ * @covers ::processForm()
+ */
+ public function testProcessFormRestictions() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+ $badActor = $this->getTestUser()->getUser();
+ $context = RequestContext::getMain();
+
+ $pageSaturn = $this->getExistingTestPage( 'Saturn' );
+ $pageMars = $this->getExistingTestPage( 'Mars' );
+
+ $titles = [
+ $pageSaturn->getTitle()->getText(),
+ $pageMars->getTitle()->getText(),
+ ];
+
+ $page = $this->newSpecialPage();
+ $reason = 'test';
+ $expiry = 'infinity';
+ $data = [
+ 'Target' => (string)$badActor,
+ 'Expiry' => 'infinity',
+ 'Reason' => [
+ $reason,
+ ],
+ 'Confirm' => '1',
+ 'CreateAccount' => '0',
+ 'DisableUTEdit' => '0',
+ 'DisableEmail' => '0',
+ 'HardBlock' => '0',
+ 'AutoBlock' => '1',
+ 'HideUser' => '0',
+ 'Watch' => '0',
+ 'EditingRestriction' => 'partial',
+ 'PageRestrictions' => implode( "\n", $titles ),
+ ];
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertCount( 2, $block->getRestrictions() );
+ $this->assertTrue( BlockRestriction::equals( $block->getRestrictions(), [
+ new PageRestriction( $block->getId(), $pageMars->getId() ),
+ new PageRestriction( $block->getId(), $pageSaturn->getId() ),
+ ] ) );
+ }
+
+ /**
+ * @covers ::processForm()
+ */
+ public function testProcessFormRestrictionsChange() {
+ $this->setMwGlobals( [
+ 'wgEnablePartialBlocks' => true,
+ ] );
+ $badActor = $this->getTestUser()->getUser();
+ $context = RequestContext::getMain();
+
+ $pageSaturn = $this->getExistingTestPage( 'Saturn' );
+ $pageMars = $this->getExistingTestPage( 'Mars' );
+
+ $titles = [
+ $pageSaturn->getTitle()->getText(),
+ $pageMars->getTitle()->getText(),
+ ];
+
+ // Create a partial block.
+ $page = $this->newSpecialPage();
+ $reason = 'test';
+ $expiry = 'infinity';
+ $data = [
+ 'Target' => (string)$badActor,
+ 'Expiry' => 'infinity',
+ 'Reason' => [
+ $reason,
+ ],
+ 'Confirm' => '1',
+ 'CreateAccount' => '0',
+ 'DisableUTEdit' => '0',
+ 'DisableEmail' => '0',
+ 'HardBlock' => '0',
+ 'AutoBlock' => '1',
+ 'HideUser' => '0',
+ 'Watch' => '0',
+ 'EditingRestriction' => 'partial',
+ 'PageRestrictions' => implode( "\n", $titles ),
+ ];
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertFalse( $block->isSitewide() );
+ $this->assertCount( 2, $block->getRestrictions() );
+ $this->assertTrue( BlockRestriction::equals( $block->getRestrictions(), [
+ new PageRestriction( $block->getId(), $pageMars->getId() ),
+ new PageRestriction( $block->getId(), $pageSaturn->getId() ),
+ ] ) );
+
+ // Remove a page from the partial block.
+ $data['PageRestrictions'] = $pageMars->getTitle()->getText();
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertFalse( $block->isSitewide() );
+ $this->assertCount( 1, $block->getRestrictions() );
+ $this->assertTrue( BlockRestriction::equals( $block->getRestrictions(), [
+ new PageRestriction( $block->getId(), $pageMars->getId() ),
+ ] ) );
+
+ // Remove the last page from the block.
+ $data['PageRestrictions'] = '';
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertFalse( $block->isSitewide() );
+ $this->assertCount( 0, $block->getRestrictions() );
+
+ // Change to sitewide.
+ $data['EditingRestriction'] = 'sitewide';
+ $result = $page->processForm( $data, $context );
+
+ $this->assertTrue( $result );
+
+ $block = Block::newFromTarget( $badActor );
+ $this->assertSame( $reason, $block->mReason );
+ $this->assertSame( $expiry, $block->getExpiry() );
+ $this->assertTrue( $block->isSitewide() );
+ $this->assertCount( 0, $block->getRestrictions() );
+
+ // 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 );
+ }
+
+ 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' => 1,
+ 'enableAutoblock' => true,
+ ] );
+
+ $block->insert();
+
+ return $block;
+ }
+
+ protected function resetTables() {
+ $this->db->delete( 'ipblocks', '*', __METHOD__ );
+ $this->db->delete( 'ipblocks_restrictions', '*', __METHOD__ );
+ }
+}