Main Page | Namespace List | Class Hierarchy | Class List | File List | Class Members | File Members | Related Pages

Title.php

Go to the documentation of this file.
00001 <?php 00002 # See title.doc 00003 00004 $wgTitleInterwikiCache = array(); 00005 00006 # Title class 00007 # 00008 # * Represents a title, which may contain an interwiki designation or namespace 00009 # * Can fetch various kinds of data from the database, albeit inefficiently. 00010 # 00011 class Title { 00012 # All member variables should be considered private 00013 # Please use the accessor functions 00014 00015 var $mTextform; # Text form (spaces not underscores) of the main part 00016 var $mUrlform; # URL-encoded form of the main part 00017 var $mDbkeyform; # Main part with underscores 00018 var $mNamespace; # Namespace index, i.e. one of the NS_xxxx constants 00019 var $mInterwiki; # Interwiki prefix (or null string) 00020 var $mFragment; # Title fragment (i.e. the bit after the #) 00021 var $mArticleID; # Article ID, fetched from the link cache on demand 00022 var $mRestrictions; # Array of groups allowed to edit this article 00023 # Only null or "sysop" are supported 00024 var $mRestrictionsLoaded; # Boolean for initialisation on demand 00025 var $mPrefixedText; # Text form including namespace/interwiki, initialised on demand 00026 var $mDefaultNamespace; # Namespace index when there is no namespace 00027 # Zero except in {{transclusion}} tags 00028 00029 #---------------------------------------------------------------------------- 00030 # Construction 00031 #---------------------------------------------------------------------------- 00032 00033 /* private */ function Title() 00034 { 00035 $this->mInterwiki = $this->mUrlform = 00036 $this->mTextform = $this->mDbkeyform = ""; 00037 $this->mArticleID = -1; 00038 $this->mNamespace = 0; 00039 $this->mRestrictionsLoaded = false; 00040 $this->mRestrictions = array(); 00041 $this->mDefaultNamespace = 0; 00042 } 00043 00044 # From a prefixed DB key 00045 /* static */ function newFromDBkey( $key ) 00046 { 00047 $t = new Title(); 00048 $t->mDbkeyform = $key; 00049 if( $t->secureAndSplit() ) 00050 return $t; 00051 else 00052 return NULL; 00053 } 00054 00055 # From text, such as what you would find in a link 00056 /* static */ function newFromText( $text, $defaultNamespace = 0 ) 00057 { 00058 $fname = "Title::newFromText"; 00059 wfProfileIn( $fname ); 00060 00061 if( is_object( $text ) ) { 00062 wfDebugDieBacktrace( "Called with object instead of string." ); 00063 } 00064 global $wgInputEncoding; 00065 $text = do_html_entity_decode( $text, ENT_COMPAT, $wgInputEncoding ); 00066 00067 $text = wfMungeToUtf8( $text ); 00068 00069 00070 # What was this for? TS 2004-03-03 00071 # $text = urldecode( $text ); 00072 00073 $t = new Title(); 00074 $t->mDbkeyform = str_replace( " ", "_", $text ); 00075 $t->mDefaultNamespace = $defaultNamespace; 00076 00077 wfProfileOut( $fname ); 00078 if ( !is_object( $t ) ) { 00079 var_dump( debug_backtrace() ); 00080 } 00081 if( $t->secureAndSplit() ) { 00082 return $t; 00083 } else { 00084 return NULL; 00085 } 00086 } 00087 00088 # From a URL-encoded title 00089 /* static */ function newFromURL( $url ) 00090 { 00091 global $wgLang, $wgServer, $wgIsMySQL, $wgIsPg; 00092 $t = new Title(); 00093 00094 # For compatibility with old buggy URLs. "+" is not valid in titles, 00095 # but some URLs used it as a space replacement and they still come 00096 # from some external search tools. 00097 $s = str_replace( "+", " ", $url ); 00098 00099 # For links that came from outside, check for alternate/legacy 00100 # character encoding. 00101 wfDebug( "Servr: $wgServer\n" ); 00102 if( empty( $_SERVER["HTTP_REFERER"] ) || 00103 strncmp($wgServer, $_SERVER["HTTP_REFERER"], strlen( $wgServer ) ) ) 00104 { 00105 $s = $wgLang->checkTitleEncoding( $s ); 00106 } else { 00107 wfDebug( "Refer: {$_SERVER['HTTP_REFERER']}\n" ); 00108 } 00109 00110 $t->mDbkeyform = str_replace( " ", "_", $s ); 00111 if( $t->secureAndSplit() ) { 00112 # check that lenght of title is < cur_title size 00113 if ($wgIsMySQL) { 00114 $sql = "SHOW COLUMNS FROM cur LIKE \"cur_title\";"; 00115 $cur_title_object = wfFetchObject(wfQuery( $sql, DB_READ )); 00116 00117 preg_match( "/\((.*)\)/", $cur_title_object->Type, $cur_title_type); 00118 $cur_title_size=$cur_title_type[1]; 00119 } else { 00120 /* midom:FIXME pg_field_type does not return varchar length 00121 assume 255 */ 00122 $cur_title_size=255; 00123 } 00124 00125 if (strlen($t->mDbkeyform) > $cur_title_size ) { 00126 return NULL; 00127 } 00128 00129 return $t; 00130 } else { 00131 return NULL; 00132 } 00133 } 00134 00135 # From a cur_id 00136 # This is inefficiently implemented, the cur row is requested but not 00137 # used for anything else 00138 /* static */ function newFromID( $id ) 00139 { 00140 $fname = "Title::newFromID"; 00141 $row = wfGetArray( "cur", array( "cur_namespace", "cur_title" ), 00142 array( "cur_id" => $id ), $fname ); 00143 if ( $row !== false ) { 00144 $title = Title::makeTitle( $row->cur_namespace, $row->cur_title ); 00145 } else { 00146 $title = NULL; 00147 } 00148 return $title; 00149 } 00150 00151 # From a namespace index and a DB key 00152 /* static */ function makeTitle( $ns, $title ) 00153 { 00154 $t = new Title(); 00155 $t->mDbkeyform = Title::makeName( $ns, $title ); 00156 if( $t->secureAndSplit() ) { 00157 return $t; 00158 } else { 00159 return NULL; 00160 } 00161 } 00162 00163 function newMainPage() 00164 { 00165 return Title::newFromText( wfMsg( "mainpage" ) ); 00166 } 00167 00168 #---------------------------------------------------------------------------- 00169 # Static functions 00170 #---------------------------------------------------------------------------- 00171 00172 # Get the prefixed DB key associated with an ID 00173 /* static */ function nameOf( $id ) 00174 { 00175 $sql = "SELECT cur_namespace,cur_title FROM cur WHERE " . 00176 "cur_id={$id}"; 00177 $res = wfQuery( $sql, DB_READ, "Article::nameOf" ); 00178 if ( 0 == wfNumRows( $res ) ) { return NULL; } 00179 00180 $s = wfFetchObject( $res ); 00181 $n = Title::makeName( $s->cur_namespace, $s->cur_title ); 00182 return $n; 00183 } 00184 00185 # Get a regex character class describing the legal characters in a link 00186 /* static */ function legalChars() 00187 { 00188 # Missing characters: 00189 # * []|# Needed for link syntax 00190 # * % and + are corrupted by Apache when they appear in the path 00191 # * % seems to work though 00192 # 00193 # Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but 00194 # this breaks interlanguage links 00195 00196 $set = " %!\"$&'()*,\\-.\\/0-9:;<=>?@A-Z\\\\^_`a-z{}~\\x80-\\xFF"; 00197 return $set; 00198 } 00199 00200 # Returns a stripped-down a title string ready for the search index 00201 # Takes a namespace index and a text-form main part 00202 /* static */ function indexTitle( $ns, $title ) 00203 { 00204 global $wgDBminWordLen, $wgLang; 00205 00206 $lc = SearchEngine::legalSearchChars() . "&#;"; 00207 $t = $wgLang->stripForSearch( $title ); 00208 $t = preg_replace( "/[^{$lc}]+/", " ", $t ); 00209 $t = strtolower( $t ); 00210 00211 # Handle 's, s' 00212 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t ); 00213 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t ); 00214 00215 $t = preg_replace( "/\\s+/", " ", $t ); 00216 00217 if ( $ns == Namespace::getImage() ) { 00218 $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t ); 00219 } 00220 return trim( $t ); 00221 } 00222 00223 # Make a prefixed DB key from a DB key and a namespace index 00224 /* static */ function makeName( $ns, $title ) 00225 { 00226 global $wgLang; 00227 00228 $n = $wgLang->getNsText( $ns ); 00229 if ( "" == $n ) { return $title; } 00230 else { return "{$n}:{$title}"; } 00231 } 00232 00233 # Arguably static 00234 # Returns the URL associated with an interwiki prefix 00235 # The URL contains $1, which is replaced by the title 00236 function getInterwikiLink( $key ) 00237 { 00238 global $wgMemc, $wgDBname, $wgInterwikiExpiry, $wgTitleInterwikiCache; 00239 00240 $k = "$wgDBname:interwiki:$key"; 00241 00242 if( array_key_exists( $k, $wgTitleInterwikiCache ) ) 00243 return $wgTitleInterwikiCache[$k]->iw_url; 00244 00245 $s = $wgMemc->get( $k ); 00246 # Ignore old keys with no iw_local 00247 if( $s && isset( $s->iw_local ) ) { 00248 $wgTitleInterwikiCache[$k] = $s; 00249 return $s->iw_url; 00250 } 00251 $dkey = wfStrencode( $key ); 00252 $query = "SELECT iw_url,iw_local FROM interwiki WHERE iw_prefix='$dkey'"; 00253 $res = wfQuery( $query, DB_READ, "Title::getInterwikiLink" ); 00254 if(!$res) return ""; 00255 00256 $s = wfFetchObject( $res ); 00257 if(!$s) { 00258 $s = (object)false; 00259 $s->iw_url = ""; 00260 } 00261 $wgMemc->set( $k, $s, $wgInterwikiExpiry ); 00262 $wgTitleInterwikiCache[$k] = $s; 00263 return $s->iw_url; 00264 } 00265 00266 function isLocal() { 00267 global $wgTitleInterwikiCache, $wgDBname; 00268 00269 if ( $this->mInterwiki != "" ) { 00270 # Make sure key is loaded into cache 00271 $this->getInterwikiLink( $this->mInterwiki ); 00272 $k = "$wgDBname:interwiki:" . $this->mInterwiki; 00273 return (bool)($wgTitleInterwikiCache[$k]->iw_local); 00274 } else { 00275 return true; 00276 } 00277 } 00278 00279 # Update the cur_touched field for an array of title objects 00280 # Inefficient unless the IDs are already loaded into the link cache 00281 /* static */ function touchArray( $titles, $timestamp = "" ) { 00282 if ( count( $titles ) == 0 ) { 00283 return; 00284 } 00285 if ( $timestamp == "" ) { 00286 $timestamp = wfTimestampNow(); 00287 } 00288 $sql = "UPDATE cur SET cur_touched='{$timestamp}' WHERE cur_id IN ("; 00289 $first = true; 00290 00291 foreach ( $titles as $title ) { 00292 if ( ! $first ) { 00293 $sql .= ","; 00294 } 00295 00296 $first = false; 00297 $sql .= $title->getArticleID(); 00298 } 00299 $sql .= ")"; 00300 if ( ! $first ) { 00301 wfQuery( $sql, DB_WRITE, "Title::touchArray" ); 00302 } 00303 } 00304 00305 #---------------------------------------------------------------------------- 00306 # Other stuff 00307 #---------------------------------------------------------------------------- 00308 00309 # Simple accessors 00310 # See the definitions at the top of this file 00311 00312 function getText() { return $this->mTextform; } 00313 function getPartialURL() { return $this->mUrlform; } 00314 function getDBkey() { return $this->mDbkeyform; } 00315 function getNamespace() { return $this->mNamespace; } 00316 function setNamespace( $n ) { $this->mNamespace = $n; } 00317 function getInterwiki() { return $this->mInterwiki; } 00318 function getFragment() { return $this->mFragment; } 00319 function getDefaultNamespace() { return $this->mDefaultNamespace; } 00320 00321 # Get title for search index 00322 function getIndexTitle() 00323 { 00324 return Title::indexTitle( $this->mNamespace, $this->mTextform ); 00325 } 00326 00327 # Get prefixed title with underscores 00328 function getPrefixedDBkey() 00329 { 00330 $s = $this->prefix( $this->mDbkeyform ); 00331 $s = str_replace( " ", "_", $s ); 00332 return $s; 00333 } 00334 00335 # Get prefixed title with spaces 00336 # This is the form usually used for display 00337 function getPrefixedText() 00338 { 00339 if ( empty( $this->mPrefixedText ) ) { 00340 $s = $this->prefix( $this->mTextform ); 00341 $s = str_replace( "_", " ", $s ); 00342 $this->mPrefixedText = $s; 00343 } 00344 return $this->mPrefixedText; 00345 } 00346 00347 # Get a URL-encoded title (not an actual URL) including interwiki 00348 function getPrefixedURL() 00349 { 00350 $s = $this->prefix( $this->mDbkeyform ); 00351 $s = str_replace( " ", "_", $s ); 00352 00353 $s = wfUrlencode ( $s ) ; 00354 00355 # Cleaning up URL to make it look nice -- is this safe? 00356 $s = preg_replace( "/%3[Aa]/", ":", $s ); 00357 $s = preg_replace( "/%2[Ff]/", "/", $s ); 00358 $s = str_replace( "%28", "(", $s ); 00359 $s = str_replace( "%29", ")", $s ); 00360 00361 return $s; 00362 } 00363 00364 # Get a real URL referring to this title, with interwiki link and fragment 00365 function getFullURL( $query = "" ) 00366 { 00367 global $wgLang, $wgArticlePath, $wgServer, $wgScript; 00368 00369 if ( "" == $this->mInterwiki ) { 00370 $p = $wgArticlePath; 00371 return $wgServer . $this->getLocalUrl( $query ); 00372 } 00373 00374 $p = $this->getInterwikiLink( $this->mInterwiki ); 00375 $n = $wgLang->getNsText( $this->mNamespace ); 00376 if ( "" != $n ) { $n .= ":"; } 00377 $u = str_replace( "$1", $n . $this->mUrlform, $p ); 00378 if ( "" != $this->mFragment ) { 00379 $u .= "#" . wfUrlencode( $this->mFragment ); 00380 } 00381 return $u; 00382 } 00383 00384 # Get a URL with an optional query string, no fragment 00385 # * If $query=="", it will use $wgArticlePath 00386 # * Returns a full for an interwiki link, loses any query string 00387 # * Optionally adds the server and escapes for HTML 00388 # * Setting $query to "-" makes an old-style URL with nothing in the 00389 # query except a title 00390 00391 function getURL() { 00392 die( "Call to obsolete obsolete function Title::getURL()" ); 00393 } 00394 00395 function getLocalURL( $query = "" ) 00396 { 00397 global $wgLang, $wgArticlePath, $wgScript; 00398 00399 if ( $this->isExternal() ) { 00400 return $this->getFullURL(); 00401 } 00402 00403 $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); 00404 if ( $query == "" ) { 00405 $url = str_replace( "$1", $dbkey, $wgArticlePath ); 00406 } else { 00407 if ( $query == "-" ) { 00408 $query = ""; 00409 } 00410 if ( $wgScript != "" ) { 00411 $url = "{$wgScript}?title={$dbkey}&{$query}"; 00412 } else { 00413 # Top level wiki 00414 $url = "/{$dbkey}?{$query}"; 00415 } 00416 } 00417 return $url; 00418 } 00419 00420 function escapeLocalURL( $query = "" ) { 00421 return wfEscapeHTML( $this->getLocalURL( $query ) ); 00422 } 00423 00424 function escapeFullURL( $query = "" ) { 00425 return wfEscapeHTML( $this->getFullURL( $query ) ); 00426 } 00427 00428 function getInternalURL( $query = "" ) { 00429 # Used in various Squid-related code, in case we have a different 00430 # internal hostname for the server than the exposed one. 00431 global $wgInternalServer; 00432 return $wgInternalServer . $this->getLocalURL( $query ); 00433 } 00434 00435 # Get the edit URL, or a null string if it is an interwiki link 00436 function getEditURL() 00437 { 00438 global $wgServer, $wgScript; 00439 00440 if ( "" != $this->mInterwiki ) { return ""; } 00441 $s = $this->getLocalURL( "action=edit" ); 00442 00443 return $s; 00444 } 00445 00446 # Get HTML-escaped displayable text 00447 # For the title field in <a> tags 00448 function getEscapedText() 00449 { 00450 return wfEscapeHTML( $this->getPrefixedText() ); 00451 } 00452 00453 # Is the title interwiki? 00454 function isExternal() { return ( "" != $this->mInterwiki ); } 00455 00456 # Does the title correspond to a protected article? 00457 function isProtected() 00458 { 00459 if ( -1 == $this->mNamespace ) { return true; } 00460 $a = $this->getRestrictions(); 00461 if ( in_array( "sysop", $a ) ) { return true; } 00462 return false; 00463 } 00464 00465 # Is the page a log page, i.e. one where the history is messed up by 00466 # LogPage.php? This used to be used for suppressing diff links in recent 00467 # changes, but now that's done by setting a flag in the recentchanges 00468 # table. Hence, this probably is no longer used. 00469 function isLog() 00470 { 00471 if ( $this->mNamespace != Namespace::getWikipedia() ) { 00472 return false; 00473 } 00474 if ( ( 0 == strcmp( wfMsg( "uploadlogpage" ), $this->mDbkeyform ) ) || 00475 ( 0 == strcmp( wfMsg( "dellogpage" ), $this->mDbkeyform ) ) ) { 00476 return true; 00477 } 00478 return false; 00479 } 00480 00481 # Is $wgUser is watching this page? 00482 function userIsWatching() 00483 { 00484 global $wgUser; 00485 00486 if ( -1 == $this->mNamespace ) { return false; } 00487 if ( 0 == $wgUser->getID() ) { return false; } 00488 00489 return $wgUser->isWatched( $this ); 00490 } 00491 00492 # Can $wgUser edit this page? 00493 function userCanEdit() 00494 { 00495 global $wgUser; 00496 if ( -1 == $this->mNamespace ) { return false; } 00497 if ( NS_MEDIAWIKI == $this->mNamespace && !$wgUser->isSysop() ) { return false; } 00498 # if ( 0 == $this->getArticleID() ) { return false; } 00499 if ( $this->mDbkeyform == "_" ) { return false; } 00500 # protect global styles and js 00501 if ( NS_MEDIAWIKI == $this->mNamespace 00502 && preg_match("/\\.(css|js)$/", $this->mTextform ) 00503 && !$wgUser->isSysop() ) 00504 { return false; } 00505 //if ( $this->isCssJsSubpage() and !$this->userCanEditCssJsSubpage() ) { return false; } 00506 # protect css/js subpages of user pages 00507 # XXX: this might be better using restrictions 00508 # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working 00509 if( Namespace::getUser() == $this->mNamespace 00510 and preg_match("/\\.(css|js)$/", $this->mTextform ) 00511 and !$wgUser->isSysop() 00512 and !preg_match("/^".preg_quote($wgUser->getName(), '/')."/", $this->mTextform) ) 00513 { return false; } 00514 $ur = $wgUser->getRights(); 00515 foreach ( $this->getRestrictions() as $r ) { 00516 if ( "" != $r && ( ! in_array( $r, $ur ) ) ) { 00517 return false; 00518 } 00519 } 00520 return true; 00521 } 00522 00523 function userCanRead() { 00524 global $wgUser; 00525 global $wgWhitelistRead; 00526 00527 if( 0 != $wgUser->getID() ) return true; 00528 if( !is_array( $wgWhitelistRead ) ) return true; 00529 00530 $name = $this->getPrefixedText(); 00531 if( in_array( $name, $wgWhitelistRead ) ) return true; 00532 00533 # Compatibility with old settings 00534 if( $this->getNamespace() == NS_ARTICLE ) { 00535 if( in_array( ":" . $name, $wgWhitelistRead ) ) return true; 00536 } 00537 return false; 00538 } 00539 00540 function isCssJsSubpage() { 00541 return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.(css|js)$/", $this->mTextform ) ); 00542 } 00543 function isCssSubpage() { 00544 return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.css$/", $this->mTextform ) ); 00545 } 00546 function isJsSubpage() { 00547 return ( Namespace::getUser() == $this->mNamespace and preg_match("/\\.js$/", $this->mTextform ) ); 00548 } 00549 function userCanEditCssJsSubpage() { 00550 # protect css/js subpages of user pages 00551 # XXX: this might be better using restrictions 00552 global $wgUser; 00553 return ( $wgUser->isSysop() or preg_match("/^".preg_quote($wgUser->getName())."/", $this->mTextform) ); 00554 } 00555 00556 # Accessor/initialisation for mRestrictions 00557 function getRestrictions() 00558 { 00559 $id = $this->getArticleID(); 00560 if ( 0 == $id ) { return array(); } 00561 00562 if ( ! $this->mRestrictionsLoaded ) { 00563 $res = wfGetSQL( "cur", "cur_restrictions", "cur_id=$id" ); 00564 $this->mRestrictions = explode( ",", trim( $res ) ); 00565 $this->mRestrictionsLoaded = true; 00566 } 00567 return $this->mRestrictions; 00568 } 00569 00570 # Is there a version of this page in the deletion archive? 00571 function isDeleted() { 00572 $ns = $this->getNamespace(); 00573 $t = wfStrencode( $this->getDBkey() ); 00574 $sql = "SELECT COUNT(*) AS n FROM archive WHERE ar_namespace=$ns AND ar_title='$t'"; 00575 if( $res = wfQuery( $sql, DB_READ ) ) { 00576 $s = wfFetchObject( $res ); 00577 return $s->n; 00578 } 00579 return 0; 00580 } 00581 00582 # Get the article ID from the link cache 00583 # Used very heavily, e.g. in Parser::replaceInternalLinks() 00584 function getArticleID() 00585 { 00586 global $wgLinkCache; 00587 00588 if ( -1 != $this->mArticleID ) { return $this->mArticleID; } 00589 $this->mArticleID = $wgLinkCache->addLinkObj( $this ); 00590 return $this->mArticleID; 00591 } 00592 00593 # This clears some fields in this object, and clears any associated keys in the 00594 # "bad links" section of $wgLinkCache. This is called from Article::insertNewArticle() 00595 # to allow loading of the new cur_id. It's also called from Article::doDeleteArticle() 00596 function resetArticleID( $newid ) 00597 { 00598 global $wgLinkCache; 00599 $wgLinkCache->clearBadLink( $this->getPrefixedDBkey() ); 00600 00601 if ( 0 == $newid ) { $this->mArticleID = -1; } 00602 else { $this->mArticleID = $newid; } 00603 $this->mRestrictionsLoaded = false; 00604 $this->mRestrictions = array(); 00605 } 00606 00607 # Updates cur_touched 00608 # Called from LinksUpdate.php 00609 function invalidateCache() { 00610 $now = wfTimestampNow(); 00611 $ns = $this->getNamespace(); 00612 $ti = wfStrencode( $this->getDBkey() ); 00613 $sql = "UPDATE cur SET cur_touched='$now' WHERE cur_namespace=$ns AND cur_title='$ti'"; 00614 return wfQuery( $sql, DB_WRITE, "Title::invalidateCache" ); 00615 } 00616 00617 # Prefixes some arbitrary text with the namespace or interwiki prefix of this object 00618 /* private */ function prefix( $name ) 00619 { 00620 global $wgLang; 00621 00622 $p = ""; 00623 if ( "" != $this->mInterwiki ) { 00624 $p = $this->mInterwiki . ":"; 00625 } 00626 if ( 0 != $this->mNamespace ) { 00627 $p .= $wgLang->getNsText( $this->mNamespace ) . ":"; 00628 } 00629 return $p . $name; 00630 } 00631 00632 # Secure and split - main initialisation function for this object 00633 # 00634 # Assumes that mDbkeyform has been set, and is urldecoded 00635 # and uses undersocres, but not otherwise munged. This function 00636 # removes illegal characters, splits off the winterwiki and 00637 # namespace prefixes, sets the other forms, and canonicalizes 00638 # everything. 00639 # 00640 /* private */ function secureAndSplit() 00641 { 00642 global $wgLang, $wgLocalInterwiki, $wgCapitalLinks; 00643 $fname = "Title::secureAndSplit"; 00644 wfProfileIn( $fname ); 00645 00646 static $imgpre = false; 00647 static $rxTc = false; 00648 00649 # Initialisation 00650 if ( $imgpre === false ) { 00651 $imgpre = ":" . $wgLang->getNsText( Namespace::getImage() ) . ":"; 00652 # % is needed as well 00653 $rxTc = "/[^" . Title::legalChars() . "]/"; 00654 } 00655 00656 $this->mInterwiki = $this->mFragment = ""; 00657 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN 00658 00659 # Clean up whitespace 00660 # 00661 $t = preg_replace( "/[\\s_]+/", "_", $this->mDbkeyform ); 00662 $t = preg_replace( '/^_*(.*?)_*$/', '$1', $t ); 00663 00664 if ( "" == $t ) { 00665 wfProfileOut( $fname ); 00666 return false; 00667 } 00668 00669 $this->mDbkeyform = $t; 00670 $done = false; 00671 00672 # :Image: namespace 00673 if ( 0 == strncasecmp( $imgpre, $t, strlen( $imgpre ) ) ) { 00674 $t = substr( $t, 1 ); 00675 } 00676 00677 # Initial colon indicating main namespace 00678 if ( ":" == $t{0} ) { 00679 $r = substr( $t, 1 ); 00680 $this->mNamespace = NS_MAIN; 00681 } else { 00682 # Namespace or interwiki prefix 00683 if ( preg_match( "/^(.+?)_*:_*(.*)$/", $t, $m ) ) { 00684 #$p = strtolower( $m[1] ); 00685 $p = $m[1]; 00686 $lowerNs = strtolower( $p ); 00687 if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) { 00688 # Canonical namespace 00689 $t = $m[2]; 00690 $this->mNamespace = $ns; 00691 } elseif ( $ns = $wgLang->getNsIndex( $lowerNs )) { 00692 # Ordinary namespace 00693 $t = $m[2]; 00694 $this->mNamespace = $ns; 00695 } elseif ( $this->getInterwikiLink( $p ) ) { 00696 # Interwiki link 00697 $t = $m[2]; 00698 $this->mInterwiki = $p; 00699 00700 if ( !preg_match( "/^([A-Za-z0-9_\\x80-\\xff]+):(.*)$/", $t, $m ) ) { 00701 $done = true; 00702 } elseif($this->mInterwiki != $wgLocalInterwiki) { 00703 $done = true; 00704 } 00705 } 00706 } 00707 $r = $t; 00708 } 00709 00710 # Redundant interwiki prefix to the local wiki 00711 if ( 0 == strcmp( $this->mInterwiki, $wgLocalInterwiki ) ) { 00712 $this->mInterwiki = ""; 00713 } 00714 # We already know that some pages won't be in the database! 00715 # 00716 if ( "" != $this->mInterwiki || -1 == $this->mNamespace ) { 00717 $this->mArticleID = 0; 00718 } 00719 $f = strstr( $r, "#" ); 00720 if ( false !== $f ) { 00721 $this->mFragment = substr( $f, 1 ); 00722 $r = substr( $r, 0, strlen( $r ) - strlen( $f ) ); 00723 } 00724 00725 # Reject illegal characters. 00726 # 00727 if( preg_match( $rxTc, $r ) ) { 00728 return false; 00729 } 00730 00731 # "." and ".." conflict with the directories of those namesa 00732 if ( $r === "." || $r === ".." || strpos( $r, "./" ) !== false ) { 00733 return false; 00734 } 00735 00736 # Initial capital letter 00737 if( $wgCapitalLinks && $this->mInterwiki == "") { 00738 $t = $wgLang->ucfirst( $r ); 00739 } 00740 00741 # Fill fields 00742 $this->mDbkeyform = $t; 00743 $this->mUrlform = wfUrlencode( $t ); 00744 00745 $this->mTextform = str_replace( "_", " ", $t ); 00746 00747 wfProfileOut( $fname ); 00748 return true; 00749 } 00750 00751 # Get a title object associated with the talk page of this article 00752 function getTalkPage() { 00753 return Title::makeTitle( Namespace::getTalk( $this->getNamespace() ), $this->getDBkey() ); 00754 } 00755 00756 # Get a title object associated with the subject page of this talk page 00757 function getSubjectPage() { 00758 return Title::makeTitle( Namespace::getSubject( $this->getNamespace() ), $this->getDBkey() ); 00759 } 00760 00761 # Get an array of Title objects linking to this title 00762 # Also stores the IDs in the link cache 00763 function getLinksTo() { 00764 global $wgLinkCache; 00765 $id = $this->getArticleID(); 00766 $sql = "SELECT cur_namespace,cur_title,cur_id FROM cur,links WHERE l_from=cur_id AND l_to={$id}"; 00767 $res = wfQuery( $sql, DB_READ, "Title::getLinksTo" ); 00768 $retVal = array(); 00769 if ( wfNumRows( $res ) ) { 00770 while ( $row = wfFetchObject( $res ) ) { 00771 if ( $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title ) ) { 00772 $wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() ); 00773 $retVal[] = $titleObj; 00774 } 00775 } 00776 } 00777 wfFreeResult( $res ); 00778 return $retVal; 00779 } 00780 00781 # Get an array of Title objects linking to this non-existent title 00782 # Also stores the IDs in the link cache 00783 function getBrokenLinksTo() { 00784 global $wgLinkCache; 00785 $encTitle = wfStrencode( $this->getPrefixedDBkey() ); 00786 $sql = "SELECT cur_namespace,cur_title,cur_id FROM brokenlinks,cur " . 00787 "WHERE bl_from=cur_id AND bl_to='$encTitle'"; 00788 $res = wfQuery( $sql, DB_READ, "Title::getBrokenLinksTo" ); 00789 $retVal = array(); 00790 if ( wfNumRows( $res ) ) { 00791 while ( $row = wfFetchObject( $res ) ) { 00792 $titleObj = Title::makeTitle( $row->cur_namespace, $row->cur_title ); 00793 $wgLinkCache->addGoodLink( $row->cur_id, $titleObj->getPrefixedDBkey() ); 00794 $retVal[] = $titleObj; 00795 } 00796 } 00797 wfFreeResult( $res ); 00798 return $retVal; 00799 } 00800 00801 function getSquidURLs() { 00802 return array( 00803 $this->getInternalURL(), 00804 $this->getInternalURL( "action=history" ) 00805 ); 00806 } 00807 00808 function moveNoAuth( &$nt ) { 00809 return $this->moveTo( $nt, false ); 00810 } 00811 00812 # Move a title to a new location 00813 # Returns true on success, message name on failure 00814 # auth indicates whether wgUser's permissions should be checked 00815 function moveTo( &$nt, $auth = true ) { 00816 if( !$this or !$nt ) { 00817 return "badtitletext"; 00818 } 00819 00820 $fname = "Title::move"; 00821 $oldid = $this->getArticleID(); 00822 $newid = $nt->getArticleID(); 00823 00824 if ( strlen( $nt->getDBkey() ) < 1 ) { 00825 return "articleexists"; 00826 } 00827 if ( ( ! Namespace::isMovable( $this->getNamespace() ) ) || 00828 ( "" == $this->getDBkey() ) || 00829 ( "" != $this->getInterwiki() ) || 00830 ( !$oldid ) || 00831 ( ! Namespace::isMovable( $nt->getNamespace() ) ) || 00832 ( "" == $nt->getDBkey() ) || 00833 ( "" != $nt->getInterwiki() ) ) { 00834 return "badarticleerror"; 00835 } 00836 00837 if ( $auth && ( !$this->userCanEdit() || !$nt->userCanEdit() ) ) { 00838 return "protectedpage"; 00839 } 00840 00841 # The move is allowed only if (1) the target doesn't exist, or 00842 # (2) the target is a redirect to the source, and has no history 00843 # (so we can undo bad moves right after they're done). 00844 00845 if ( 0 != $newid ) { # Target exists; check for validity 00846 if ( ! $this->isValidMoveTarget( $nt ) ) { 00847 return "articleexists"; 00848 } 00849 $this->moveOverExistingRedirect( $nt ); 00850 } else { # Target didn't exist, do normal move. 00851 $this->moveToNewTitle( $nt, $newid ); 00852 } 00853 00854 # Update watchlists 00855 00856 $oldnamespace = $this->getNamespace() & ~1; 00857 $newnamespace = $nt->getNamespace() & ~1; 00858 $oldtitle = $this->getDBkey(); 00859 $newtitle = $nt->getDBkey(); 00860 00861 if( $oldnamespace != $newnamespace && $oldtitle != $newtitle ) { 00862 WatchedItem::duplicateEntries( $this, $nt ); 00863 } 00864 00865 # Update search engine 00866 $u = new SearchUpdate( $oldid, $nt->getPrefixedDBkey() ); 00867 $u->doUpdate(); 00868 $u = new SearchUpdate( $newid, $this->getPrefixedDBkey(), "" ); 00869 $u->doUpdate(); 00870 00871 return true; 00872 } 00873 00874 # Move page to title which is presently a redirect to the source page 00875 00876 /* private */ function moveOverExistingRedirect( &$nt ) 00877 { 00878 global $wgUser, $wgLinkCache, $wgUseSquid, $wgMwRedir; 00879 $fname = "Title::moveOverExistingRedirect"; 00880 $comment = wfMsg( "1movedto2", $this->getPrefixedText(), $nt->getPrefixedText() ); 00881 00882 $now = wfTimestampNow(); 00883 $won = wfInvertTimestamp( $now ); 00884 $newid = $nt->getArticleID(); 00885 $oldid = $this->getArticleID(); 00886 00887 # Change the name of the target page: 00888 wfUpdateArray( 00889 /* table */ 'cur', 00890 /* SET */ array( 00891 'cur_touched' => $now, 00892 'cur_namespace' => $nt->getNamespace(), 00893 'cur_title' => $nt->getDBkey() 00894 ), 00895 /* WHERE */ array( 'cur_id' => $oldid ), 00896 $fname 00897 ); 00898 $wgLinkCache->clearLink( $nt->getPrefixedDBkey() ); 00899 00900 # Repurpose the old redirect. We don't save it to history since 00901 # by definition if we've got here it's rather uninteresting. 00902 00903 $redirectText = $wgMwRedir->getSynonym( 0 ) . " [[" . $nt->getPrefixedText() . "]]\n"; 00904 wfUpdateArray( 00905 /* table */ 'cur', 00906 /* SET */ array( 00907 'cur_touched' => $now, 00908 'cur_timestamp' => $now, 00909 'inverse_timestamp' => $won, 00910 'cur_namespace' => $this->getNamespace(), 00911 'cur_title' => $this->getDBkey(), 00912 'cur_text' => $wgMwRedir->getSynonym( 0 ) . " [[" . $nt->getPrefixedText() . "]]\n", 00913 'cur_comment' => $comment, 00914 'cur_user' => $wgUser->getID(), 00915 'cur_minor_edit' => 0, 00916 'cur_counter' => 0, 00917 'cur_restrictions' => '', 00918 'cur_user_text' => $wgUser->getName(), 00919 'cur_is_redirect' => 1, 00920 'cur_is_new' => 1 00921 ), 00922 /* WHERE */ array( 'cur_id' => $newid ), 00923 $fname 00924 ); 00925 00926 $wgLinkCache->clearLink( $this->getPrefixedDBkey() ); 00927 00928 # Fix the redundant names for the past revisions of the target page. 00929 # The redirect should have no old revisions. 00930 wfUpdateArray( 00931 /* table */ 'old', 00932 /* SET */ array( 00933 'old_namespace' => $nt->getNamespace(), 00934 'old_title' => $nt->getDBkey(), 00935 ), 00936 /* WHERE */ array( 00937 'old_namespace' => $this->getNamespace(), 00938 'old_title' => $this->getDBkey(), 00939 ), 00940 $fname 00941 ); 00942 00943 RecentChange::notifyMoveOverRedirect( $now, $this, $nt, $wgUser, $comment ); 00944 00945 # Swap links 00946 00947 # Load titles and IDs 00948 $linksToOld = $this->getLinksTo(); 00949 $linksToNew = $nt->getLinksTo(); 00950 00951 # Delete them all 00952 $sql = "DELETE FROM links WHERE l_to=$oldid OR l_to=$newid"; 00953 wfQuery( $sql, DB_WRITE, $fname ); 00954 00955 # Reinsert 00956 if ( count( $linksToOld ) || count( $linksToNew )) { 00957 $sql = "INSERT INTO links (l_from,l_to) VALUES "; 00958 $first = true; 00959 00960 # Insert links to old title 00961 foreach ( $linksToOld as $linkTitle ) { 00962 if ( $first ) { 00963 $first = false; 00964 } else { 00965 $sql .= ","; 00966 } 00967 $id = $linkTitle->getArticleID(); 00968 $sql .= "($id,$newid)"; 00969 } 00970 00971 # Insert links to new title 00972 foreach ( $linksToNew as $linkTitle ) { 00973 if ( $first ) { 00974 $first = false; 00975 } else { 00976 $sql .= ","; 00977 } 00978 $id = $linkTitle->getArticleID(); 00979 $sql .= "($id, $oldid)"; 00980 } 00981 00982 wfQuery( $sql, DB_WRITE, $fname ); 00983 } 00984 00985 # Now, we record the link from the redirect to the new title. 00986 # It should have no other outgoing links... 00987 $sql = "DELETE FROM links WHERE l_from={$newid}"; 00988 wfQuery( $sql, DB_WRITE, $fname ); 00989 $sql = "INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})"; 00990 wfQuery( $sql, DB_WRITE, $fname ); 00991 00992 # Clear linkscc 00993 LinkCache::linksccClearLinksTo( $oldid ); 00994 LinkCache::linksccClearLinksTo( $newid ); 00995 00996 # Purge squid 00997 if ( $wgUseSquid ) { 00998 $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() ); 00999 $u = new SquidUpdate( $urls ); 01000 $u->doUpdate(); 01001 } 01002 } 01003 01004 # Move page to non-existing title. 01005 # Sets $newid to be the new article ID 01006 01007 /* private */ function moveToNewTitle( &$nt, &$newid ) 01008 { 01009 global $wgUser, $wgLinkCache, $wgUseSquid; 01010 $fname = "MovePageForm::moveToNewTitle"; 01011 $comment = wfMsg( "1movedto2", $this->getPrefixedText(), $nt->getPrefixedText() ); 01012 01013 $now = wfTimestampNow(); 01014 $won = wfInvertTimestamp( $now ); 01015 $newid = $nt->getArticleID(); 01016 $oldid = $this->getArticleID(); 01017 01018 # Rename cur entry 01019 wfUpdateArray( 01020 /* table */ 'cur', 01021 /* SET */ array( 01022 'cur_touched' => $now, 01023 'cur_namespace' => $nt->getNamespace(), 01024 'cur_title' => $nt->getDBkey() 01025 ), 01026 /* WHERE */ array( 'cur_id' => $oldid ), 01027 $fname 01028 ); 01029 01030 $wgLinkCache->clearLink( $nt->getPrefixedDBkey() ); 01031 01032 # Insert redirct 01033 wfInsertArray( 'cur', array( 01034 'cur_namespace' => $this->getNamespace(), 01035 'cur_title' => $this->getDBkey(), 01036 'cur_comment' => $comment, 01037 'cur_user' => $wgUser->getID(), 01038 'cur_user_text' => $wgUser->getName(), 01039 'cur_timestamp' => $now, 01040 'inverse_timestamp' => $won, 01041 'cur_touched' => $now, 01042 'cur_is_redirect' => 1, 01043 'cur_is_new' => 1, 01044 'cur_text' => "#REDIRECT [[" . $nt->getPrefixedText() . "]]\n" ) 01045 ); 01046 $newid = wfInsertId(); 01047 $wgLinkCache->clearLink( $this->getPrefixedDBkey() ); 01048 01049 # Rename old entries 01050 wfUpdateArray( 01051 /* table */ 'old', 01052 /* SET */ array( 01053 'old_namespace' => $nt->getNamespace(), 01054 'old_title' => $nt->getDBkey() 01055 ), 01056 /* WHERE */ array( 01057 'old_namespace' => $this->getNamespace(), 01058 'old_title' => $this->getDBkey() 01059 ), $fname 01060 ); 01061 01062 # Record in RC 01063 RecentChange::notifyMoveToNew( $now, $this, $nt, $wgUser, $comment ); 01064 01065 # Purge squid and linkscc as per article creation 01066 Article::onArticleCreate( $nt ); 01067 01068 # Any text links to the old title must be reassigned to the redirect 01069 $sql = "UPDATE links SET l_to={$newid} WHERE l_to={$oldid}"; 01070 wfQuery( $sql, DB_WRITE, $fname ); 01071 LinkCache::linksccClearLinksTo( $oldid ); 01072 01073 # Record the just-created redirect's linking to the page 01074 $sql = "INSERT INTO links (l_from,l_to) VALUES ({$newid},{$oldid})"; 01075 wfQuery( $sql, DB_WRITE, $fname ); 01076 01077 # Non-existent target may have had broken links to it; these must 01078 # now be removed and made into good links. 01079 $update = new LinksUpdate( $oldid, $nt->getPrefixedDBkey() ); 01080 $update->fixBrokenLinks(); 01081 01082 # Purge old title from squid 01083 # The new title, and links to the new title, are purged in Article::onArticleCreate() 01084 $titles = $nt->getLinksTo(); 01085 if ( $wgUseSquid ) { 01086 $urls = $this->getSquidURLs(); 01087 foreach ( $titles as $linkTitle ) { 01088 $urls[] = $linkTitle->getInternalURL(); 01089 } 01090 $u = new SquidUpdate( $urls ); 01091 $u->doUpdate(); 01092 } 01093 } 01094 01095 # Checks if $this can be moved to $nt 01096 # Both titles must exist in the database, otherwise it will blow up 01097 function isValidMoveTarget( $nt ) 01098 { 01099 $fname = "Title::isValidMoveTarget"; 01100 01101 # Is it a redirect? 01102 $id = $nt->getArticleID(); 01103 $sql = "SELECT cur_is_redirect,cur_text FROM cur " . 01104 "WHERE cur_id={$id}"; 01105 $res = wfQuery( $sql, DB_READ, $fname ); 01106 $obj = wfFetchObject( $res ); 01107 01108 if ( 0 == $obj->cur_is_redirect ) { 01109 # Not a redirect 01110 return false; 01111 } 01112 01113 # Does the redirect point to the source? 01114 if ( preg_match( "/\\[\\[\\s*([^\\]]*)]]/", $obj->cur_text, $m ) ) { 01115 $redirTitle = Title::newFromText( $m[1] ); 01116 if ( 0 != strcmp( $redirTitle->getPrefixedDBkey(), $this->getPrefixedDBkey() ) ) { 01117 return false; 01118 } 01119 } 01120 01121 # Does the article have a history? 01122 $row = wfGetArray( 'old', array( 'old_id' ), array( 01123 'old_namespace' => $nt->getNamespace(), 01124 'old_title' => $nt->getDBkey() ) 01125 ); 01126 01127 # Return true if there was no history 01128 return $row === false; 01129 } 01130 01131 # Create a redirect, fails if the title already exists, does not notify RC 01132 # Returns success 01133 function createRedirect( $dest, $comment ) { 01134 global $wgUser; 01135 if ( $this->getArticleID() ) { 01136 return false; 01137 } 01138 01139 $now = wfTimestampNow(); 01140 $won = wfInvertTimestamp( $now ); 01141 01142 wfInsertArray( 'cur', array( 01143 'cur_namespace' => $this->getNamespace(), 01144 'cur_title' => $this->getDBkey(), 01145 'cur_comment' => $comment, 01146 'cur_user' => $wgUser->getID(), 01147 'cur_user_text' => $wgUser->getName(), 01148 'cur_timestamp' => $now, 01149 'inverse_timestamp' => $won, 01150 'cur_touched' => $now, 01151 'cur_is_redirect' => 1, 01152 'cur_is_new' => 1, 01153 'cur_text' => "#REDIRECT [[" . $dest->getPrefixedText() . "]]\n" 01154 )); 01155 $newid = wfInsertId(); 01156 $this->resetArticleID( $newid ); 01157 01158 # Link table 01159 if ( $dest->getArticleID() ) { 01160 wfInsertArray( 'links', array( 01161 'l_to' => $dest->getArticleID(), 01162 'l_from' => $newid 01163 )); 01164 } else { 01165 wfInsertArray( 'brokenlinks', array( 01166 'bl_to' => $dest->getPrefixedDBkey(), 01167 'bl_from' => $newid 01168 )); 01169 } 01170 01171 Article::onArticleCreate( $this ); 01172 return true; 01173 } 01174 01175 # Get categories to wich belong this title and return an array of 01176 # categories names. 01177 function getParentCategories( ) 01178 { 01179 global $wgLang,$wgUser; 01180 01181 #$titlekey = wfStrencode( $this->getArticleID() ); 01182 $titlekey = $this->getArticleId(); 01183 $cns = Namespace::getCategory(); 01184 $sk =& $wgUser->getSkin(); 01185 $parents = array(); 01186 01187 # get the parents categories of this title from the database 01188 $sql = "SELECT DISTINCT cur_id FROM cur,categorylinks 01189 WHERE cl_from='$titlekey' AND cl_to=cur_title AND cur_namespace='$cns' 01190 ORDER BY cl_sortkey" ; 01191 $res = wfQuery ( $sql, DB_READ ) ; 01192 01193 if(wfNumRows($res) > 0) { 01194 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ; 01195 wfFreeResult ( $res ) ; 01196 } else { 01197 $data = ''; 01198 } 01199 return $data; 01200 } 01201 01202 # will get the parents and grand-parents 01203 # TODO : not sure what's happening when a loop happen like: 01204 # Encyclopedia > Astronomy > Encyclopedia 01205 function getAllParentCategories(&$stack) 01206 { 01207 global $wgUser,$wgLang; 01208 $result = ''; 01209 01210 # getting parents 01211 $parents = $this->getParentCategories( ); 01212 01213 if($parents == '') 01214 { 01215 # The current element has no more parent so we dump the stack 01216 # and make a clean line of categories 01217 $sk =& $wgUser->getSkin() ; 01218 01219 foreach ( array_reverse($stack) as $child => $parent ) 01220 { 01221 # make a link of that parent 01222 $result .= $sk->makeLink($wgLang->getNSText ( Namespace::getCategory() ).":".$parent,$parent); 01223 $result .= ' &gt; '; 01224 $lastchild = $child; 01225 } 01226 # append the last child. 01227 # TODO : We should have a last child unless there is an error in the 01228 # "categorylinks" table. 01229 if(isset($lastchild)) { $result .= $lastchild; } 01230 01231 $result .= "<br/>\n"; 01232 01233 # now we can empty the stack 01234 $stack = array(); 01235 01236 } else { 01237 # look at parents of current category 01238 foreach($parents as $parent) 01239 { 01240 # create a title object for the parent 01241 $tpar = Title::newFromID($parent->cur_id); 01242 # add it to the stack 01243 $stack[$this->getText()] = $tpar->getText(); 01244 # grab its parents 01245 $result .= $tpar->getAllParentCategories($stack); 01246 } 01247 } 01248 01249 if(isset($result)) { return $result; } 01250 else { return ''; }; 01251 } 01252 01253 01254 } 01255 ?>

Generated on Tue Jun 29 23:40:07 2004 for Mediawiki by doxygen 1.3.7