Add interwiki support to LinkTarget and TitleValue

This adds support to the LinkTarget interface and TitleValue
implementation for having an interwiki component, matching the function
names used in Title.

MediaWikiTitleCodec was updated accordingly.

The motivation behind this change is to be able to fully use LinkTarget
in the Linker rewrite instead of depending upon Title.

Change-Id: I6666b64f0e336aadc7261e7ca87ac2e498c61856
This commit is contained in:
Kunal Mehta 2016-04-26 19:21:59 -07:00
parent eec846227d
commit 9b1f8b4ca3
6 changed files with 118 additions and 42 deletions

View file

@ -73,4 +73,18 @@ interface LinkTarget {
* @return LinkTarget
*/
public function createFragmentTarget( $fragment );
/**
* Whether this LinkTarget has an interwiki component
*
* @return bool
*/
public function isExternal();
/**
* The interwiki component of this LinkTarget
*
* @return string
*/
public function getInterwiki();
}

View file

@ -98,11 +98,12 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
* @param string $text The page title. Should be valid. Only minimal normalization is applied.
* Underscores will be replaced.
* @param string $fragment The fragment name (may be empty).
* @param string $interwiki The interwiki name (may be empty).
*
* @throws InvalidArgumentException If the namespace is invalid
* @return string
*/
public function formatTitle( $namespace, $text, $fragment = '' ) {
public function formatTitle( $namespace, $text, $fragment = '', $interwiki = '' ) {
if ( $namespace !== false ) {
$namespace = $this->getNamespaceName( $namespace, $text );
@ -115,6 +116,10 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
$text = $text . '#' . $fragment;
}
if ( $interwiki !== '' ) {
$text = $interwiki . ':' . $text;
}
$text = str_replace( '_', ' ', $text );
return $text;
@ -136,17 +141,17 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
// be refactored to avoid this.
$parts = $this->splitTitleString( $text, $defaultNamespace );
// Interwiki links are not supported by TitleValue
if ( $parts['interwiki'] !== '' ) {
throw new MalformedTitleException( 'title-invalid-interwiki', $text );
}
// Relative fragment links are not supported by TitleValue
if ( $parts['dbkey'] === '' ) {
throw new MalformedTitleException( 'title-invalid-empty', $text );
}
return new TitleValue( $parts['namespace'], $parts['dbkey'], $parts['fragment'] );
return new TitleValue(
$parts['namespace'],
$parts['dbkey'],
$parts['fragment'],
$parts['interwiki']
);
}
/**
@ -168,7 +173,12 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
* @return string
*/
public function getPrefixedText( LinkTarget $title ) {
return $this->formatTitle( $title->getNamespace(), $title->getText(), '' );
return $this->formatTitle(
$title->getNamespace(),
$title->getText(),
'',
$title->getInterwiki()
);
}
/**
@ -179,7 +189,12 @@ class MediaWikiTitleCodec implements TitleFormatter, TitleParser {
* @return string
*/
public function getFullText( LinkTarget $title ) {
return $this->formatTitle( $title->getNamespace(), $title->getText(), $title->getFragment() );
return $this->formatTitle(
$title->getNamespace(),
$title->getText(),
$title->getFragment(),
$title->getInterwiki()
);
}
/**

View file

@ -42,10 +42,11 @@ interface TitleFormatter {
* @param int|bool $namespace The namespace ID (or false, if the namespace should be ignored)
* @param string $text The page title
* @param string $fragment The fragment name (may be empty).
* @param string $interwiki The interwiki prefix (may be empty).
*
* @return string
*/
public function formatTitle( $namespace, $text, $fragment = '' );
public function formatTitle( $namespace, $text, $fragment = '', $interwiki = '' );
/**
* Returns the title text formatted for display, without namespace of fragment.

View file

@ -30,9 +30,6 @@ use Wikimedia\Assert\Assert;
* @note In contrast to Title, this is designed to be a plain value object. That is,
* it is immutable, does not use global state, and causes no side effects.
*
* @note TitleValue represents the title of a local page (or fragment of a page).
* It does not represent a link, and does not support interwiki prefixes etc.
*
* @see https://www.mediawiki.org/wiki/Requests_for_comment/TitleValue
* @since 1.23
*/
@ -52,6 +49,11 @@ class TitleValue implements LinkTarget {
*/
protected $fragment;
/**
* @var string
*/
protected $interwiki;
/**
* Constructs a TitleValue.
*
@ -65,13 +67,15 @@ class TitleValue implements LinkTarget {
* @param string $dbkey The page title in valid DBkey form. No normalization is applied.
* @param string $fragment The fragment title. Use '' to represent the whole page.
* No validation or normalization is applied.
* @param string $interwiki The interwiki component
*
* @throws InvalidArgumentException
*/
public function __construct( $namespace, $dbkey, $fragment = '' ) {
public function __construct( $namespace, $dbkey, $fragment = '', $interwiki = '' ) {
Assert::parameterType( 'integer', $namespace, '$namespace' );
Assert::parameterType( 'string', $dbkey, '$dbkey' );
Assert::parameterType( 'string', $fragment, '$fragment' );
Assert::parameterType( 'string', $interwiki, '$interwiki' );
// Sanity check, no full validation or normalization applied here!
Assert::parameter( !preg_match( '/^_|[ \r\n\t]|_$/', $dbkey ), '$dbkey', 'invalid DB key' );
@ -80,6 +84,7 @@ class TitleValue implements LinkTarget {
$this->namespace = $namespace;
$this->dbkey = $dbkey;
$this->fragment = $fragment;
$this->interwiki = $interwiki;
}
/**
@ -138,7 +143,32 @@ class TitleValue implements LinkTarget {
* @return TitleValue
*/
public function createFragmentTarget( $fragment ) {
return new TitleValue( $this->namespace, $this->dbkey, $fragment );
return new TitleValue(
$this->namespace,
$this->dbkey,
$fragment,
$this->interwiki
);
}
/**
* Whether it has an interwiki part
*
* @since 1.27
* @return bool
*/
public function isExternal() {
return $this->interwiki !== '';
}
/**
* Returns the interwiki part
*
* @since 1.27
* @return string
*/
public function getInterwiki() {
return $this->interwiki;
}
/**
@ -155,6 +185,10 @@ class TitleValue implements LinkTarget {
$name .= '#' . $this->fragment;
}
if ( $this->interwiki !== '' ) {
$name = $this->interwiki . ':' . $name;
}
return $name;
}
}

View file

@ -87,13 +87,14 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
public static function provideFormat() {
return [
[ NS_MAIN, 'Foo_Bar', '', 'en', 'Foo Bar' ],
[ NS_USER, 'Hansi_Maier', 'stuff_and_so_on', 'en', 'User:Hansi Maier#stuff and so on' ],
[ false, 'Hansi_Maier', '', 'en', 'Hansi Maier' ],
[ NS_MAIN, 'Foo_Bar', '', '', 'en', 'Foo Bar' ],
[ NS_USER, 'Hansi_Maier', 'stuff_and_so_on', '', 'en', 'User:Hansi Maier#stuff and so on' ],
[ false, 'Hansi_Maier', '', '', 'en', 'Hansi Maier' ],
[
NS_USER_TALK,
'hansi__maier',
'',
'',
'en',
'User talk:hansi maier',
'User talk:Hansi maier'
@ -101,20 +102,23 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
// getGenderCache() provides a mock that considers first
// names ending in "a" to be female.
[ NS_USER, 'Lisa_Müller', '', 'de', 'Benutzerin:Lisa Müller' ],
[ NS_USER, 'Lisa_Müller', '', '', 'de', 'Benutzerin:Lisa Müller' ],
[ NS_MAIN, 'FooBar', '', 'remotetestiw', 'en', 'remotetestiw:FooBar' ],
];
}
/**
* @dataProvider provideFormat
*/
public function testFormat( $namespace, $text, $fragment, $lang, $expected, $normalized = null ) {
public function testFormat( $namespace, $text, $fragment, $interwiki, $lang, $expected,
$normalized = null
) {
if ( $normalized === null ) {
$normalized = $expected;
}
$codec = $this->makeCodec( $lang );
$actual = $codec->formatTitle( $namespace, $text, $fragment );
$actual = $codec->formatTitle( $namespace, $text, $fragment, $interwiki );
$this->assertEquals( $expected, $actual, 'formatted' );
@ -123,7 +127,8 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
$actual2 = $codec->formatTitle(
$parsed->getNamespace(),
$parsed->getText(),
$parsed->getFragment()
$parsed->getFragment(),
$parsed->getInterwiki()
);
$this->assertEquals( $normalized, $actual2, 'normalized after round trip' );
@ -293,7 +298,6 @@ class MediaWikiTitleCodecTest extends MediaWikiTestCase {
[ 'Talk:File:Foo.jpg' ],
[ 'Talk:localtestiw:Foo' ],
[ 'remotetestiw:Foo' ],
[ '::1' ], // only valid in user namespace
[ 'User::x' ], // leading ":" in a user name is only valid of IPv6 addresses

View file

@ -28,49 +28,57 @@ class TitleValueTest extends MediaWikiTestCase {
public function goodConstructorProvider() {
return [
[ NS_USER, 'TestThis', 'stuff', true ],
[ NS_USER, 'TestThis', '', false ],
[ NS_USER, 'TestThis', 'stuff', '', true, false ],
[ NS_USER, 'TestThis', '', 'baz', false, true ],
];
}
/**
* @dataProvider goodConstructorProvider
*/
public function testConstruction( $ns, $text, $fragment, $hasFragment ) {
$title = new TitleValue( $ns, $text, $fragment );
public function testConstruction( $ns, $text, $fragment, $interwiki, $hasFragment,
$hasInterwiki
) {
$title = new TitleValue( $ns, $text, $fragment, $interwiki );
$this->assertEquals( $ns, $title->getNamespace() );
$this->assertEquals( $text, $title->getText() );
$this->assertEquals( $fragment, $title->getFragment() );
$this->assertEquals( $hasFragment, $title->hasFragment() );
$this->assertEquals( $interwiki, $title->getInterwiki() );
$this->assertEquals( $hasInterwiki, $title->isExternal() );
}
public function badConstructorProvider() {
return [
[ 'foo', 'title', 'fragment' ],
[ null, 'title', 'fragment' ],
[ 2.3, 'title', 'fragment' ],
[ 'foo', 'title', 'fragment', '' ],
[ null, 'title', 'fragment', '' ],
[ 2.3, 'title', 'fragment', '' ],
[ NS_MAIN, 5, 'fragment' ],
[ NS_MAIN, null, 'fragment' ],
[ NS_MAIN, '', 'fragment' ],
[ NS_MAIN, 'foo bar', '' ],
[ NS_MAIN, 'bar_', '' ],
[ NS_MAIN, '_foo', '' ],
[ NS_MAIN, ' eek ', '' ],
[ NS_MAIN, 5, 'fragment', '' ],
[ NS_MAIN, null, 'fragment', '' ],
[ NS_MAIN, '', 'fragment', '' ],
[ NS_MAIN, 'foo bar', '', '' ],
[ NS_MAIN, 'bar_', '', '' ],
[ NS_MAIN, '_foo', '', '' ],
[ NS_MAIN, ' eek ', '', '' ],
[ NS_MAIN, 'title', 5 ],
[ NS_MAIN, 'title', null ],
[ NS_MAIN, 'title', [] ],
[ NS_MAIN, 'title', 5, '' ],
[ NS_MAIN, 'title', null, '' ],
[ NS_MAIN, 'title', [], '' ],
[ NS_MAIN, 'title', '', 5 ],
[ NS_MAIN, 'title', null, 5 ],
[ NS_MAIN, 'title', [], 5 ],
];
}
/**
* @dataProvider badConstructorProvider
*/
public function testConstructionErrors( $ns, $text, $fragment ) {
public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
$this->setExpectedException( 'InvalidArgumentException' );
new TitleValue( $ns, $text, $fragment );
new TitleValue( $ns, $text, $fragment, $interwiki );
}
public function fragmentTitleProvider() {