465 lines
13 KiB
PHP
465 lines
13 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Block\BlockActionInfo;
|
|
use MediaWiki\Block\BlockRestrictionStore;
|
|
use MediaWiki\Block\BlockUtils;
|
|
use MediaWiki\Block\DatabaseBlock;
|
|
use MediaWiki\Block\HideUserUtils;
|
|
use MediaWiki\Block\Restriction\NamespaceRestriction;
|
|
use MediaWiki\Block\Restriction\PageRestriction;
|
|
use MediaWiki\Cache\LinkBatchFactory;
|
|
use MediaWiki\CommentFormatter\RowCommentFormatter;
|
|
use MediaWiki\CommentStore\CommentStore;
|
|
use MediaWiki\Context\RequestContext;
|
|
use MediaWiki\Linker\LinkRenderer;
|
|
use MediaWiki\MainConfigNames;
|
|
use MediaWiki\Pager\BlockListPager;
|
|
use MediaWiki\Permissions\SimpleAuthority;
|
|
use MediaWiki\Permissions\UltimateAuthority;
|
|
use MediaWiki\Request\FauxRequest;
|
|
use MediaWiki\SpecialPage\SpecialPageFactory;
|
|
use MediaWiki\Utils\MWTimestamp;
|
|
use Wikimedia\Rdbms\FakeResultWrapper;
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @group Database
|
|
* @coversDefaultClass \MediaWiki\Pager\BlockListPager
|
|
*/
|
|
class BlockListPagerTest extends MediaWikiIntegrationTestCase {
|
|
|
|
/** @var BlockActionInfo */
|
|
private $blockActionInfo;
|
|
|
|
/** @var BlockRestrictionStore */
|
|
private $blockRestrictionStore;
|
|
|
|
/** @var BlockUtils */
|
|
private $blockUtils;
|
|
|
|
/** @var HideUserUtils */
|
|
private $hideUserUtils;
|
|
|
|
/** @var CommentStore */
|
|
private $commentStore;
|
|
|
|
/** @var LinkRenderer */
|
|
private $linkRenderer;
|
|
|
|
/** @var LinkBatchFactory */
|
|
private $linkBatchFactory;
|
|
|
|
/** @var IConnectionProvider */
|
|
private $dbProvider;
|
|
|
|
/** @var RowCommentFormatter */
|
|
private $rowCommentFormatter;
|
|
|
|
/** @var SpecialPageFactory */
|
|
private $specialPageFactory;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
|
|
$services = $this->getServiceContainer();
|
|
$this->blockActionInfo = $services->getBlockActionInfo();
|
|
$this->blockRestrictionStore = $services->getBlockRestrictionStore();
|
|
$this->blockUtils = $services->getBlockUtils();
|
|
$this->hideUserUtils = $services->getHideUserUtils();
|
|
$this->commentStore = $services->getCommentStore();
|
|
$this->linkBatchFactory = $services->getLinkBatchFactory();
|
|
$this->linkRenderer = $services->getLinkRenderer();
|
|
$this->dbProvider = $services->getConnectionProvider();
|
|
$this->rowCommentFormatter = $services->getRowCommentFormatter();
|
|
$this->specialPageFactory = $services->getSpecialPageFactory();
|
|
}
|
|
|
|
private function getBlockListPager() {
|
|
return new BlockListPager(
|
|
RequestContext::getMain(),
|
|
$this->blockActionInfo,
|
|
$this->blockRestrictionStore,
|
|
$this->blockUtils,
|
|
$this->hideUserUtils,
|
|
$this->commentStore,
|
|
$this->linkBatchFactory,
|
|
$this->linkRenderer,
|
|
$this->dbProvider,
|
|
$this->rowCommentFormatter,
|
|
$this->specialPageFactory,
|
|
[]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @covers ::formatValue
|
|
* @dataProvider formatValueEmptyProvider
|
|
* @dataProvider formatValueDefaultProvider
|
|
*/
|
|
public function testFormatValue( $name, $expected, $row ) {
|
|
// Set the time to now so it does not get off during the test.
|
|
MWTimestamp::setFakeTime( '20230405060708' );
|
|
|
|
$value = $row->$name ?? null;
|
|
|
|
if ( $name === 'bl_timestamp' ) {
|
|
// Wrap the expected timestamp in a string with the timestamp in the format
|
|
// used by the BlockListPager.
|
|
$linkRenderer = $this->getServiceContainer()->getLinkRenderer();
|
|
$link = $linkRenderer->makeKnownLink(
|
|
$this->specialPageFactory->getTitleForAlias( 'BlockList' ),
|
|
MWTimestamp::getInstance( $value )->format( 'H:i, j F Y' ),
|
|
[],
|
|
[ 'wpTarget' => "#{$row->bl_id}" ],
|
|
);
|
|
$expected = $link;
|
|
}
|
|
|
|
$pager = $this->getBlockListPager();
|
|
$wrappedPager = TestingAccessWrapper::newFromObject( $pager );
|
|
$wrappedPager->mCurrentRow = $row;
|
|
|
|
$formatted = $pager->formatValue( $name, $value );
|
|
$this->assertStringMatchesFormat( $expected, $formatted );
|
|
}
|
|
|
|
/**
|
|
* Test empty values.
|
|
*/
|
|
public static function formatValueEmptyProvider() {
|
|
$row = (object)[
|
|
'bl_id' => 1,
|
|
];
|
|
|
|
return [
|
|
[ 'test', 'Unable to format test', $row ],
|
|
[ 'bl_timestamp', null, $row ],
|
|
[ 'bl_expiry', 'infinite<br />0 seconds left', $row ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the default row values.
|
|
*/
|
|
public static function formatValueDefaultProvider() {
|
|
$row = (object)[
|
|
'bt_user' => 0,
|
|
'bt_user_text' => null,
|
|
'bt_address' => '127.0.0.1',
|
|
'bl_id' => 1,
|
|
'bl_by_text' => 'Admin',
|
|
'bt_auto' => 0,
|
|
'bl_anon_only' => 0,
|
|
'bl_create_account' => 1,
|
|
'bl_enable_autoblock' => 1,
|
|
'bl_deleted' => 0,
|
|
'bl_block_email' => 0,
|
|
'bl_allow_usertalk' => 0,
|
|
'bl_sitewide' => 1,
|
|
];
|
|
|
|
return [
|
|
[
|
|
'test',
|
|
'Unable to format test',
|
|
$row,
|
|
],
|
|
[
|
|
'bl_timestamp',
|
|
'20230405060708',
|
|
$row,
|
|
],
|
|
[
|
|
'bl_expiry',
|
|
'infinite<br />0 seconds left',
|
|
$row,
|
|
],
|
|
[
|
|
'by',
|
|
'<a %s><bdi>Admin</bdi></a>%s',
|
|
$row,
|
|
],
|
|
[
|
|
'params',
|
|
'<ul><li>editing (sitewide)</li>' .
|
|
'<li>account creation disabled</li><li>cannot edit own talk page</li></ul>',
|
|
$row,
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @covers ::formatValue
|
|
* @covers ::getRestrictionListHTML
|
|
*/
|
|
public function testFormatValueRestrictions() {
|
|
$this->overrideConfigValues( [
|
|
MainConfigNames::Script => '/w/index.php',
|
|
] );
|
|
|
|
$pager = $this->getBlockListPager();
|
|
|
|
$row = (object)[
|
|
'bl_id' => 0,
|
|
'bt_user' => 0,
|
|
'bl_anon_only' => 0,
|
|
'bl_enable_autoblock' => 0,
|
|
'bl_create_account' => 0,
|
|
'bl_block_email' => 0,
|
|
'bl_allow_usertalk' => 1,
|
|
'bl_sitewide' => 0,
|
|
'bl_deleted' => 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 ),
|
|
new NamespaceRestriction( 0, NS_MAIN ),
|
|
// Deleted page.
|
|
new PageRestriction( 0, 999999 ),
|
|
];
|
|
|
|
$wrappedPager = TestingAccessWrapper::newFromObject( $pager );
|
|
$wrappedPager->restrictions = $restrictions;
|
|
|
|
$formatted = $pager->formatValue( 'params', '' );
|
|
$this->assertEquals( '<ul><li>'
|
|
// FIXME: Expectation value should not be dynamic
|
|
// and must not depend on a localisation message.
|
|
// TODO: Mock the message or consider using qqx.
|
|
. wfMessage( 'blocklist-editing' )->text()
|
|
. '<ul><li>'
|
|
. wfMessage( 'blocklist-editing-page' )->text()
|
|
. '<ul><li>'
|
|
. '<a href="/wiki/Victor_Frankenstein" title="'
|
|
. $pageName
|
|
. '">'
|
|
. $pageName
|
|
. '</a></li></ul></li><li>'
|
|
. wfMessage( 'blocklist-editing-ns' )->text()
|
|
. '<ul><li>'
|
|
. '<a href="/w/index.php?title=Special:AllPages&namespace=0" title="'
|
|
. 'Special:AllPages'
|
|
. '">'
|
|
. wfMessage( 'blanknamespace' )->text()
|
|
. '</a></li></ul></li></ul></li></ul>',
|
|
$formatted
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @covers ::preprocessResults
|
|
*/
|
|
public function testPreprocessResults() {
|
|
// Test the Link Cache.
|
|
$linkCache = $this->getServiceContainer()->getLinkCache();
|
|
$wrappedlinkCache = TestingAccessWrapper::newFromObject( $linkCache );
|
|
$admin = $this->getTestSysop()->getUser();
|
|
|
|
$links = [
|
|
'User:127.0.0.1',
|
|
'User_talk:127.0.0.1',
|
|
$admin->getUserPage()->getPrefixedDBkey(),
|
|
$admin->getTalkPage()->getPrefixedDBkey(),
|
|
'Comment_link'
|
|
];
|
|
|
|
foreach ( $links as $link ) {
|
|
$this->assertNull( $wrappedlinkCache->entries->get( $link ) );
|
|
}
|
|
|
|
$row = (object)[
|
|
'bt_address' => '127.0.0.1',
|
|
'bt_user' => null,
|
|
'bt_user_text' => null,
|
|
'bl_by' => $admin->getId(),
|
|
'bl_by_text' => $admin->getName(),
|
|
'bl_sitewide' => 1,
|
|
'bl_timestamp' => $this->getDb()->timestamp( wfTimestamp( TS_MW ) ),
|
|
'bl_reason_text' => '[[Comment link]]',
|
|
'bl_reason_data' => null,
|
|
];
|
|
$pager = $this->getBlockListPager();
|
|
$pager->preprocessResults( new FakeResultWrapper( [ $row ] ) );
|
|
|
|
foreach ( $links as $link ) {
|
|
$this->assertTrue( $wrappedlinkCache->isBadLink( $link ), "Bad link [[$link]]" );
|
|
}
|
|
|
|
// Test sitewide blocks.
|
|
$row = (object)[
|
|
'bt_address' => '127.0.0.1',
|
|
'bt_user' => null,
|
|
'bt_user_text' => null,
|
|
'bl_by' => $admin->getId(),
|
|
'bl_by_text' => $admin->getName(),
|
|
'bl_sitewide' => 1,
|
|
'bl_reason_text' => '',
|
|
'bl_reason_data' => null,
|
|
];
|
|
$pager = $this->getBlockListPager();
|
|
$pager->preprocessResults( new FakeResultWrapper( [ $row ] ) );
|
|
|
|
$this->assertObjectNotHasProperty( 'bl_restrictions', $row );
|
|
|
|
$page = $this->getExistingTestPage( 'Victor Frankenstein' );
|
|
$title = $page->getTitle();
|
|
|
|
$target = '127.0.0.1';
|
|
|
|
// Test partial blocks.
|
|
$block = new DatabaseBlock( [
|
|
'address' => $target,
|
|
'by' => $this->getTestSysop()->getUser(),
|
|
'reason' => 'Parce que',
|
|
'expiry' => $this->getDb()->getInfinity(),
|
|
'sitewide' => false,
|
|
] );
|
|
$block->setRestrictions( [
|
|
new PageRestriction( 0, $page->getId() ),
|
|
] );
|
|
$blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
|
|
$blockStore->insertBlock( $block );
|
|
|
|
$pager = $this->getBlockListPager();
|
|
$result = $this->getDb()->newSelectQueryBuilder()
|
|
->queryInfo( $pager->getQueryInfo() )
|
|
->where( [ 'bl_id' => $block->getId() ] )
|
|
->caller( __METHOD__ )
|
|
->fetchResultSet();
|
|
|
|
$pager->preprocessResults( $result );
|
|
|
|
$wrappedPager = TestingAccessWrapper::newFromObject( $pager );
|
|
|
|
$restrictions = $wrappedPager->restrictions;
|
|
$this->assertIsArray( $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() );
|
|
}
|
|
|
|
/**
|
|
* T352310 regression test
|
|
* @coversNothing
|
|
*/
|
|
public function testOffset() {
|
|
if ( $this->getDb()->getType() === 'postgres' ) {
|
|
$this->markTestSkipped( "PostgreSQL fatals when the first part of " .
|
|
"the offset parameter has the wrong timestamp format" );
|
|
}
|
|
$request = new FauxRequest( [
|
|
'offset' => '20231115010645|7'
|
|
] );
|
|
RequestContext::getMain()->setRequest( $request );
|
|
$pager = $this->getBlockListPager();
|
|
$pager->getFullOutput();
|
|
$this->assertTrue( true );
|
|
}
|
|
|
|
/**
|
|
* T385765 regression test
|
|
* @coversNothing
|
|
*/
|
|
public function testAutoblockLeak() {
|
|
$sysop = $this->getTestSysop()->getUserIdentity();
|
|
$this->overrideConfigValue( MainConfigNames::UseCodexSpecialBlock, true );
|
|
// Enable block links
|
|
RequestContext::getMain()->setAuthority( new UltimateAuthority( $sysop ) );
|
|
// Don't localise
|
|
RequestContext::getMain()->setLanguage( 'qqx' );
|
|
// Create autoblock
|
|
$addr = '127.0.0.1';
|
|
$block = new DatabaseBlock( [
|
|
'address' => $addr,
|
|
'auto' => true,
|
|
] );
|
|
$block->setBlocker( $sysop );
|
|
$blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
|
|
$status = $blockStore->insertBlock( $block );
|
|
$this->assertNotFalse( $status );
|
|
// Run the pager over all blocks (there should only be one)
|
|
$pager = $this->getBlockListPager();
|
|
$body = $pager->getBody();
|
|
// Check that we managed to generate a remove link
|
|
$this->assertStringContainsString( '(unblocklink)', $body );
|
|
// Check that we didn't leak the IP address into it
|
|
$this->assertStringNotContainsString( $addr, $body );
|
|
}
|
|
|
|
/**
|
|
* @param string $expected
|
|
* @param string $actual
|
|
*/
|
|
private function assertStringNotContainsStringIgnoringPunctuation( $expected, $actual ) {
|
|
$this->assertStringNotContainsString( $expected, $actual );
|
|
// Fail even if punctuation in the name was replaced
|
|
$regex = '/' . preg_replace( '/[^A-Za-z0-9]+/', '.+', $expected ) . '/';
|
|
$this->assertDoesNotMatchRegularExpression( $regex, $actual );
|
|
}
|
|
|
|
/**
|
|
* T391343 regression test
|
|
* @coversNothing
|
|
*/
|
|
public function testBlockLinkSuppression() {
|
|
$user = $this->getTestUser()->getUserIdentity();
|
|
$store = $this->getServiceContainer()->getDatabaseBlockStore();
|
|
$block = new DatabaseBlock();
|
|
$block->setTarget( $user );
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
$status = $store->insertBlock( $block );
|
|
$this->assertNotFalse( $status );
|
|
$block = new DatabaseBlock( [
|
|
'hideName' => true
|
|
] );
|
|
$block->setTarget( $user );
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
$status = $store->insertBlock( $block, null );
|
|
$this->assertNotFalse( $status );
|
|
|
|
RequestContext::getMain()->setAuthority(
|
|
new SimpleAuthority(
|
|
$this->getTestSysop()->getUserIdentity(),
|
|
[ 'block' ]
|
|
)
|
|
);
|
|
|
|
$pager = $this->getBlockListPager();
|
|
$body = $pager->getBody();
|
|
$this->assertStringNotContainsStringIgnoringPunctuation( $user->getName(), $body );
|
|
}
|
|
|
|
/**
|
|
* T397595 regression test
|
|
* @coversNothing
|
|
*/
|
|
public function testAutoblockSuppression() {
|
|
$user = $this->getTestUser()->getUserIdentity();
|
|
$store = $this->getServiceContainer()->getDatabaseBlockStore();
|
|
$block = new DatabaseBlock( [
|
|
'hideName' => true,
|
|
'enableAutoblock' => true,
|
|
] );
|
|
$block->setTarget( $user );
|
|
$block->setBlocker( $this->getTestSysop()->getUser() );
|
|
$status = $store->insertBlock( $block );
|
|
$this->assertNotFalse( $status );
|
|
$store->doAutoblock( $block, '127.0.0.42' );
|
|
|
|
$pager = $this->getBlockListPager();
|
|
$body = $pager->getBody();
|
|
$this->assertStringNotContainsStringIgnoringPunctuation( $user->getName(), $body );
|
|
}
|
|
}
|