MediaWiki  REL1_24
TitleTest.php
Go to the documentation of this file.
00001 <?php
00002 
00006 class TitleTest extends MediaWikiTestCase {
00007     protected function setUp() {
00008         parent::setUp();
00009 
00010         $this->setMwGlobals( array(
00011             'wgLanguageCode' => 'en',
00012             'wgContLang' => Language::factory( 'en' ),
00013             // User language
00014             'wgLang' => Language::factory( 'en' ),
00015             'wgAllowUserJs' => false,
00016             'wgDefaultLanguageVariant' => false,
00017         ) );
00018     }
00019 
00023     public function testLegalChars() {
00024         $titlechars = Title::legalChars();
00025 
00026         foreach ( range( 1, 255 ) as $num ) {
00027             $chr = chr( $num );
00028             if ( strpos( "#[]{}<>|", $chr ) !== false || preg_match( "/[\\x00-\\x1f\\x7f]/", $chr ) ) {
00029                 $this->assertFalse(
00030                     (bool)preg_match( "/[$titlechars]/", $chr ),
00031                     "chr($num) = $chr is not a valid titlechar"
00032                 );
00033             } else {
00034                 $this->assertTrue(
00035                     (bool)preg_match( "/[$titlechars]/", $chr ),
00036                     "chr($num) = $chr is a valid titlechar"
00037                 );
00038             }
00039         }
00040     }
00041 
00042     public static function provideValidSecureAndSplit() {
00043         return array(
00044             array( 'Sandbox' ),
00045             array( 'A "B"' ),
00046             array( 'A \'B\'' ),
00047             array( '.com' ),
00048             array( '~' ),
00049             array( '#' ),
00050             array( '"' ),
00051             array( '\'' ),
00052             array( 'Talk:Sandbox' ),
00053             array( 'Talk:Foo:Sandbox' ),
00054             array( 'File:Example.svg' ),
00055             array( 'File_talk:Example.svg' ),
00056             array( 'Foo/.../Sandbox' ),
00057             array( 'Sandbox/...' ),
00058             array( 'A~~' ),
00059             array( ':A' ),
00060             // Length is 256 total, but only title part matters
00061             array( 'Category:' . str_repeat( 'x', 248 ) ),
00062             array( str_repeat( 'x', 252 ) ),
00063             // interwiki prefix
00064             array( 'localtestiw: #anchor' ),
00065             array( 'localtestiw:' ),
00066             array( 'localtestiw:foo' ),
00067             array( 'localtestiw: foo # anchor' ),
00068             array( 'localtestiw: Talk: Sandbox # anchor' ),
00069             array( 'remotetestiw:' ),
00070             array( 'remotetestiw: Talk: # anchor' ),
00071             array( 'remotetestiw: #bar' ),
00072             array( 'remotetestiw: Talk:' ),
00073             array( 'remotetestiw: Talk: Foo' ),
00074             array( 'localtestiw:remotetestiw:' ),
00075             array( 'localtestiw:remotetestiw:foo' )
00076         );
00077     }
00078 
00079     public static function provideInvalidSecureAndSplit() {
00080         return array(
00081             array( '' ),
00082             array( ':' ),
00083             array( '__  __' ),
00084             array( '  __  ' ),
00085             // Bad characters forbidden regardless of wgLegalTitleChars
00086             array( 'A [ B' ),
00087             array( 'A ] B' ),
00088             array( 'A { B' ),
00089             array( 'A } B' ),
00090             array( 'A < B' ),
00091             array( 'A > B' ),
00092             array( 'A | B' ),
00093             // URL encoding
00094             array( 'A%20B' ),
00095             array( 'A%23B' ),
00096             array( 'A%2523B' ),
00097             // XML/HTML character entity references
00098             // Note: Commented out because they are not marked invalid by the PHP test as
00099             // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first.
00100             //'A &eacute; B',
00101             //'A &#233; B',
00102             //'A &#x00E9; B',
00103             // Subject of NS_TALK does not roundtrip to NS_MAIN
00104             array( 'Talk:File:Example.svg' ),
00105             // Directory navigation
00106             array( '.' ),
00107             array( '..' ),
00108             array( './Sandbox' ),
00109             array( '../Sandbox' ),
00110             array( 'Foo/./Sandbox' ),
00111             array( 'Foo/../Sandbox' ),
00112             array( 'Sandbox/.' ),
00113             array( 'Sandbox/..' ),
00114             // Tilde
00115             array( 'A ~~~ Name' ),
00116             array( 'A ~~~~ Signature' ),
00117             array( 'A ~~~~~ Timestamp' ),
00118             array( str_repeat( 'x', 256 ) ),
00119             // Namespace prefix without actual title
00120             array( 'Talk:' ),
00121             array( 'Talk:#' ),
00122             array( 'Category: ' ),
00123             array( 'Category: #bar' ),
00124             // interwiki prefix
00125             array( 'localtestiw: Talk: # anchor' ),
00126             array( 'localtestiw: Talk:' )
00127         );
00128     }
00129 
00130     private function secureAndSplitGlobals() {
00131         $this->setMwGlobals( array(
00132             'wgLocalInterwikis' => array( 'localtestiw' ),
00133             'wgHooks' => array(
00134                 'InterwikiLoadPrefix' => array(
00135                     function ( $prefix, &$data ) {
00136                         if ( $prefix === 'localtestiw' ) {
00137                             $data = array( 'iw_url' => 'localtestiw' );
00138                         } elseif ( $prefix === 'remotetestiw' ) {
00139                             $data = array( 'iw_url' => 'remotetestiw' );
00140                         }
00141                         return false;
00142                     }
00143                 )
00144             )
00145         ));
00146     }
00147 
00154     public function testSecureAndSplitValid( $text ) {
00155         $this->secureAndSplitGlobals();
00156         $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" );
00157     }
00158 
00165     public function testSecureAndSplitInvalid( $text ) {
00166         $this->secureAndSplitGlobals();
00167         $this->assertNull( Title::newFromText( $text ), "Invalid: $text" );
00168     }
00169 
00170     public static function provideConvertByteClassToUnicodeClass() {
00171         return array(
00172             array(
00173                 ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+',
00174                 ' %!"$&\'()*,\\-./0-9:;=?@A-Z\\\\\\^_`a-z~+\\u0080-\\uFFFF',
00175             ),
00176             array(
00177                 'QWERTYf-\\xFF+',
00178                 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
00179             ),
00180             array(
00181                 'QWERTY\\x66-\\xFD+',
00182                 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
00183             ),
00184             array(
00185                 'QWERTYf-y+',
00186                 'QWERTYf-y+',
00187             ),
00188             array(
00189                 'QWERTYf-\\x80+',
00190                 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
00191             ),
00192             array(
00193                 'QWERTY\\x66-\\x80+\\x23',
00194                 'QWERTYf-\\x7F+#\\u0080-\\uFFFF',
00195             ),
00196             array(
00197                 'QWERTY\\x66-\\x80+\\xD3',
00198                 'QWERTYf-\\x7F+\\u0080-\\uFFFF',
00199             ),
00200             array(
00201                 '\\\\\\x99',
00202                 '\\\\\\u0080-\\uFFFF',
00203             ),
00204             array(
00205                 '-\\x99',
00206                 '\\-\\u0080-\\uFFFF',
00207             ),
00208             array(
00209                 'QWERTY\\-\\x99',
00210                 'QWERTY\\-\\u0080-\\uFFFF',
00211             ),
00212             array(
00213                 '\\\\x99',
00214                 '\\\\x99',
00215             ),
00216             array(
00217                 'A-\\x9F',
00218                 'A-\\x7F\\u0080-\\uFFFF',
00219             ),
00220             array(
00221                 '\\x66-\\x77QWERTY\\x88-\\x91FXZ',
00222                 'f-wQWERTYFXZ\\u0080-\\uFFFF',
00223             ),
00224             array(
00225                 '\\x66-\\x99QWERTY\\xAA-\\xEEFXZ',
00226                 'f-\\x7FQWERTYFXZ\\u0080-\\uFFFF',
00227             ),
00228         );
00229     }
00230 
00235     public function testConvertByteClassToUnicodeClass( $byteClass, $unicodeClass ) {
00236         $this->assertEquals( $unicodeClass, Title::convertByteClassToUnicodeClass( $byteClass ) );
00237     }
00238 
00243     public function testFixSpecialNameRetainsParameter( $text, $expectedParam ) {
00244         $title = Title::newFromText( $text );
00245         $fixed = $title->fixSpecialName();
00246         $stuff = explode( '/', $fixed->getDBkey(), 2 );
00247         if ( count( $stuff ) == 2 ) {
00248             $par = $stuff[1];
00249         } else {
00250             $par = null;
00251         }
00252         $this->assertEquals(
00253             $expectedParam,
00254             $par,
00255             "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter"
00256         );
00257     }
00258 
00259     public static function provideSpecialNamesWithAndWithoutParameter() {
00260         return array(
00261             array( 'Special:Version', null ),
00262             array( 'Special:Version/', '' ),
00263             array( 'Special:Version/param', 'param' ),
00264         );
00265     }
00266 
00278     public function testIsValidMoveOperation( $source, $target, $expected ) {
00279         $this->setMwGlobals( 'wgContentHandlerUseDB', false );
00280         $title = Title::newFromText( $source );
00281         $nt = Title::newFromText( $target );
00282         $errors = $title->isValidMoveOperation( $nt, false );
00283         if ( $expected === true ) {
00284             $this->assertTrue( $errors );
00285         } else {
00286             $errors = $this->flattenErrorsArray( $errors );
00287             foreach ( (array)$expected as $error ) {
00288                 $this->assertContains( $error, $errors );
00289             }
00290         }
00291     }
00292 
00293     public static function provideTestIsValidMoveOperation() {
00294         return array(
00295             // for Title::isValidMoveOperation
00296             array( 'Some page', '', 'badtitletext' ),
00297             array( 'Test', 'Test', 'selfmove' ),
00298             array( 'Special:FooBar', 'Test', 'immobile-source-namespace' ),
00299             array( 'Test', 'Special:FooBar', 'immobile-target-namespace' ),
00300             array( 'MediaWiki:Common.js', 'Help:Some wikitext page', 'bad-target-model' ),
00301             array( 'Page', 'File:Test.jpg', 'nonfile-cannot-move-to-file' ),
00302             // for Title::validateFileMoveOperation
00303             array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ),
00304         );
00305     }
00306 
00318     public function testWgWhitelistReadRegexp( $whitelistRegexp, $source, $action, $expected ) {
00319         // $wgWhitelistReadRegexp must be an array. Since the provided test cases
00320         // usually have only one regex, it is more concise to write the lonely regex
00321         // as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp
00322         // type requisite.
00323         if ( is_string( $whitelistRegexp ) ) {
00324             $whitelistRegexp = array( $whitelistRegexp );
00325         }
00326 
00327         $title = Title::newFromDBkey( $source );
00328 
00329         global $wgGroupPermissions;
00330         $oldPermissions = $wgGroupPermissions;
00331         // Disallow all so we can ensure our regex works
00332         $wgGroupPermissions = array();
00333         $wgGroupPermissions['*']['read'] = false;
00334 
00335         global $wgWhitelistRead;
00336         $oldWhitelist = $wgWhitelistRead;
00337         // Undo any LocalSettings explicite whitelists so they won't cause a
00338         // failing test to succeed. Set it to some random non sense just
00339         // to make sure we properly test Title::checkReadPermissions()
00340         $wgWhitelistRead = array( 'some random non sense title' );
00341 
00342         global $wgWhitelistReadRegexp;
00343         $oldWhitelistRegexp = $wgWhitelistReadRegexp;
00344         $wgWhitelistReadRegexp = $whitelistRegexp;
00345 
00346         // Just use $wgUser which in test is a user object for '127.0.0.1'
00347         global $wgUser;
00348         // Invalidate user rights cache to take in account $wgGroupPermissions
00349         // change above.
00350         $wgUser->clearInstanceCache();
00351         $errors = $title->userCan( $action, $wgUser );
00352 
00353         // Restore globals
00354         $wgGroupPermissions = $oldPermissions;
00355         $wgWhitelistRead = $oldWhitelist;
00356         $wgWhitelistReadRegexp = $oldWhitelistRegexp;
00357 
00358         if ( is_bool( $expected ) ) {
00359             # Forge the assertion message depending on the assertion expectation
00360             $allowableness = $expected
00361                 ? " should be allowed"
00362                 : " should NOT be allowed";
00363             $this->assertEquals(
00364                 $expected,
00365                 $errors,
00366                 "User action '$action' on [[$source]] $allowableness."
00367             );
00368         } else {
00369             $errors = $this->flattenErrorsArray( $errors );
00370             foreach ( (array)$expected as $error ) {
00371                 $this->assertContains( $error, $errors );
00372             }
00373         }
00374     }
00375 
00379     public function dataWgWhitelistReadRegexp() {
00380         $ALLOWED = true;
00381         $DISALLOWED = false;
00382 
00383         return array(
00384             // Everything, if this doesn't work, we're really in trouble
00385             array( '/.*/', 'Main_Page', 'read', $ALLOWED ),
00386             array( '/.*/', 'Main_Page', 'edit', $DISALLOWED ),
00387 
00388             // We validate against the title name, not the db key
00389             array( '/^Main_Page$/', 'Main_Page', 'read', $DISALLOWED ),
00390             // Main page
00391             array( '/^Main/', 'Main_Page', 'read', $ALLOWED ),
00392             array( '/^Main.*/', 'Main_Page', 'read', $ALLOWED ),
00393             // With spaces
00394             array( '/Mic\sCheck/', 'Mic Check', 'read', $ALLOWED ),
00395             // Unicode multibyte
00396             // ...without unicode modifier
00397             array( '/Unicode Test . Yes/', 'Unicode Test Ñ Yes', 'read', $DISALLOWED ),
00398             // ...with unicode modifier
00399             array( '/Unicode Test . Yes/u', 'Unicode Test Ñ Yes', 'read', $ALLOWED ),
00400             // Case insensitive
00401             array( '/MiC ChEcK/', 'mic check', 'read', $DISALLOWED ),
00402             array( '/MiC ChEcK/i', 'mic check', 'read', $ALLOWED ),
00403 
00404             // From DefaultSettings.php:
00405             array( "@^UsEr.*@i", 'User is banned', 'read', $ALLOWED ),
00406             array( "@^UsEr.*@i", 'User:John Doe', 'read', $ALLOWED ),
00407 
00408             // With namespaces:
00409             array( '/^Special:NewPages$/', 'Special:NewPages', 'read', $ALLOWED ),
00410             array( null, 'Special:Newpages', 'read', $DISALLOWED ),
00411 
00412         );
00413     }
00414 
00415     public function flattenErrorsArray( $errors ) {
00416         $result = array();
00417         foreach ( $errors as $error ) {
00418             $result[] = $error[0];
00419         }
00420 
00421         return $result;
00422     }
00423 
00428     public function testGetPageViewLanguage( $expected, $titleText, $contLang,
00429         $lang, $variant, $msg = ''
00430     ) {
00431         global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs;
00432 
00433         // Setup environnement for this test
00434         $wgLanguageCode = $contLang;
00435         $wgContLang = Language::factory( $contLang );
00436         $wgLang = Language::factory( $lang );
00437         $wgDefaultLanguageVariant = $variant;
00438         $wgAllowUserJs = true;
00439 
00440         $title = Title::newFromText( $titleText );
00441         $this->assertInstanceOf( 'Title', $title,
00442             "Test must be passed a valid title text, you gave '$titleText'"
00443         );
00444         $this->assertEquals( $expected,
00445             $title->getPageViewLanguage()->getCode(),
00446             $msg
00447         );
00448     }
00449 
00450     public static function provideGetPageViewLanguage() {
00451         # Format:
00452         # - expected
00453         # - Title name
00454         # - wgContLang (expected in most case)
00455         # - wgLang (on some specific pages)
00456         # - wgDefaultLanguageVariant
00457         # - Optional message
00458         return array(
00459             array( 'fr', 'Help:I_need_somebody', 'fr', 'fr', false ),
00460             array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', false ),
00461             array( 'zh', 'Help:I_need_somebody', 'zh', 'zh-tw', false ),
00462 
00463             array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', 'zh-cn' ),
00464             array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ),
00465             array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ),
00466             array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ),
00467             array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ),
00468             array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ),
00469             array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ),
00470             array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ),
00471 
00472             array( 'zh-cn', 'Help:I_need_somebody', 'zh', 'zh-tw', 'zh-cn' ),
00473             array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ),
00474             array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ),
00475             array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ),
00476             array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ),
00477             array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ),
00478             array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ),
00479             array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ),
00480             array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ),
00481             array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ),
00482 
00483             array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ),
00484             array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ),
00485 
00486         );
00487     }
00488 
00493     public function testGetBaseText( $title, $expected, $msg = '' ) {
00494         $title = Title::newFromText( $title );
00495         $this->assertEquals( $expected,
00496             $title->getBaseText(),
00497             $msg
00498         );
00499     }
00500 
00501     public static function provideBaseTitleCases() {
00502         return array(
00503             # Title, expected base, optional message
00504             array( 'User:John_Doe/subOne/subTwo', 'John Doe/subOne' ),
00505             array( 'User:Foo/Bar/Baz', 'Foo/Bar' ),
00506         );
00507     }
00508 
00513     public function testGetRootText( $title, $expected, $msg = '' ) {
00514         $title = Title::newFromText( $title );
00515         $this->assertEquals( $expected,
00516             $title->getRootText(),
00517             $msg
00518         );
00519     }
00520 
00521     public static function provideRootTitleCases() {
00522         return array(
00523             # Title, expected base, optional message
00524             array( 'User:John_Doe/subOne/subTwo', 'John Doe' ),
00525             array( 'User:Foo/Bar/Baz', 'Foo' ),
00526         );
00527     }
00528 
00534     public function testGetSubpageText( $title, $expected, $msg = '' ) {
00535         $title = Title::newFromText( $title );
00536         $this->assertEquals( $expected,
00537             $title->getSubpageText(),
00538             $msg
00539         );
00540     }
00541 
00542     public static function provideSubpageTitleCases() {
00543         return array(
00544             # Title, expected base, optional message
00545             array( 'User:John_Doe/subOne/subTwo', 'subTwo' ),
00546             array( 'User:John_Doe/subOne', 'subOne' ),
00547         );
00548     }
00549 
00550     public static function provideNewFromTitleValue() {
00551         return array(
00552             array( new TitleValue( NS_MAIN, 'Foo' ) ),
00553             array( new TitleValue( NS_MAIN, 'Foo', 'bar' ) ),
00554             array( new TitleValue( NS_USER, 'Hansi_Maier' ) ),
00555         );
00556     }
00557 
00561     public function testNewFromTitleValue( TitleValue $value ) {
00562         $title = Title::newFromTitleValue( $value );
00563 
00564         $dbkey = str_replace( ' ', '_', $value->getText() );
00565         $this->assertEquals( $dbkey, $title->getDBkey() );
00566         $this->assertEquals( $value->getNamespace(), $title->getNamespace() );
00567         $this->assertEquals( $value->getFragment(), $title->getFragment() );
00568     }
00569 
00570     public static function provideGetTitleValue() {
00571         return array(
00572             array( 'Foo' ),
00573             array( 'Foo#bar' ),
00574             array( 'User:Hansi_Maier' ),
00575         );
00576     }
00577 
00581     public function testGetTitleValue( $text ) {
00582         $title = Title::newFromText( $text );
00583         $value = $title->getTitleValue();
00584 
00585         $dbkey = str_replace( ' ', '_', $value->getText() );
00586         $this->assertEquals( $title->getDBkey(), $dbkey );
00587         $this->assertEquals( $title->getNamespace(), $value->getNamespace() );
00588         $this->assertEquals( $title->getFragment(), $value->getFragment() );
00589     }
00590 
00591     public static function provideGetFragment() {
00592         return array(
00593             array( 'Foo', '' ),
00594             array( 'Foo#bar', 'bar' ),
00595             array( 'Foo#bär', 'bär' ),
00596 
00597             // Inner whitespace is normalized
00598             array( 'Foo#bar_bar', 'bar bar' ),
00599             array( 'Foo#bar bar', 'bar bar' ),
00600             array( 'Foo#bar   bar', 'bar bar' ),
00601 
00602             // Leading whitespace is kept, trailing whitespace is trimmed.
00603             // XXX: Is this really want we want?
00604             array( 'Foo#_bar_bar_', ' bar bar' ),
00605             array( 'Foo# bar bar ', ' bar bar' ),
00606         );
00607     }
00608 
00615     public function testGetFragment( $full, $fragment ) {
00616         $title = Title::newFromText( $full );
00617         $this->assertEquals( $fragment, $title->getFragment() );
00618     }
00619 
00626     public function testIsAlwaysKnown( $page, $isKnown ) {
00627         $title = Title::newFromText( $page );
00628         $this->assertEquals( $isKnown, $title->isAlwaysKnown() );
00629     }
00630 
00631     public static function provideIsAlwaysKnown() {
00632         return array(
00633             array( 'Some nonexistent page', false ),
00634             array( 'UTPage', false ),
00635             array( '#test', true ),
00636             array( 'Special:BlankPage', true ),
00637             array( 'Special:SomeNonexistentSpecialPage', false ),
00638             array( 'MediaWiki:Parentheses', true ),
00639             array( 'MediaWiki:Some nonexistent message', false ),
00640         );
00641     }
00642 
00646     public function testIsAlwaysKnownOnInterwiki() {
00647         $title = Title::makeTitle( NS_MAIN, 'Interwiki link', '', 'externalwiki' );
00648         $this->assertTrue( $title->isAlwaysKnown() );
00649     }
00650 }