Previously, a manually constructed autocomment in the form of /* [[Some link]] */ would create a link to the the section, and then the "Some link" page. After T165189 was implemented, the entire autocomment is now a link to the section, so we're creating links inside of a link...which is problematic. In most contexts (history and watchlist particularly), the section link is more important than the title in the section heading, so that's what we'll favor here. It's worth noting that this situation is a manually created edge case. Even if the section heading is a wikilink, the edit summary will autofill a section autocomment without the double brackets. We'll now render the double brackets ([[...]]) and not link them. This is what the user literally typed, and matches the existing practice of rendering templates in section headings with their literal syntax. And as a bonus, it's still possible for user scripts such as wikEdDiff to turn the rendered double brackets into a real link if users want. Bug: T165189 Change-Id: Ib10679edd76c72a60d7e1c89fc8454166e34c463
436 lines
14 KiB
PHP
436 lines
14 KiB
PHP
<?php
|
||
|
||
|
||
/**
|
||
* @group Database
|
||
*/
|
||
class LinkerTest extends MediaWikiLangTestCase {
|
||
|
||
/**
|
||
* @dataProvider provideCasesForUserLink
|
||
* @covers Linker::userLink
|
||
*/
|
||
public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) {
|
||
$this->setMwGlobals( [
|
||
'wgArticlePath' => '/wiki/$1',
|
||
] );
|
||
|
||
$this->assertEquals(
|
||
$expected,
|
||
Linker::userLink( $userId, $userName, $altUserName ),
|
||
$msg
|
||
);
|
||
}
|
||
|
||
public static function provideCasesForUserLink() {
|
||
# Format:
|
||
# - expected
|
||
# - userid
|
||
# - username
|
||
# - optional altUserName
|
||
# - optional message
|
||
return [
|
||
|
||
# ## ANONYMOUS USER ########################################
|
||
[
|
||
'<a href="/wiki/Special:Contributions/JohnDoe" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/JohnDoe"><bdi>JohnDoe</bdi></a>',
|
||
0, 'JohnDoe', false,
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:Contributions/::1" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/::1"><bdi>::1</bdi></a>',
|
||
0, '::1', false,
|
||
'Anonymous with pretty IPv6'
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/0:0:0:0:0:0:0:1"><bdi>::1</bdi></a>',
|
||
0, '0:0:0:0:0:0:0:1', false,
|
||
'Anonymous with almost pretty IPv6'
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001"><bdi>::1</bdi></a>',
|
||
0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
|
||
'Anonymous with full IPv6'
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:Contributions/::1" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/::1"><bdi>AlternativeUsername</bdi></a>',
|
||
0, '::1', 'AlternativeUsername',
|
||
'Anonymous with pretty IPv6 and an alternative username'
|
||
],
|
||
|
||
# IPV4
|
||
[
|
||
'<a href="/wiki/Special:Contributions/127.0.0.1" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/127.0.0.1"><bdi>127.0.0.1</bdi></a>',
|
||
0, '127.0.0.1', false,
|
||
'Anonymous with IPv4'
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:Contributions/127.0.0.1" '
|
||
. 'class="mw-userlink mw-anonuserlink" '
|
||
. 'title="Special:Contributions/127.0.0.1"><bdi>AlternativeUsername</bdi></a>',
|
||
0, '127.0.0.1', 'AlternativeUsername',
|
||
'Anonymous with IPv4 and an alternative username'
|
||
],
|
||
|
||
# ## Regular user ##########################################
|
||
# TODO!
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @dataProvider provideCasesForFormatComment
|
||
* @covers Linker::formatComment
|
||
* @covers Linker::formatAutocomments
|
||
* @covers Linker::formatLinksInComment
|
||
*/
|
||
public function testFormatComment(
|
||
$expected, $comment, $title = false, $local = false, $wikiId = null
|
||
) {
|
||
$conf = new SiteConfiguration();
|
||
$conf->settings = [
|
||
'wgServer' => [
|
||
'enwiki' => '//en.example.org',
|
||
'dewiki' => '//de.example.org',
|
||
],
|
||
'wgArticlePath' => [
|
||
'enwiki' => '/w/$1',
|
||
'dewiki' => '/w/$1',
|
||
],
|
||
];
|
||
$conf->suffixes = [ 'wiki' ];
|
||
|
||
$this->setMwGlobals( [
|
||
'wgScript' => '/wiki/index.php',
|
||
'wgArticlePath' => '/wiki/$1',
|
||
'wgCapitalLinks' => true,
|
||
'wgConf' => $conf,
|
||
] );
|
||
|
||
if ( $title === false ) {
|
||
// We need a page title that exists
|
||
$title = Title::newFromText( 'Special:BlankPage' );
|
||
}
|
||
|
||
$this->assertEquals(
|
||
$expected,
|
||
Linker::formatComment( $comment, $title, $local, $wikiId )
|
||
);
|
||
}
|
||
|
||
public function provideCasesForFormatComment() {
|
||
$wikiId = 'enwiki'; // $wgConf has a fake entry for this
|
||
|
||
// phpcs:disable Generic.Files.LineLength
|
||
return [
|
||
// Linker::formatComment
|
||
[
|
||
'a<script>b',
|
||
'a<script>b',
|
||
],
|
||
[
|
||
'a—b',
|
||
'a—b',
|
||
],
|
||
[
|
||
"'''not bolded'''",
|
||
"'''not bolded'''",
|
||
],
|
||
[
|
||
"try <script>evil</scipt> things",
|
||
"try <script>evil</scipt> things",
|
||
],
|
||
// Linker::formatAutocomments
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>',
|
||
"/* autocomment */",
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#linkie.3F" title="Special:BlankPage">→[[linkie?]]</a></span></span>',
|
||
"/* [[linkie?]] */",
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> post</span>',
|
||
"/* autocomment */ post",
|
||
],
|
||
[
|
||
'pre <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>',
|
||
"pre /* autocomment */",
|
||
],
|
||
[
|
||
'pre <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> post</span>',
|
||
"pre /* autocomment */ post",
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a>: </span> multiple? <span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment2" title="Special:BlankPage">→autocomment2</a></span></span></span>',
|
||
"/* autocomment */ multiple? /* autocomment2 */",
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.2F.2A" title="Special:BlankPage">→autocomment containing /*</a>: </span> T70361</span>',
|
||
"/* autocomment containing /* */ T70361"
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.22quotes.22" title="Special:BlankPage">→autocomment containing "quotes"</a></span></span>',
|
||
"/* autocomment containing \"quotes\" */"
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment_containing_.3Cscript.3Etags.3C.2Fscript.3E" title="Special:BlankPage">→autocomment containing <script>tags</script></a></span></span>',
|
||
"/* autocomment containing <script>tags</script> */"
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="#autocomment">→autocomment</a></span></span>',
|
||
"/* autocomment */",
|
||
false, true
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment">autocomment</span></span>',
|
||
"/* autocomment */",
|
||
null
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a href="/wiki/Special:BlankPage#autocomment" title="Special:BlankPage">→autocomment</a></span></span>',
|
||
"/* autocomment */",
|
||
false, false
|
||
],
|
||
[
|
||
'<span dir="auto"><span class="autocomment"><a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage#autocomment">→autocomment</a></span></span>',
|
||
"/* autocomment */",
|
||
false, false, $wikiId
|
||
],
|
||
// Linker::formatLinksInComment
|
||
[
|
||
'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def',
|
||
"abc [[link]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">text</a> def',
|
||
"abc [[link|text]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a> def',
|
||
"abc [[Special:BlankPage|]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/index.php?title=%C4%84%C5%9B%C5%BC&action=edit&redlink=1" class="new" title="Ąśż (page does not exist)">ąśż</a> def',
|
||
"abc [[%C4%85%C5%9B%C5%BC]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/Special:BlankPage#section" title="Special:BlankPage">#section</a> def',
|
||
"abc [[#section]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> def',
|
||
"abc [[/subpage]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/index.php?title=%22evil!%22&action=edit&redlink=1" class="new" title=""evil!" (page does not exist)">"evil!"</a> def',
|
||
"abc [[\"evil!\"]] def",
|
||
],
|
||
[
|
||
'abc [[<script>very evil</script>]] def',
|
||
"abc [[<script>very evil</script>]] def",
|
||
],
|
||
[
|
||
'abc [[|]] def',
|
||
"abc [[|]] def",
|
||
],
|
||
[
|
||
'abc <a href="/wiki/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">link</a> def',
|
||
"abc [[link]] def",
|
||
false, false
|
||
],
|
||
[
|
||
'abc <a class="external" rel="nofollow" href="//en.example.org/w/Link">link</a> def',
|
||
"abc [[link]] def",
|
||
false, false, $wikiId
|
||
],
|
||
];
|
||
// phpcs:enable
|
||
}
|
||
|
||
/**
|
||
* @covers Linker::formatLinksInComment
|
||
* @dataProvider provideCasesForFormatLinksInComment
|
||
*/
|
||
public function testFormatLinksInComment( $expected, $input, $wiki ) {
|
||
$conf = new SiteConfiguration();
|
||
$conf->settings = [
|
||
'wgServer' => [
|
||
'enwiki' => '//en.example.org'
|
||
],
|
||
'wgArticlePath' => [
|
||
'enwiki' => '/w/$1',
|
||
],
|
||
];
|
||
$conf->suffixes = [ 'wiki' ];
|
||
$this->setMwGlobals( [
|
||
'wgScript' => '/wiki/index.php',
|
||
'wgArticlePath' => '/wiki/$1',
|
||
'wgCapitalLinks' => true,
|
||
'wgConf' => $conf,
|
||
] );
|
||
|
||
$this->assertEquals(
|
||
$expected,
|
||
Linker::formatLinksInComment( $input, Title::newFromText( 'Special:BlankPage' ), false, $wiki )
|
||
);
|
||
}
|
||
|
||
public static function provideCasesForFormatLinksInComment() {
|
||
// phpcs:disable Generic.Files.LineLength
|
||
return [
|
||
[
|
||
'foo bar <a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>',
|
||
'foo bar [[Special:BlankPage]]',
|
||
null,
|
||
],
|
||
[
|
||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>',
|
||
'[[ :Special:BlankPage]]',
|
||
null,
|
||
],
|
||
[
|
||
'<a class="external" rel="nofollow" href="//en.example.org/w/Foo%27bar">Foo\'bar</a>',
|
||
"[[Foo'bar]]",
|
||
'enwiki',
|
||
],
|
||
[
|
||
'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/Special:BlankPage">Special:BlankPage</a>',
|
||
'foo bar [[Special:BlankPage]]',
|
||
'enwiki',
|
||
],
|
||
[
|
||
'foo bar <a class="external" rel="nofollow" href="//en.example.org/w/File:Example">Image:Example</a>',
|
||
'foo bar [[Image:Example]]',
|
||
'enwiki',
|
||
],
|
||
];
|
||
// phpcs:enable
|
||
}
|
||
|
||
public static function provideLinkBeginHook() {
|
||
// phpcs:disable Generic.Files.LineLength
|
||
return [
|
||
// Modify $html
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$html = 'foobar';
|
||
},
|
||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
|
||
],
|
||
// Modify $attribs
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$attribs['bar'] = 'baz';
|
||
},
|
||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
|
||
],
|
||
// Modify $query
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$query['bar'] = 'baz';
|
||
},
|
||
'<a href="/w/index.php?title=Special:BlankPage&bar=baz" title="Special:BlankPage">Special:BlankPage</a>'
|
||
],
|
||
// Force HTTP $options
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$options = [ 'http' ];
|
||
},
|
||
'<a href="http://example.org/wiki/Special:BlankPage" title="Special:BlankPage">Special:BlankPage</a>'
|
||
],
|
||
// Force 'forcearticlepath' in $options
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$options = [ 'forcearticlepath' ];
|
||
$query['foo'] = 'bar';
|
||
},
|
||
'<a href="/wiki/Special:BlankPage?foo=bar" title="Special:BlankPage">Special:BlankPage</a>'
|
||
],
|
||
// Abort early
|
||
[
|
||
function ( $dummy, $title, &$html, &$attribs, &$query, &$options, &$ret ) {
|
||
$ret = 'foobar';
|
||
return false;
|
||
},
|
||
'foobar'
|
||
],
|
||
];
|
||
// phpcs:enable
|
||
}
|
||
|
||
/**
|
||
* @covers MediaWiki\Linker\LinkRenderer::runLegacyBeginHook
|
||
* @dataProvider provideLinkBeginHook
|
||
*/
|
||
public function testLinkBeginHook( $callback, $expected ) {
|
||
$this->hideDeprecated( 'LinkBegin hook (used in hook-LinkBegin-closure)' );
|
||
$this->setMwGlobals( [
|
||
'wgArticlePath' => '/wiki/$1',
|
||
'wgServer' => '//example.org',
|
||
'wgCanonicalServer' => 'http://example.org',
|
||
'wgScriptPath' => '/w',
|
||
'wgScript' => '/w/index.php',
|
||
] );
|
||
|
||
$this->setMwGlobals( 'wgHooks', [ 'LinkBegin' => [ $callback ] ] );
|
||
$title = SpecialPage::getTitleFor( 'Blankpage' );
|
||
$out = Linker::link( $title );
|
||
$this->assertEquals( $expected, $out );
|
||
}
|
||
|
||
public static function provideLinkEndHook() {
|
||
return [
|
||
// Override $html
|
||
[
|
||
function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
|
||
$html = 'foobar';
|
||
},
|
||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage">foobar</a>'
|
||
],
|
||
// Modify $attribs
|
||
[
|
||
function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
|
||
$attribs['bar'] = 'baz';
|
||
},
|
||
'<a href="/wiki/Special:BlankPage" title="Special:BlankPage" bar="baz">Special:BlankPage</a>'
|
||
],
|
||
// Fully override return value and abort hook
|
||
[
|
||
function ( $dummy, $title, $options, &$html, &$attribs, &$ret ) {
|
||
$ret = 'blahblahblah';
|
||
return false;
|
||
},
|
||
'blahblahblah'
|
||
],
|
||
|
||
];
|
||
}
|
||
|
||
/**
|
||
* @covers MediaWiki\Linker\LinkRenderer::buildAElement
|
||
* @dataProvider provideLinkEndHook
|
||
*/
|
||
public function testLinkEndHook( $callback, $expected ) {
|
||
$this->hideDeprecated( 'LinkEnd hook (used in hook-LinkEnd-closure)' );
|
||
$this->setMwGlobals( [
|
||
'wgArticlePath' => '/wiki/$1',
|
||
] );
|
||
|
||
$this->setMwGlobals( 'wgHooks', [ 'LinkEnd' => [ $callback ] ] );
|
||
|
||
$title = SpecialPage::getTitleFor( 'Blankpage' );
|
||
$out = Linker::link( $title );
|
||
$this->assertEquals( $expected, $out );
|
||
}
|
||
}
|