5 * ^--- needed for language cache stuff
9 class TitleTest
extends MediaWikiTestCase
{
10 protected function setUp() {
13 $this->setMwGlobals( array(
14 'wgLanguageCode' => 'en',
15 'wgContLang' => Language
::factory( 'en' ),
17 'wgLang' => Language
::factory( 'en' ),
18 'wgAllowUserJs' => false,
19 'wgDefaultLanguageVariant' => false,
23 public function addDBData() {
24 $this->db
->replace( 'interwiki', 'iw_prefix',
26 'iw_prefix' => 'externalwiki',
27 'iw_url' => '//example.com/$1',
28 'iw_api' => '//example.com/api.php',
37 * @covers Title::legalChars
39 public function testLegalChars() {
40 $titlechars = Title
::legalChars();
42 foreach ( range( 1, 255 ) as $num ) {
44 if ( strpos( "#[]{}<>|", $chr ) !== false ||
preg_match( "/[\\x00-\\x1f\\x7f]/", $chr ) ) {
46 (bool)preg_match( "/[$titlechars]/", $chr ),
47 "chr($num) = $chr is not a valid titlechar"
51 (bool)preg_match( "/[$titlechars]/", $chr ),
52 "chr($num) = $chr is a valid titlechar"
59 * See also mediawiki.Title.test.js
60 * @covers Title::secureAndSplit
61 * @todo This method should be split into 2 separate tests each with a provider
62 * @note: This mainly tests MediaWikiTitleCodec::parseTitle().
64 public function testSecureAndSplit() {
65 $this->setMwGlobals( array(
66 'wgLocalInterwikis' => array( 'localtestiw' ),
68 'InterwikiLoadPrefix' => array(
69 function ( $prefix, &$data ) {
70 if ( $prefix === 'localtestiw' ) {
71 $data = array( 'iw_url' => 'localtestiw' );
72 } elseif ( $prefix === 'remotetestiw' ) {
73 $data = array( 'iw_url' => 'remotetestiw' );
93 'File_talk:Example.svg',
98 // Length is 256 total, but only title part matters
99 'Category:' . str_repeat( 'x', 248 ),
100 str_repeat( 'x', 252 ),
102 'localtestiw: #anchor',
105 'localtestiw: foo # anchor',
106 'localtestiw: Talk: Sandbox # anchor',
108 'remotetestiw: Talk: # anchor',
109 'remotetestiw: #bar',
110 'remotetestiw: Talk:',
111 'remotetestiw: Talk: Foo',
112 'localtestiw:remotetestiw:',
113 'localtestiw:remotetestiw:foo'
115 $this->assertInstanceOf( 'Title', Title
::newFromText( $text ), "Valid: $text" );
124 // Bad characters forbidden regardless of wgLegalTitleChars
136 // XML/HTML character entity references
137 // Note: Commented out because they are not marked invalid by the PHP test as
138 // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first.
142 // Subject of NS_TALK does not roundtrip to NS_MAIN
143 'Talk:File:Example.svg',
144 // Directory navigation
157 str_repeat( 'x', 256 ),
158 // Namespace prefix without actual title
164 'localtestiw: Talk: # anchor',
167 $this->assertNull( Title
::newFromText( $text ), "Invalid: $text" );
171 public static function provideConvertByteClassToUnicodeClass() {
174 ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+',
175 ' %!"$&\'()*,\\-./0-9:;=?@A-Z\\\\\\^_`a-z~+\\u0080-\\uFFFF',
179 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
182 'QWERTY\\x66-\\xFD+',
183 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
191 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
194 'QWERTY\\x66-\\x80+\\x23',
195 'QWERTYf-\\x7F+#\\u0080-\\uFFFF',
198 'QWERTY\\x66-\\x80+\\xD3',
199 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
203 '\\\\\\u0080-\\uFFFF',
207 '\\-\\u0080-\\uFFFF',
211 'QWERTY\\-\\u0080-\\uFFFF',
219 'A-\\x7F\\u0080-\\uFFFF',
222 '\\x66-\\x77QWERTY\\x88-\\x91FXZ',
223 'f-wQWERTYFXZ\\u0080-\\uFFFF',
226 '\\x66-\\x99QWERTY\\xAA-\\xEEFXZ',
227 'f-\\x7FQWERTYFXZ\\u0080-\\uFFFF',
233 * @dataProvider provideConvertByteClassToUnicodeClass
234 * @covers Title::convertByteClassToUnicodeClass
236 public function testConvertByteClassToUnicodeClass( $byteClass, $unicodeClass ) {
237 $this->assertEquals( $unicodeClass, Title
::convertByteClassToUnicodeClass( $byteClass ) );
241 * @dataProvider provideBug31100
242 * @covers Title::fixSpecialName
243 * @todo give this test a real name explaining what is being tested here
245 public function testBug31100FixSpecialName( $text, $expectedParam ) {
246 $title = Title
::newFromText( $text );
247 $fixed = $title->fixSpecialName();
248 $stuff = explode( '/', $fixed->getDBkey(), 2 );
249 if ( count( $stuff ) == 2 ) {
257 "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter"
261 public static function provideBug31100() {
263 array( 'Special:Version', null ),
264 array( 'Special:Version/', '' ),
265 array( 'Special:Version/param', 'param' ),
270 * Auth-less test of Title::isValidMoveOperation
273 * @param string $source
274 * @param string $target
275 * @param array|string|bool $expected Required error
276 * @dataProvider provideTestIsValidMoveOperation
277 * @covers Title::isValidMoveOperation
279 public function testIsValidMoveOperation( $source, $target, $expected ) {
280 $title = Title
::newFromText( $source );
281 $nt = Title
::newFromText( $target );
282 $errors = $title->isValidMoveOperation( $nt, false );
283 if ( $expected === true ) {
284 $this->assertTrue( $errors );
286 $errors = $this->flattenErrorsArray( $errors );
287 foreach ( (array)$expected as $error ) {
288 $this->assertContains( $error, $errors );
294 * Provides test parameter values for testIsValidMoveOperation()
296 public function dataTestIsValidMoveOperation() {
298 array( 'Test', 'Test', 'selfmove' ),
299 array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' )
304 * Auth-less test of Title::userCan
306 * @param array $whitelistRegexp
307 * @param string $source
308 * @param string $action
309 * @param array|string|bool $expected Required error
311 * @covers Title::checkReadPermissions
312 * @dataProvider dataWgWhitelistReadRegexp
314 public function testWgWhitelistReadRegexp( $whitelistRegexp, $source, $action, $expected ) {
315 // $wgWhitelistReadRegexp must be an array. Since the provided test cases
316 // usually have only one regex, it is more concise to write the lonely regex
317 // as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp
319 if ( is_string( $whitelistRegexp ) ) {
320 $whitelistRegexp = array( $whitelistRegexp );
323 $title = Title
::newFromDBkey( $source );
325 global $wgGroupPermissions;
326 $oldPermissions = $wgGroupPermissions;
327 // Disallow all so we can ensure our regex works
328 $wgGroupPermissions = array();
329 $wgGroupPermissions['*']['read'] = false;
331 global $wgWhitelistRead;
332 $oldWhitelist = $wgWhitelistRead;
333 // Undo any LocalSettings explicite whitelists so they won't cause a
334 // failing test to succeed. Set it to some random non sense just
335 // to make sure we properly test Title::checkReadPermissions()
336 $wgWhitelistRead = array( 'some random non sense title' );
338 global $wgWhitelistReadRegexp;
339 $oldWhitelistRegexp = $wgWhitelistReadRegexp;
340 $wgWhitelistReadRegexp = $whitelistRegexp;
342 // Just use $wgUser which in test is a user object for '127.0.0.1'
344 // Invalidate user rights cache to take in account $wgGroupPermissions
346 $wgUser->clearInstanceCache();
347 $errors = $title->userCan( $action, $wgUser );
350 $wgGroupPermissions = $oldPermissions;
351 $wgWhitelistRead = $oldWhitelist;
352 $wgWhitelistReadRegexp = $oldWhitelistRegexp;
354 if ( is_bool( $expected ) ) {
355 # Forge the assertion message depending on the assertion expectation
356 $allowableness = $expected
357 ?
" should be allowed"
358 : " should NOT be allowed";
362 "User action '$action' on [[$source]] $allowableness."
365 $errors = $this->flattenErrorsArray( $errors );
366 foreach ( (array)$expected as $error ) {
367 $this->assertContains( $error, $errors );
373 * Provides test parameter values for testWgWhitelistReadRegexp()
375 public function dataWgWhitelistReadRegexp() {
380 // Everything, if this doesn't work, we're really in trouble
381 array( '/.*/', 'Main_Page', 'read', $ALLOWED ),
382 array( '/.*/', 'Main_Page', 'edit', $DISALLOWED ),
384 // We validate against the title name, not the db key
385 array( '/^Main_Page$/', 'Main_Page', 'read', $DISALLOWED ),
387 array( '/^Main/', 'Main_Page', 'read', $ALLOWED ),
388 array( '/^Main.*/', 'Main_Page', 'read', $ALLOWED ),
390 array( '/Mic\sCheck/', 'Mic Check', 'read', $ALLOWED ),
392 // ...without unicode modifier
393 array( '/Unicode Test . Yes/', 'Unicode Test Ñ Yes', 'read', $DISALLOWED ),
394 // ...with unicode modifier
395 array( '/Unicode Test . Yes/u', 'Unicode Test Ñ Yes', 'read', $ALLOWED ),
397 array( '/MiC ChEcK/', 'mic check', 'read', $DISALLOWED ),
398 array( '/MiC ChEcK/i', 'mic check', 'read', $ALLOWED ),
400 // From DefaultSettings.php:
401 array( "@^UsEr.*@i", 'User is banned', 'read', $ALLOWED ),
402 array( "@^UsEr.*@i", 'User:John Doe', 'read', $ALLOWED ),
405 array( '/^Special:NewPages$/', 'Special:NewPages', 'read', $ALLOWED ),
406 array( null, 'Special:Newpages', 'read', $DISALLOWED ),
411 public function flattenErrorsArray( $errors ) {
413 foreach ( $errors as $error ) {
414 $result[] = $error[0];
420 public static function provideTestIsValidMoveOperation() {
422 array( 'Test', 'Test', 'selfmove' ),
423 array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' )
428 * @dataProvider provideGetPageViewLanguage
429 * @covers Title::getPageViewLanguage
431 public function testGetPageViewLanguage( $expected, $titleText, $contLang,
432 $lang, $variant, $msg = ''
434 global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs;
436 // Setup environnement for this test
437 $wgLanguageCode = $contLang;
438 $wgContLang = Language
::factory( $contLang );
439 $wgLang = Language
::factory( $lang );
440 $wgDefaultLanguageVariant = $variant;
441 $wgAllowUserJs = true;
443 $title = Title
::newFromText( $titleText );
444 $this->assertInstanceOf( 'Title', $title,
445 "Test must be passed a valid title text, you gave '$titleText'"
447 $this->assertEquals( $expected,
448 $title->getPageViewLanguage()->getCode(),
453 public static function provideGetPageViewLanguage() {
457 # - wgContLang (expected in most case)
458 # - wgLang (on some specific pages)
459 # - wgDefaultLanguageVariant
462 array( 'fr', 'Help:I_need_somebody', 'fr', 'fr', false ),
463 array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', false ),
464 array( 'zh', 'Help:I_need_somebody', 'zh', 'zh-tw', false ),
466 array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', 'zh-cn' ),
467 array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ),
468 array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ),
469 array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ),
470 array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ),
471 array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ),
472 array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ),
473 array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ),
475 array( 'zh-cn', 'Help:I_need_somebody', 'zh', 'zh-tw', 'zh-cn' ),
476 array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ),
477 array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ),
478 array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ),
479 array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ),
480 array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ),
481 array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ),
482 array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ),
483 array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ),
484 array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ),
486 array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ),
487 array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ),
493 * @dataProvider provideBaseTitleCases
494 * @covers Title::getBaseText
496 public function testGetBaseText( $title, $expected, $msg = '' ) {
497 $title = Title
::newFromText( $title );
498 $this->assertEquals( $expected,
499 $title->getBaseText(),
504 public static function provideBaseTitleCases() {
506 # Title, expected base, optional message
507 array( 'User:John_Doe/subOne/subTwo', 'John Doe/subOne' ),
508 array( 'User:Foo/Bar/Baz', 'Foo/Bar' ),
513 * @dataProvider provideRootTitleCases
514 * @covers Title::getRootText
516 public function testGetRootText( $title, $expected, $msg = '' ) {
517 $title = Title
::newFromText( $title );
518 $this->assertEquals( $expected,
519 $title->getRootText(),
524 public static function provideRootTitleCases() {
526 # Title, expected base, optional message
527 array( 'User:John_Doe/subOne/subTwo', 'John Doe' ),
528 array( 'User:Foo/Bar/Baz', 'Foo' ),
533 * @todo Handle $wgNamespacesWithSubpages cases
534 * @dataProvider provideSubpageTitleCases
535 * @covers Title::getSubpageText
537 public function testGetSubpageText( $title, $expected, $msg = '' ) {
538 $title = Title
::newFromText( $title );
539 $this->assertEquals( $expected,
540 $title->getSubpageText(),
545 public static function provideSubpageTitleCases() {
547 # Title, expected base, optional message
548 array( 'User:John_Doe/subOne/subTwo', 'subTwo' ),
549 array( 'User:John_Doe/subOne', 'subOne' ),
553 public function provideNewFromTitleValue() {
555 array( new TitleValue( NS_MAIN
, 'Foo' ) ),
556 array( new TitleValue( NS_MAIN
, 'Foo', 'bar' ) ),
557 array( new TitleValue( NS_USER
, 'Hansi_Maier' ) ),
562 * @dataProvider provideNewFromTitleValue
564 public function testNewFromTitleValue( TitleValue
$value ) {
565 $title = Title
::newFromTitleValue( $value );
567 $dbkey = str_replace( ' ', '_', $value->getText() );
568 $this->assertEquals( $dbkey, $title->getDBkey() );
569 $this->assertEquals( $value->getNamespace(), $title->getNamespace() );
570 $this->assertEquals( $value->getFragment(), $title->getFragment() );
573 public function provideGetTitleValue() {
577 array( 'User:Hansi_Maier' ),
582 * @dataProvider provideGetTitleValue
584 public function testGetTitleValue( $text ) {
585 $title = Title
::newFromText( $text );
586 $value = $title->getTitleValue();
588 $dbkey = str_replace( ' ', '_', $value->getText() );
589 $this->assertEquals( $title->getDBkey(), $dbkey );
590 $this->assertEquals( $title->getNamespace(), $value->getNamespace() );
591 $this->assertEquals( $title->getFragment(), $value->getFragment() );
594 public function provideGetFragment() {
597 array( 'Foo#bar', 'bar' ),
598 array( 'Foo#bär', 'bär' ),
600 // Inner whitespace is normalized
601 array( 'Foo#bar_bar', 'bar bar' ),
602 array( 'Foo#bar bar', 'bar bar' ),
603 array( 'Foo#bar bar', 'bar bar' ),
605 // Leading whitespace is kept, trailing whitespace is trimmed.
606 // XXX: Is this really want we want?
607 array( 'Foo#_bar_bar_', ' bar bar' ),
608 array( 'Foo# bar bar ', ' bar bar' ),
613 * @dataProvider provideGetFragment
615 * @param string $full
616 * @param string $fragment
618 public function testGetFragment( $full, $fragment ) {
619 $title = Title
::newFromText( $full );
620 $this->assertEquals( $fragment, $title->getFragment() );
624 * @covers Title::isAlwaysKnown
625 * @dataProvider provideIsAlwaysKnown
626 * @param string $page
627 * @param bool $isKnown
629 public function testIsAlwaysKnown( $page, $isKnown ) {
630 $title = Title
::newFromText( $page );
631 $this->assertEquals( $isKnown, $title->isAlwaysKnown() );
634 public function provideIsAlwaysKnown() {
636 array( 'Some nonexistent page', false ),
637 array( 'UTPage', false ),
638 array( '#test', true ),
639 array( 'Special:BlankPage', true ),
640 array( 'Special:SomeNonexistentSpecialPage', false ),
641 array( 'MediaWiki:Parentheses', true ),
642 array( 'MediaWiki:Some nonexistent message', false ),
643 array( 'externalwiki:Interwiki link', true ),