X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FTitle.php;h=cf79780f2c4e47980a0908270e70addfb7989faa;hb=dfaa474400f1739914a26a4dfdd0fe3d667abe44;hp=31842aeec62735414d980917126b788eb785414b;hpb=11337038b38b7b2bc586444ce82215fbef74f787;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Title.php b/includes/Title.php index 31842aeec6..cf79780f2c 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -8,9 +8,6 @@ /** */ require_once( 'normal/UtfNormal.php' ); -$wgTitleInterwikiCache = array(); -$wgTitleCache = array(); - define ( 'GAID_FOR_UPDATE', 1 ); # Title::newFromTitle maintains a cache to avoid @@ -28,13 +25,20 @@ define( 'MW_TITLECACHE_MAX', 1000 ); * @package MediaWiki */ class Title { + /** + * Static cache variables + */ + static private $titleCache=array(); + static private $interwikiCache=array(); + + /** * All member variables should be considered private * Please use the accessor functions */ /**#@+ - * @access private + * @private */ var $mTextform; # Text form (spaces not underscores) of the main part @@ -50,14 +54,14 @@ class Title { var $mRestrictionsLoaded; # Boolean for initialisation on demand var $mPrefixedText; # Text form including namespace/interwiki, initialised on demand var $mDefaultNamespace; # Namespace index when there is no namespace - # Zero except in {{transclusion}} tags - var $mWatched; # Is $wgUser watching this page? NULL if unfilled, accessed through userIsWatching() + # Zero except in {{transclusion}} tags + var $mWatched; # Is $wgUser watching this page? NULL if unfilled, accessed through userIsWatching() /**#@-*/ /** * Constructor - * @access private + * @private */ /* private */ function Title() { $this->mInterwiki = $this->mUrlform = @@ -104,12 +108,11 @@ class Title { * @static * @access public */ - function newFromText( $text, $defaultNamespace = NS_MAIN ) { - global $wgTitleCache; + public static function newFromText( $text, $defaultNamespace = NS_MAIN ) { $fname = 'Title::newFromText'; if( is_object( $text ) ) { - wfDebugDieBacktrace( 'Title::newFromText given an object' ); + throw new MWException( 'Title::newFromText given an object' ); } /** @@ -120,8 +123,8 @@ class Title { * * In theory these are value objects and won't get changed... */ - if( $defaultNamespace == NS_MAIN && isset( $wgTitleCache[$text] ) ) { - return $wgTitleCache[$text]; + if( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) { + return Title::$titleCache[$text]; } /** @@ -129,7 +132,7 @@ class Title { */ $filteredText = Sanitizer::decodeCharReferences( $text ); - $t =& new Title(); + $t = new Title(); $t->mDbkeyform = str_replace( ' ', '_', $filteredText ); $t->mDefaultNamespace = $defaultNamespace; @@ -138,11 +141,11 @@ class Title { if( $defaultNamespace == NS_MAIN ) { if( $cachedcount >= MW_TITLECACHE_MAX ) { # Avoid memory leaks on mass operations... - $wgTitleCache = array(); + Title::$titleCache = array(); $cachedcount=0; } $cachedcount++; - $wgTitleCache[$text] =& $t; + Title::$titleCache[$text] =& $t; } return $t; } else { @@ -202,6 +205,21 @@ class Title { return $title; } + /** + * Make an array of titles from an array of IDs + */ + function newFromIDs( $ids ) { + $dbr =& wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ), + 'page_id IN (' . $dbr->makeList( $ids ) . ')', __METHOD__ ); + + $titles = array(); + while ( $row = $dbr->fetchObject( $res ) ) { + $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); + } + return $titles; + } + /** * Create a new Title from a namespace index and a DB key. * It's assumed that $ns and $title are *valid*, for instance when @@ -215,8 +233,8 @@ class Title { * @static * @access public */ - function &makeTitle( $ns, $title ) { - $t =& new Title(); + public static function &makeTitle( $ns, $title ) { + $t = new Title(); $t->mInterwiki = ''; $t->mFragment = ''; $t->mNamespace = intval( $ns ); @@ -228,7 +246,7 @@ class Title { } /** - * Create a new Title frrom a namespace index and a DB key. + * Create a new Title from a namespace index and a DB key. * The parameters will be checked for validity, which is a bit slower * than makeTitle() but safer for user-provided data. * @@ -238,7 +256,7 @@ class Title { * @static * @access public */ - function makeTitleSafe( $ns, $title ) { + public static function makeTitleSafe( $ns, $title ) { $t = new Title(); $t->mDbkeyform = Title::makeName( $ns, $title ); if( $t->secureAndSplit() ) { @@ -255,7 +273,7 @@ class Title { * @return Title the new object * @access public */ - function newMainPage() { + public static function newMainPage() { return Title::newFromText( wfMsgForContent( 'mainpage' ) ); } @@ -267,10 +285,10 @@ class Title { * @static * @access public */ - function newFromRedirect( $text ) { - global $wgMwRedir; + public static function newFromRedirect( $text ) { + $mwRedir = MagicWord::get( 'redirect' ); $rt = NULL; - if ( $wgMwRedir->matchStart( $text ) ) { + if ( $mwRedir->matchStart( $text ) ) { if ( preg_match( '/\[{2}(.*?)(?:\||\]{2})/', $text, $m ) ) { # categories are escaped using : for example one can enter: # #REDIRECT [[:Category:Music]]. Need to remove it. @@ -318,7 +336,7 @@ class Title { * @static * @access public */ - function legalChars() { + public static function legalChars() { global $wgLegalTitleChars; return $wgLegalTitleChars; } @@ -334,7 +352,6 @@ class Title { */ /* static */ function indexTitle( $ns, $title ) { global $wgContLang; - require_once( 'SearchEngine.php' ); $lc = SearchEngine::legalSearchChars() . '&#;'; $t = $wgContLang->stripForSearch( $title ); @@ -359,7 +376,7 @@ class Title { * @param string $title the DB key form the title * @return string the prefixed form of the title */ - /* static */ function makeName( $ns, $title ) { + public static function makeName( $ns, $title ) { global $wgContLang; $n = $wgContLang->getNsText( $ns ); @@ -375,15 +392,15 @@ class Title { * @access public */ function getInterwikiLink( $key ) { - global $wgMemc, $wgDBname, $wgInterwikiExpiry, $wgTitleInterwikiCache; + global $wgMemc, $wgDBname, $wgInterwikiExpiry; global $wgInterwikiCache; $fname = 'Title::getInterwikiLink'; $key = strtolower( $key ); $k = $wgDBname.':interwiki:'.$key; - if( array_key_exists( $k, $wgTitleInterwikiCache ) ) { - return $wgTitleInterwikiCache[$k]->iw_url; + if( array_key_exists( $k, Title::$interwikiCache ) ) { + return Title::$interwikiCache[$k]->iw_url; } if ($wgInterwikiCache) { @@ -393,7 +410,7 @@ class Title { $s = $wgMemc->get( $k ); # Ignore old keys with no iw_local if( $s && isset( $s->iw_local ) && isset($s->iw_trans)) { - $wgTitleInterwikiCache[$k] = $s; + Title::$interwikiCache[$k] = $s; return $s->iw_url; } @@ -414,7 +431,7 @@ class Title { $s->iw_trans = 0; } $wgMemc->set( $k, $s, $wgInterwikiExpiry ); - $wgTitleInterwikiCache[$k] = $s; + Title::$interwikiCache[$k] = $s; return $s->iw_url; } @@ -429,7 +446,6 @@ class Title { */ function getInterwikiCached( $key ) { global $wgDBname, $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite; - global $wgTitleInterwikiCache; static $db, $site; if (!$db) @@ -441,11 +457,11 @@ class Title { $site = $wgInterwikiFallbackSite; } $value = dba_fetch("{$wgDBname}:{$key}", $db); - if ($value=='' and $wgInterwikiScopes>=3) { + if ($value=='' and $wgInterwikiScopes>=3) { /* try site-level */ $value = dba_fetch("_{$site}:{$key}", $db); } - if ($value=='' and $wgInterwikiScopes>=2) { + if ($value=='' and $wgInterwikiScopes>=2) { /* try globals */ $value = dba_fetch("__global:{$key}", $db); } @@ -460,7 +476,7 @@ class Title { $s->iw_url=$url; $s->iw_local=(int)$local; } - $wgTitleInterwikiCache[$wgDBname.':interwiki:'.$key] = $s; + Title::$interwikiCache[$wgDBname.':interwiki:'.$key] = $s; return $s->iw_url; } /** @@ -472,13 +488,13 @@ class Title { * @access public */ function isLocal() { - global $wgTitleInterwikiCache, $wgDBname; + global $wgDBname; if ( $this->mInterwiki != '' ) { # Make sure key is loaded into cache $this->getInterwikiLink( $this->mInterwiki ); $k = $wgDBname.':interwiki:' . $this->mInterwiki; - return (bool)($wgTitleInterwikiCache[$k]->iw_local); + return (bool)(Title::$interwikiCache[$k]->iw_local); } else { return true; } @@ -492,14 +508,14 @@ class Title { * @access public */ function isTrans() { - global $wgTitleInterwikiCache, $wgDBname; + global $wgDBname; if ($this->mInterwiki == '') return false; # Make sure key is loaded into cache $this->getInterwikiLink( $this->mInterwiki ); $k = $wgDBname.':interwiki:' . $this->mInterwiki; - return (bool)($wgTitleInterwikiCache[$k]->iw_trans); + return (bool)(Title::$interwikiCache[$k]->iw_trans); } /** @@ -604,6 +620,23 @@ class Title { return $wgContLang->getNsText( Namespace::getSubject( $this->mNamespace ) ); } + /** + * Get the namespace text of the talk page + * @return string + */ + function getTalkNsText() { + global $wgContLang; + return( $wgContLang->getNsText( Namespace::getTalk( $this->mNamespace ) ) ); + } + + /** + * Could this title have a corresponding talk page? + * @return bool + */ + function canTalk() { + return( Namespace::canTalk( $this->mNamespace ) ); + } + /** * Get the interwiki prefix (or null string) * @return string @@ -674,19 +707,47 @@ class Title { return $text; } + /** + * Get the base name, i.e. the leftmost parts before the / + * @return string Base name + */ + function getBaseText() { + global $wgNamespacesWithSubpages; + if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) { + $parts = explode( '/', $this->getText() ); + # Don't discard the real title if there's no subpage involved + if( count( $parts ) > 1 ) + unset( $parts[ count( $parts ) - 1 ] ); + return implode( '/', $parts ); + } else { + return $this->getText(); + } + } + /** * Get the lowest-level subpage name, i.e. the rightmost part after / * @return string Subpage name */ function getSubpageText() { global $wgNamespacesWithSubpages; - if( $wgNamespacesWithSubpages[ $this->mNamespace ] ) { + if( isset( $wgNamespacesWithSubpages[ $this->mNamespace ] ) && $wgNamespacesWithSubpages[ $this->mNamespace ] ) { $parts = explode( '/', $this->mTextform ); return( $parts[ count( $parts ) - 1 ] ); } else { return( $this->mTextform ); } } + + /** + * Get a URL-encoded form of the subpage text + * @return string URL-encoded subpage name + */ + function getSubpageUrlForm() { + $text = $this->getSubpageText(); + $text = wfUrlencode( str_replace( ' ', '_', $text ) ); + $text = str_replace( '%28', '(', str_replace( '%29', ')', $text ) ); # Clean up the URL; per below, this might not be safe + return( $text ); + } /** * Get a URL-encoded title (not an actual URL) including interwiki @@ -743,10 +804,13 @@ class Title { } $url .= $query; } - if ( '' != $this->mFragment ) { - $url .= '#' . $this->mFragment; - } } + + # Finally, add the fragment. + if ( '' != $this->mFragment ) { + $url .= '#' . $this->mFragment; + } + wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) ); return $url; } @@ -765,8 +829,8 @@ class Title { if ( $this->isExternal() ) { $url = $this->getFullURL(); if ( $query ) { - // This is currently only used for edit section links in the - // context of interwiki transclusion. In theory we should + // This is currently only used for edit section links in the + // context of interwiki transclusion. In theory we should // append the query to the end of any existing query string, // but interwiki transclusion is already broken in that case. $url .= "?$query"; @@ -947,7 +1011,7 @@ class Title { * Can $wgUser perform $action this page? * @param string $action action that permission needs to be checked for * @return boolean - * @access private + * @private */ function userCan($action) { $fname = 'Title::userCan'; @@ -955,8 +1019,9 @@ class Title { global $wgUser; - $result = true; - if ( !wfRunHooks( 'userCan', array( &$this, &$wgUser, $action, &$result ) ) ) { + $result = null; + wfRunHooks( 'userCan', array( &$this, &$wgUser, $action, &$result ) ); + if ( $result !== null ) { wfProfileOut( $fname ); return $result; } @@ -979,14 +1044,6 @@ class Title { return false; } - # protect global styles and js - if ( NS_MEDIAWIKI == $this->mNamespace - && preg_match("/\\.(css|js)$/", $this->mTextform ) - && !$wgUser->isAllowed('editinterface') ) { - wfProfileOut( $fname ); - return false; - } - # protect css/js subpages of user pages # XXX: this might be better using restrictions # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working @@ -1018,6 +1075,7 @@ class Title { if( $action == 'create' ) { if( ( $this->isTalkPage() && !$wgUser->isAllowed( 'createtalk' ) ) || ( !$this->isTalkPage() && !$wgUser->isAllowed( 'createpage' ) ) ) { + wfProfileOut( $fname ); return false; } } @@ -1035,6 +1093,15 @@ class Title { return $this->userCan('edit'); } + /** + * Can $wgUser create this page? + * @return boolean + * @access public + */ + function userCanCreate() { + return $this->userCan('create'); + } + /** * Can $wgUser move this page? * @return boolean @@ -1064,8 +1131,9 @@ class Title { function userCanRead() { global $wgUser; - $result = true; - if ( !wfRunHooks( 'userCan', array( &$this, &$wgUser, "read", &$result ) ) ) { + $result = null; + wfRunHooks( 'userCan', array( &$this, &$wgUser, 'read', &$result ) ); + if ( $result !== null ) { return $result; } @@ -1120,8 +1188,12 @@ class Title { * Check that the corresponding skin exists */ function isValidCssJsSubpage() { - global $wgValidSkinNames; - return( $this->isCssJsSubpage() && array_key_exists( $this->getSkinFromCssJsSubpage(), $wgValidSkinNames ) ); + if ( $this->isCssJsSubpage() ) { + $skinNames = Skin::getSkinNames(); + return array_key_exists( $this->getSkinFromCssJsSubpage(), $skinNames ); + } else { + return false; + } } /** * Trim down a .css or .js subpage title to get the corresponding skin name @@ -1213,6 +1285,10 @@ class Title { $dbr =& wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), $fname ); + if( $this->getNamespace() == NS_IMAGE ) { + $n += $dbr->selectField( 'filearchive', 'COUNT(*)', + array( 'fa_name' => $this->getDBkey() ), $fname ); + } } return (int)$n; } @@ -1307,7 +1383,7 @@ class Title { * * @param string $name the text * @return string the prefixed text - * @access private + * @private */ /* private */ function prefix( $name ) { global $wgContLang; @@ -1331,7 +1407,7 @@ class Title { * namespace prefixes, sets the other forms, and canonicalizes * everything. * @return bool true on success - * @access private + * @private */ /* private */ function secureAndSplit() { global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks; @@ -1407,7 +1483,7 @@ class Title { continue; } - # If there's an initial colon after the interwiki, that also + # If there's an initial colon after the interwiki, that also # resets the default namespace if ( $t !== '' && $t[0] == ':' ) { $this->mNamespace = NS_MAIN; @@ -1487,6 +1563,11 @@ class Title { return false; } + // Any remaining initial :s are illegal. + if ( $t !== '' && ':' == $t{0} ) { + return false; + } + # Fill fields $this->mDbkeyform = $t; $this->mUrlform = wfUrlencode( $t ); @@ -1520,6 +1601,9 @@ class Title { * Get an array of Title objects linking to this Title * Also stores the IDs in the link cache. * + * WARNING: do not use this function on arbitrary user-supplied titles! + * On heavily-used templates it will max out the memory. + * * @param string $options may be FOR UPDATE * @return array the Title objects linking here * @access public @@ -1560,6 +1644,9 @@ class Title { * Get an array of Title objects using this Title as a template * Also stores the IDs in the link cache. * + * WARNING: do not use this function on arbitrary user-supplied titles! + * On heavily-used templates it will max out the memory. + * * @param string $options may be FOR UPDATE * @return array the Title objects linking here * @access public @@ -1621,6 +1708,15 @@ class Title { ); } + function purgeSquid() { + global $wgUseSquid; + if ( $wgUseSquid ) { + $urls = $this->getSquidURLs(); + $u = new SquidUpdate( $urls ); + $u->doUpdate(); + } + } + /** * Move this page without authentication * @param Title &$nt the new page Title @@ -1760,10 +1856,10 @@ class Title { * * @param Title &$nt the page to move to, which should currently * be a redirect - * @access private + * @private */ function moveOverExistingRedirect( &$nt, $reason = '' ) { - global $wgUseSquid, $wgMwRedir; + global $wgUseSquid; $fname = 'Title::moveOverExistingRedirect'; $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); @@ -1802,7 +1898,8 @@ class Title { $linkCache->clearLink( $nt->getPrefixedDBkey() ); # Recreate the redirect, this time in the other direction. - $redirectText = $wgMwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; + $mwRedir = MagicWord::get( 'redirect' ); + $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article( $this ); $newid = $redirectArticle->insertOn( $dbw ); $redirectRevision = new Revision( array( @@ -1838,11 +1935,10 @@ class Title { /** * Move page to non-existing title. * @param Title &$nt the new Title - * @access private + * @private */ function moveToNewTitle( &$nt, $reason = '' ) { global $wgUseSquid; - global $wgMwRedir; $fname = 'MovePageForm::moveToNewTitle'; $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); if ( $reason ) { @@ -1875,7 +1971,8 @@ class Title { $linkCache->clearLink( $nt->getPrefixedDBkey() ); # Insert redirect - $redirectText = $wgMwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; + $mwRedir = MagicWord::get( 'redirect' ); + $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article( $this ); $newid = $redirectArticle->insertOn( $dbw ); $redirectRevision = new Revision( array( @@ -1901,21 +1998,9 @@ class Title { 'pl_title' => $nt->getDBkey() ), $fname ); - # Non-existent target may have had broken links to it; these must - # now be touched to update link coloring. - $nt->touchLinks(); - # Purge old title from squid # The new title, and links to the new title, are purged in Article::onArticleCreate() - $titles = $nt->getLinksTo(); - if ( $wgUseSquid ) { - $urls = $this->getSquidURLs(); - foreach ( $titles as $linkTitle ) { - $urls[] = $linkTitle->getInternalURL(); - } - $u = new SquidUpdate( $urls ); - $u->doUpdate(); - } + $this->purgeSquid(); } /** @@ -1939,19 +2024,24 @@ class Title { if ( !$obj || 0 == $obj->page_is_redirect ) { # Not a redirect + wfDebug( __METHOD__ . ": not a redirect\n" ); return false; } $text = Revision::getRevisionText( $obj ); # Does the redirect point to the source? + # Or is it a broken self-redirect, usually caused by namespace collisions? if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) { $redirTitle = Title::newFromText( $m[1] ); if( !is_object( $redirTitle ) || - $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() ) { + ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() && + $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) { + wfDebug( __METHOD__ . ": redirect points to other page\n" ); return false; } } else { # Fail safe + wfDebug( __METHOD__ . ": failsafe\n" ); return false; } @@ -2058,7 +2148,9 @@ class Title { $stack[$parent] = array(); } else { $nt = Title::newFromText($parent); - $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) ); + if ( $nt ) { + $stack[$parent] = $nt->getParentCategoryTree( $children + array($parent => 1) ); + } } } return $stack; @@ -2138,44 +2230,58 @@ class Title { } /** - * Update page_touched timestamps on pages linking to this title. - * In principal, this could be backgrounded and could also do squid - * purging. + * Update page_touched timestamps and send squid purge messages for + * pages linking to this title. May be sent to the job queue depending + * on the number of links. Typically called on create and delete. */ function touchLinks() { - $fname = 'Title::touchLinks'; - - $dbw =& wfGetDB( DB_MASTER ); - - $res = $dbw->select( 'pagelinks', - array( 'pl_from' ), - array( - 'pl_namespace' => $this->getNamespace(), - 'pl_title' => $this->getDbKey() ), - $fname ); + $u = new HTMLCacheUpdate( $this, 'pagelinks' ); + $u->doUpdate(); - $toucharr = array(); - while( $row = $dbw->fetchObject( $res ) ) { - $toucharr[] = $row->pl_from; + if ( $this->getNamespace() == NS_CATEGORY ) { + $u = new HTMLCacheUpdate( $this, 'categorylinks' ); + $u->doUpdate(); } - $dbw->freeResult( $res ); + } - if( $this->getNamespace() == NS_CATEGORY ) { - // Categories show up in a separate set of links as well - $res = $dbw->select( 'categorylinks', - array( 'cl_from' ), - array( 'cl_to' => $this->getDbKey() ), - $fname ); - while( $row = $dbw->fetchObject( $res ) ) { - $toucharr[] = $row->cl_from; + /** + * Get the last touched timestamp + */ + function getTouched() { + $dbr =& wfGetDB( DB_SLAVE ); + $touched = $dbr->selectField( 'page', 'page_touched', + array( + 'page_namespace' => $this->getNamespace(), + 'page_title' => $this->getDBkey() + ), __METHOD__ + ); + return $touched; + } + + /** + * Get a cached value from a global cache that is invalidated when this page changes + * @param string $key the key + * @param callback $callback A callback function which generates the value on cache miss + */ + function getRelatedCache( $memc, $key, $expiry, $callback, $params = array() ) { + $touched = $this->getTouched(); + $cacheEntry = $memc->get( $key ); + if ( $cacheEntry ) { + if ( $cacheEntry['touched'] >= $touched ) { + return $cacheEntry['value']; + } else { + wfDebug( __METHOD__.": $key expired\n" ); } - $dbw->freeResult( $res ); + } else { + wfDebug( __METHOD__.": $key not found\n" ); } - - if (!count($toucharr)) - return; - $dbw->update( 'page', /* SET */ array( 'page_touched' => $dbw->timestamp() ), - /* WHERE */ array( 'page_id' => $toucharr ),$fname); + $value = call_user_func_array( $callback, $params ); + $cacheEntry = array( + 'value' => $value, + 'touched' => $touched + ); + $memc->set( $key, $cacheEntry, $expiry ); + return $value; } function trackbackURL() { @@ -2201,5 +2307,44 @@ class Title { trackback:ping=\"$tburl\" /> "; } + + /** + * Generate strings used for xml 'id' names in monobook tabs + * @return string + */ + function getNamespaceKey() { + switch ($this->getNamespace()) { + case NS_MAIN: + case NS_TALK: + return 'nstab-main'; + case NS_USER: + case NS_USER_TALK: + return 'nstab-user'; + case NS_MEDIA: + return 'nstab-media'; + case NS_SPECIAL: + return 'nstab-special'; + case NS_PROJECT: + case NS_PROJECT_TALK: + return 'nstab-project'; + case NS_IMAGE: + case NS_IMAGE_TALK: + return 'nstab-image'; + case NS_MEDIAWIKI: + case NS_MEDIAWIKI_TALK: + return 'nstab-mediawiki'; + case NS_TEMPLATE: + case NS_TEMPLATE_TALK: + return 'nstab-template'; + case NS_HELP: + case NS_HELP_TALK: + return 'nstab-help'; + case NS_CATEGORY: + case NS_CATEGORY_TALK: + return 'nstab-category'; + default: + return 'nstab-' . strtolower( $this->getSubjectNsText() ); + } + } } ?>