3 use MediaWiki\Block\BlockRestrictionStore
;
4 use MediaWiki\Block\DatabaseBlock
;
5 use MediaWiki\Block\Restriction\PageRestriction
;
6 use MediaWiki\Block\Restriction\NamespaceRestriction
;
7 use Wikimedia\TestingAccessWrapper
;
8 use Wikimedia\Rdbms\LoadBalancer
;
13 * @coversDefaultClass SpecialBlock
15 class SpecialBlockTest
extends SpecialPageTestBase
{
19 protected function newSpecialPage() {
20 return new SpecialBlock();
23 public function tearDown() {
29 * @covers ::getFormFields()
31 public function testGetFormFields() {
32 $this->setMwGlobals( [
33 'wgEnablePartialBlocks' => false,
34 'wgBlockAllowsUTEdit' => true,
36 $page = $this->newSpecialPage();
37 $wrappedPage = TestingAccessWrapper
::newFromObject( $page );
38 $fields = $wrappedPage->getFormFields();
39 $this->assertInternalType( 'array', $fields );
40 $this->assertArrayHasKey( 'Target', $fields );
41 $this->assertArrayHasKey( 'Expiry', $fields );
42 $this->assertArrayHasKey( 'Reason', $fields );
43 $this->assertArrayHasKey( 'CreateAccount', $fields );
44 $this->assertArrayHasKey( 'DisableUTEdit', $fields );
45 $this->assertArrayHasKey( 'AutoBlock', $fields );
46 $this->assertArrayHasKey( 'HardBlock', $fields );
47 $this->assertArrayHasKey( 'PreviousTarget', $fields );
48 $this->assertArrayHasKey( 'Confirm', $fields );
50 $this->assertArrayNotHasKey( 'EditingRestriction', $fields );
51 $this->assertArrayNotHasKey( 'PageRestrictions', $fields );
52 $this->assertArrayNotHasKey( 'NamespaceRestrictions', $fields );
56 * @covers ::getFormFields()
58 public function testGetFormFieldsPartialBlocks() {
59 $this->setMwGlobals( [
60 'wgEnablePartialBlocks' => true,
62 $page = $this->newSpecialPage();
63 $wrappedPage = TestingAccessWrapper
::newFromObject( $page );
64 $fields = $wrappedPage->getFormFields();
66 $this->assertArrayHasKey( 'EditingRestriction', $fields );
67 $this->assertArrayHasKey( 'PageRestrictions', $fields );
68 $this->assertArrayHasKey( 'NamespaceRestrictions', $fields );
72 * @covers ::maybeAlterFormDefaults()
74 public function testMaybeAlterFormDefaults() {
75 $this->setMwGlobals( [
76 'wgEnablePartialBlocks' => false,
77 'wgBlockAllowsUTEdit' => true,
80 $block = $this->insertBlock();
82 // Refresh the block from the database.
83 $block = DatabaseBlock
::newFromTarget( $block->getTarget() );
85 $page = $this->newSpecialPage();
87 $wrappedPage = TestingAccessWrapper
::newFromObject( $page );
88 $wrappedPage->target
= $block->getTarget();
89 $fields = $wrappedPage->getFormFields();
91 $this->assertSame( (string)$block->getTarget(), $fields['Target']['default'] );
92 $this->assertSame( $block->isHardblock(), $fields['HardBlock']['default'] );
93 $this->assertSame( $block->isCreateAccountBlocked(), $fields['CreateAccount']['default'] );
94 $this->assertSame( $block->isAutoblocking(), $fields['AutoBlock']['default'] );
95 $this->assertSame( !$block->isUsertalkEditAllowed(), $fields['DisableUTEdit']['default'] );
96 $this->assertSame( $block->getReason(), $fields['Reason']['default'] );
97 $this->assertSame( 'infinite', $fields['Expiry']['default'] );
101 * @covers ::maybeAlterFormDefaults()
103 public function testMaybeAlterFormDefaultsPartial() {
104 $this->setMwGlobals( [
105 'wgEnablePartialBlocks' => true,
108 $badActor = $this->getTestUser()->getUser();
109 $sysop = $this->getTestSysop()->getUser();
110 $pageSaturn = $this->getExistingTestPage( 'Saturn' );
111 $pageMars = $this->getExistingTestPage( 'Mars' );
113 $block = new DatabaseBlock( [
114 'address' => $badActor->getName(),
115 'user' => $badActor->getId(),
116 'by' => $sysop->getId(),
117 'expiry' => 'infinity',
119 'enableAutoblock' => true,
122 $block->setRestrictions( [
123 new PageRestriction( 0, $pageSaturn->getId() ),
124 new PageRestriction( 0, $pageMars->getId() ),
125 new NamespaceRestriction( 0, NS_TALK
),
127 new PageRestriction( 0, 999999 ),
132 // Refresh the block from the database.
133 $block = DatabaseBlock
::newFromTarget( $block->getTarget() );
135 $page = $this->newSpecialPage();
137 $wrappedPage = TestingAccessWrapper
::newFromObject( $page );
138 $wrappedPage->target
= $block->getTarget();
139 $fields = $wrappedPage->getFormFields();
142 $pageMars->getTitle()->getPrefixedText(),
143 $pageSaturn->getTitle()->getPrefixedText(),
146 $this->assertSame( (string)$block->getTarget(), $fields['Target']['default'] );
147 $this->assertSame( 'partial', $fields['EditingRestriction']['default'] );
148 $this->assertSame( implode( "\n", $titles ), $fields['PageRestrictions']['default'] );
152 * @covers ::processForm()
154 public function testProcessForm() {
155 $this->setMwGlobals( [
156 'wgEnablePartialBlocks' => false,
158 $badActor = $this->getTestUser()->getUser();
159 $context = RequestContext
::getMain();
161 $page = $this->newSpecialPage();
163 $expiry = 'infinity';
165 'Target' => (string)$badActor,
166 'Expiry' => 'infinity',
171 'CreateAccount' => '0',
172 'DisableUTEdit' => '0',
173 'DisableEmail' => '0',
179 $result = $page->processForm( $data, $context );
181 $this->assertTrue( $result );
183 $block = DatabaseBlock
::newFromTarget( $badActor );
184 $this->assertSame( $reason, $block->getReason() );
185 $this->assertSame( $expiry, $block->getExpiry() );
189 * @covers ::processForm()
191 public function testProcessFormExisting() {
192 $this->setMwGlobals( [
193 'wgEnablePartialBlocks' => false,
195 $badActor = $this->getTestUser()->getUser();
196 $sysop = $this->getTestSysop()->getUser();
197 $context = RequestContext
::getMain();
199 // Create a block that will be updated.
200 $block = new DatabaseBlock( [
201 'address' => $badActor->getName(),
202 'user' => $badActor->getId(),
203 'by' => $sysop->getId(),
204 'expiry' => 'infinity',
206 'enableAutoblock' => false,
210 $page = $this->newSpecialPage();
212 $expiry = 'infinity';
214 'Target' => (string)$badActor,
215 'Expiry' => 'infinity',
220 'CreateAccount' => '0',
221 'DisableUTEdit' => '0',
222 'DisableEmail' => '0',
228 $result = $page->processForm( $data, $context );
230 $this->assertTrue( $result );
232 $block = DatabaseBlock
::newFromTarget( $badActor );
233 $this->assertSame( $reason, $block->getReason() );
234 $this->assertSame( $expiry, $block->getExpiry() );
235 $this->assertSame( '1', $block->isAutoblocking() );
239 * @covers ::processForm()
241 public function testProcessFormRestrictions() {
242 $this->setMwGlobals( [
243 'wgEnablePartialBlocks' => true,
245 $badActor = $this->getTestUser()->getUser();
246 $context = RequestContext
::getMain();
248 $pageSaturn = $this->getExistingTestPage( 'Saturn' );
249 $pageMars = $this->getExistingTestPage( 'Mars' );
252 $pageSaturn->getTitle()->getText(),
253 $pageMars->getTitle()->getText(),
256 $page = $this->newSpecialPage();
258 $expiry = 'infinity';
260 'Target' => (string)$badActor,
261 'Expiry' => 'infinity',
266 'CreateAccount' => '0',
267 'DisableUTEdit' => '0',
268 'DisableEmail' => '0',
273 'EditingRestriction' => 'partial',
274 'PageRestrictions' => implode( "\n", $titles ),
275 'NamespaceRestrictions' => '',
277 $result = $page->processForm( $data, $context );
279 $this->assertTrue( $result );
281 $block = DatabaseBlock
::newFromTarget( $badActor );
282 $this->assertSame( $reason, $block->getReason() );
283 $this->assertSame( $expiry, $block->getExpiry() );
284 $this->assertCount( 2, $block->getRestrictions() );
285 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [
286 new PageRestriction( $block->getId(), $pageMars->getId() ),
287 new PageRestriction( $block->getId(), $pageSaturn->getId() ),
292 * @covers ::processForm()
294 public function testProcessFormRestrictionsChange() {
295 $this->setMwGlobals( [
296 'wgEnablePartialBlocks' => true,
298 $badActor = $this->getTestUser()->getUser();
299 $context = RequestContext
::getMain();
301 $pageSaturn = $this->getExistingTestPage( 'Saturn' );
302 $pageMars = $this->getExistingTestPage( 'Mars' );
305 $pageSaturn->getTitle()->getText(),
306 $pageMars->getTitle()->getText(),
309 // Create a partial block.
310 $page = $this->newSpecialPage();
312 $expiry = 'infinity';
314 'Target' => (string)$badActor,
315 'Expiry' => 'infinity',
320 'CreateAccount' => '0',
321 'DisableUTEdit' => '0',
322 'DisableEmail' => '0',
327 'EditingRestriction' => 'partial',
328 'PageRestrictions' => implode( "\n", $titles ),
329 'NamespaceRestrictions' => '',
331 $result = $page->processForm( $data, $context );
333 $this->assertTrue( $result );
335 $block = DatabaseBlock
::newFromTarget( $badActor );
336 $this->assertSame( $reason, $block->getReason() );
337 $this->assertSame( $expiry, $block->getExpiry() );
338 $this->assertFalse( $block->isSitewide() );
339 $this->assertCount( 2, $block->getRestrictions() );
340 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [
341 new PageRestriction( $block->getId(), $pageMars->getId() ),
342 new PageRestriction( $block->getId(), $pageSaturn->getId() ),
345 // Remove a page from the partial block.
346 $data['PageRestrictions'] = $pageMars->getTitle()->getText();
347 $result = $page->processForm( $data, $context );
349 $this->assertTrue( $result );
351 $block = DatabaseBlock
::newFromTarget( $badActor );
352 $this->assertSame( $reason, $block->getReason() );
353 $this->assertSame( $expiry, $block->getExpiry() );
354 $this->assertFalse( $block->isSitewide() );
355 $this->assertCount( 1, $block->getRestrictions() );
356 $this->assertTrue( $this->getBlockRestrictionStore()->equals( $block->getRestrictions(), [
357 new PageRestriction( $block->getId(), $pageMars->getId() ),
360 // Remove the last page from the block.
361 $data['PageRestrictions'] = '';
362 $result = $page->processForm( $data, $context );
364 $this->assertTrue( $result );
366 $block = DatabaseBlock
::newFromTarget( $badActor );
367 $this->assertSame( $reason, $block->getReason() );
368 $this->assertSame( $expiry, $block->getExpiry() );
369 $this->assertFalse( $block->isSitewide() );
370 $this->assertCount( 0, $block->getRestrictions() );
372 // Change to sitewide.
373 $data['EditingRestriction'] = 'sitewide';
374 $result = $page->processForm( $data, $context );
376 $this->assertTrue( $result );
378 $block = DatabaseBlock
::newFromTarget( $badActor );
379 $this->assertSame( $reason, $block->getReason() );
380 $this->assertSame( $expiry, $block->getExpiry() );
381 $this->assertTrue( $block->isSitewide() );
382 $this->assertCount( 0, $block->getRestrictions() );
384 // Ensure that there are no restrictions where the blockId is 0.
385 $count = $this->db
->selectRowCount(
386 'ipblocks_restrictions',
388 [ 'ir_ipb_id' => 0 ],
391 $this->assertSame( 0, $count );
395 * @dataProvider provideProcessFormErrors
396 * @covers ::processForm()
398 public function testProcessFormErrors( $data, $expected, $config = [] ) {
400 'wgEnablePartialBlocks' => true,
401 'wgBlockAllowsUTEdit' => true,
404 $this->setMwGlobals( array_merge( $defaultConfig, $config ) );
407 'Target' => '1.2.3.4',
408 'Expiry' => 'infinity',
409 'Reason' => [ 'bad reason' ],
411 'PageRestrictions' => '',
412 'NamespaceRestrictions' => '',
415 $context = RequestContext
::getMain();
416 $page = $this->newSpecialPage();
417 $result = $page->processForm( array_merge( $defaultData, $data ), $context );
419 $this->assertEquals( $result[0], $expected );
422 public function provideProcessFormErrors() {
424 'Invalid expiry' => [
426 'Expiry' => 'invalid',
428 'ipb_expiry_invalid',
430 'Expiry is in the past' => [
432 'Expiry' => 'yesterday',
436 'HideUser with wrong permissions' => [
442 'Bad ip address' => [
444 'Target' => '1.2.3.4/1234',
448 'Edit user talk page invalid with no restrictions' => [
450 'EditingRestriction' => 'partial',
451 'DisableUTEdit' => 1,
453 'ipb-prevent-user-talk-edit',
455 'Edit user talk page invalid with namespace restriction != NS_USER_TALK ' => [
457 'EditingRestriction' => 'partial',
458 'DisableUTEdit' => 1,
459 'NamespaceRestrictions' => NS_USER
461 'ipb-prevent-user-talk-edit',
467 * @dataProvider provideCheckUnblockSelf
468 * @covers ::checkUnblockSelf
470 public function testCheckUnblockSelf(
478 $this->setMwGlobals( [
479 'wgBlockDisablesLogin' => false,
481 $this->setGroupPermissions( 'sysop', 'unblockself', true );
482 $this->setGroupPermissions( 'user', 'block', true );
483 // Getting errors about creating users in db in provider.
484 // Need to use mutable to ensure different named testusers.
486 'u1' => TestUserRegistry
::getMutableTestUser( __CLASS__
, 'sysop' )->getUser(),
487 'u2' => TestUserRegistry
::getMutableTestUser( __CLASS__
, 'sysop' )->getUser(),
488 'u3' => TestUserRegistry
::getMutableTestUser( __CLASS__
, 'sysop' )->getUser(),
489 'u4' => TestUserRegistry
::getMutableTestUser( __CLASS__
, 'sysop' )->getUser(),
490 'nonsysop' => $this->getTestUser()->getUser()
492 foreach ( [ 'blockedUser', 'blockPerformer', 'adjustPerformer', 'adjustTarget' ] as $var ) {
493 $
$var = $users[$
$var];
496 $block = new DatabaseBlock( [
497 'address' => $blockedUser->getName(),
498 'user' => $blockedUser->getId(),
499 'by' => $blockPerformer->getId(),
500 'expiry' => 'infinity',
502 'enableAutoblock' => true,
508 SpecialBlock
::checkUnblockSelf( $adjustTarget, $adjustPerformer ),
514 public function provideCheckUnblockSelf() {
515 // 'blockedUser', 'blockPerformer', 'adjustPerformer', 'adjustTarget'
517 [ 'u1', 'u2', 'u3', 'u4', true, 'Unrelated users' ],
518 [ 'u1', 'u2', 'u1', 'u4', 'ipbblocked', 'Block unrelated while blocked' ],
519 [ 'u1', 'u2', 'u1', 'u1', true, 'Has unblockself' ],
520 [ 'nonsysop', 'u2', 'nonsysop', 'nonsysop', 'ipbnounblockself', 'no unblockself' ],
521 [ 'nonsysop', 'nonsysop', 'nonsysop', 'nonsysop', true, 'no unblockself but can de-selfblock' ],
522 [ 'u1', 'u2', 'u1', 'u2', true, 'Can block user who blocked' ],
526 protected function insertBlock() {
527 $badActor = $this->getTestUser()->getUser();
528 $sysop = $this->getTestSysop()->getUser();
530 $block = new DatabaseBlock( [
531 'address' => $badActor->getName(),
532 'user' => $badActor->getId(),
533 'by' => $sysop->getId(),
534 'expiry' => 'infinity',
536 'enableAutoblock' => true,
544 protected function resetTables() {
545 $this->db
->delete( 'ipblocks', '*', __METHOD__
);
546 $this->db
->delete( 'ipblocks_restrictions', '*', __METHOD__
);
550 * Get a BlockRestrictionStore instance
552 * @return BlockRestrictionStore
554 private function getBlockRestrictionStore() : BlockRestrictionStore
{
555 $loadBalancer = $this->getMockBuilder( LoadBalancer
::class )
556 ->disableOriginalConstructor()
559 return new BlockRestrictionStore( $loadBalancer );