MediaWiki
REL1_24
|
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 é B', 00101 //'A é B', 00102 //'A é 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 }