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