createNoOpMock( RepoGroup::class, [ 'findFiles' ] );
$repoGroup->method( 'findFiles' )->willReturn( [] );
return $repoGroup;
}
private function getParser() {
$services = $this->getServiceContainer();
return new CommentParser(
$services->getLinkRenderer(),
$services->getLinkBatchFactory(),
$services->getLinkCache(),
$this->getRepoGroup(),
$services->getContentLanguage(),
$services->getContentLanguage(),
$services->getTitleParser(),
$services->getNamespaceInfo(),
$services->getHookContainer()
);
}
private function getFormatter() {
$parserFactory = $this->createNoOpMock( CommentParserFactory::class, [ 'create' ] );
$parserFactory->method( 'create' )->willReturnCallback( function () {
return $this->getParser();
} );
return new CommentFormatter( $parserFactory );
}
/**
* @before
*/
public function interwikiSetUp() {
$this->setService( 'InterwikiLookup', function () {
return $this->getDummyInterwikiLookup( [
'interwiki' => [
'iw_prefix' => 'interwiki',
'iw_url' => 'https://interwiki/$1',
]
] );
} );
}
/**
* @before
*/
public function configSetUp() {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'foowiki' => '//foo.example.org'
],
'wgArticlePath' => [
'foowiki' => '/foo/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/w/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
MainConfigNames::LanguageCode => 'en',
] );
}
public static function provideFormatComment() {
return [
// MediaWiki\CommentFormatter\CommentFormatter::format
[
'a<script>b',
'a */"
],
[
'',
"/* autocomment */",
false, true
],
[
'',
"/* autocomment */",
null
],
[
'',
"/* */",
false, true
],
[
'',
"/* */",
null
],
[
'',
"/* [[ */",
false, true
],
[
'',
"/* [[ */",
null
],
[
"foo ",
"foo /* [[#_\t_]] */",
false, true
],
[
"foo ",
"foo /* [[#_\t_]] */",
null
],
[
'',
"/* autocomment */",
false, false
],
[
'',
"/* autocomment */",
false, false, 'foowiki'
],
// MediaWiki\CommentFormatter\CommentParser::doWikiLinks
[
'abc link def',
"abc [[link]] def",
],
[
'abc text def',
"abc [[link|text]] def",
],
[
'abc Special:BlankPage def',
"abc [[Special:BlankPage|]] def",
],
[
'abc ąśż def',
"abc [[%C4%85%C5%9B%C5%BC]] def",
],
[
'abc #section def',
"abc [[#section]] def",
],
[
'abc /subpage def',
"abc [[/subpage]] def",
],
[
'abc "evil!" def',
"abc [[\"evil!\"]] def",
],
[
'abc [[<script>very evil</script>]] def',
"abc [[]] def",
],
[
'abc [[|]] def',
"abc [[|]] def",
],
[
'abc link def',
"abc [[link]] def",
false, false
],
[
'abc link def',
"abc [[link]] def",
false, false, 'foowiki'
],
[
'Media:LinkerTest.jpg',
'[[Media:LinkerTest.jpg]]'
],
[
'Special:BlankPage',
'[[:Special:BlankPage]]'
],
[
'linktrail...',
'[[link]]trail...'
],
[
'Present',
'[[Present]]',
],
[
'interwiki:Some page',
'[[interwiki:Some page]]',
],
[
'interwiki:Present Present',
'[[interwiki:Present]] [[Present]]'
]
];
// phpcs:enable
}
/**
* @dataProvider provideFormatComment
*/
public function testFormatComment(
$expected, $comment, $title = false, $local = false, $wikiId = null
) {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'foowiki' => '//foo.example.org',
],
'wgArticlePath' => [
'foowiki' => '/foo/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/w/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
// TODO: update tests when the default changes
MainConfigNames::FragmentMode => [ 'legacy' ],
MainConfigNames::LanguageCode => 'en',
] );
$this->addGoodLinkObject( 1, Title::makeTitle( NS_MAIN, 'Present' ) );
if ( $title === false ) {
// We need a page title that exists
$title = Title::makeTitle( NS_SPECIAL, 'BlankPage' );
}
$parser = $this->getParser();
$result = $parser->finalize(
$parser->preprocess(
$comment,
$title,
$local,
$wikiId
)
);
$this->assertEquals( $expected, $result );
}
public static function provideFormatLinksInComment() {
return [
[
'foo bar Special:BlankPage',
'foo bar [[Special:BlankPage]]',
null,
],
[
'Special:BlankPage',
'[[ :Special:BlankPage]]',
null,
],
[
'[[FooSpecial:BlankPage',
'[[Foo[[Special:BlankPage]]',
null,
],
[
'Foo'bar',
"[[Foo'bar]]",
'foowiki',
],
[
'Foo$100bar',
'[[Foo$100bar]]',
'foowiki',
],
[
'foo bar Special:BlankPage',
'foo bar [[Special:BlankPage]]',
'foowiki',
],
[
'foo bar Image:Example',
'foo bar [[Image:Example]]',
'foowiki',
],
];
// phpcs:enable
}
/**
* @covers \MediaWiki\CommentFormatter\CommentFormatter
* @covers \MediaWiki\CommentFormatter\CommentParser
* @dataProvider provideCommentBlock
*/
public function testCommentBlock(
$expected, $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
) {
$conf = new SiteConfiguration();
$conf->settings = [
'wgServer' => [
'foowiki' => '//foo.example.org'
],
'wgArticlePath' => [
'foowiki' => '/foo/$1',
],
];
$conf->suffixes = [ 'wiki' ];
$this->setMwGlobals( 'wgConf', $conf );
$this->overrideConfigValues( [
MainConfigNames::Script => '/w/index.php',
MainConfigNames::ArticlePath => '/wiki/$1',
MainConfigNames::CapitalLinks => true,
] );
$formatter = $this->getFormatter();
$this->assertEquals(
$expected,
$formatter->formatBlock( $comment, $title, $local, $wikiId, $useParentheses )
);
}
public static function provideCommentBlock() {
return [
[
' ',
'Test'
],
'Empty comment' => [ '', '' ],
'Backwards compatibility empty comment' => [ '', '*' ],
'No parenthesis' => [
' ',
'Test',
null, false, null,
false
],
'Page exist link' => [
' ',
'[[Special:BlankPage]]'
],
'Page does not exist link' => [
' ',
'[[Test]]'
],
'Link to other page section' => [
' ',
'[[#Test]]',
Title::makeTitle( NS_SPECIAL, 'BlankPage' )
],
'$local is true' => [
' ',
'[[#Test]]',
Title::makeTitle( NS_SPECIAL, 'BlankPage' ),
true
],
'Given wikiId' => [
' ',
'[[Test]]',
null, false,
'foowiki'
],
'Section link to external wiki page' => [
' ',
'[[#Test]]',
Title::makeTitle( NS_SPECIAL, 'BlankPage' ),
false,
'foowiki'
],
];
}
/**
* Note that we test the new HTML escaping variant.
*
* @dataProvider provideFormatLinksInComment
*/
public function testFormatLinksInComment( $expected, $input, $wiki ) {
$parser = $this->getParser();
$title = Title::makeTitle( NS_SPECIAL, 'BlankPage' );
$result = $parser->finalize(
$parser->preprocess(
$input, $title, false, $wiki, false
)
);
$this->assertEquals( $expected, $result );
}
public function testLinkCacheInteraction() {
$services = $this->getServiceContainer();
$present = $this->getExistingTestPage( 'Present' )->getTitle();
$absent = $this->getNonexistingTestPage( 'Absent' )->getTitle();
$parser = $this->getParser();
$linkCache = $services->getLinkCache();
$result = $parser->finalize( [
$parser->preprocess( "[[$present]]" ),
$parser->preprocess( "[[$absent]]" )
] );
$expected = [
'Present',
'Absent'
];
$this->assertSame( $expected, $result );
$this->assertGreaterThan( 0, $linkCache->getGoodLinkID( $present ) );
$this->assertTrue( $linkCache->isBadLink( $absent ) );
// Run the comment batch again and confirm that LinkBatch does not need
// to execute a query. This is a CommentParser responsibility since
// LinkBatch does not provide a transparent read-through cache.
// TODO: Generic $this->assertQueryCount() would do the job.
$parser = new CommentParser(
$services->getLinkRenderer(),
$services->getLinkBatchFactory(),
$linkCache,
$this->getRepoGroup(),
$services->getContentLanguage(),
$services->getContentLanguage(),
$services->getTitleParser(),
$services->getNamespaceInfo(),
$services->getHookContainer()
);
$result = $parser->finalize( [
$parser->preprocess( "[[$present]]" ),
$parser->preprocess( "[[$absent]]" )
] );
$this->assertSame( $expected, $result );
}
/**
* Regression test for T300311
*/
public function testInterwikiLinkCachePollution() {
$present = $this->getExistingTestPage( 'Template:Present' )->getTitle();
$this->getServiceContainer()->getLinkCache()->clear();
$parser = $this->getParser();
$result = $parser->finalize(
$parser->preprocess( "[[interwiki:$present]] [[$present]]" )
);
$this->assertSame(
// phpcs:ignore Generic.Files.LineLength
"interwiki:$present $present",
$result
);
}
/**
* Regression test for T293665
*/
public function testAlwaysKnownPages() {
$this->setTemporaryHook( 'TitleIsAlwaysKnown',
static function ( $target, &$isKnown ) {
$isKnown = $target->getText() == 'AlwaysKnownFoo';
}
);
$title = Title::makeTitle( NS_USER, 'AlwaysKnownFoo' );
$this->assertFalse( $title->exists() );
$parser = $this->getParser();
$result = $parser->finalize( $parser->preprocess( 'test [[User:AlwaysKnownFoo]]' ) );
$this->assertSame(
'test User:AlwaysKnownFoo',
$result
);
}
/**
* @dataProvider provideRevComment
*/
public function testRevComment(
string $expected,
bool $isSysop = false,
int $visibility = 0,
bool $local = false,
bool $isPublic = false,
bool $useParentheses = true,
?string $comment = 'Some comment!'
) {
$pageData = $this->insertPage( 'RevCommentTestPage' );
$revisionRecord = new MutableRevisionRecord( $pageData['title'] );
if ( $comment ) {
$revisionRecord->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
}
$revisionRecord->setVisibility( $visibility );
$context = RequestContext::getMain();
$user = $isSysop ? $this->getTestSysop()->getUser() : $this->getTestUser()->getUser();
$context->setUser( $user );
$formatter = $this->getFormatter();
$authority = RequestContext::getMain()->getAuthority();
$this->assertEquals( $expected, $formatter->formatRevision( $revisionRecord, $authority, $local, $isPublic, $useParentheses ) );
}
public static function provideRevComment() {
return [
'Should be visible' => [
' '
],
'Should not have parenthesis' => [
' ',
false, 0, false, false,
false
],
'Should be empty' => [
'',
false, 0, false, false, true,
null
],
'Deleted comment should not be visible to normal users' => [
' ',
false,
RevisionRecord::DELETED_COMMENT
],
'Deleted comment should not be visible to normal users even if public' => [
' ',
false,
RevisionRecord::DELETED_COMMENT,
false,
true
],
'Deleted comment should be visible to sysops' => [
' ',
true,
RevisionRecord::DELETED_COMMENT
],
];
}
}