X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FTitle.php;h=de575cb1d399248f957836b838ec536fdc1a23db;hb=fcd1cdeb930e1779af77b3747b7c60338d4e81d7;hp=a294343d9824f413ad739442e14d1a8011c000dc;hpb=9acfb88be75ded0229efe56fc84ec89fdb030ef7;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Title.php b/includes/Title.php index a294343d98..de575cb1d3 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1,13 +1,16 @@ mDefaultNamespace = NS_MAIN; $this->mWatched = NULL; + $this->mLatestID = false; } /** @@ -85,7 +90,7 @@ class Title { else return NULL; } - + /** * Create a new Title from text, such as what one would * find in a link. Decodes any HTML entities in the text. @@ -99,14 +104,14 @@ class Title { * @static * @access public */ - function &newFromText( $text, $defaultNamespace = NS_MAIN ) { + function newFromText( $text, $defaultNamespace = NS_MAIN ) { + global $wgTitleCache; $fname = 'Title::newFromText'; - wfProfileIn( $fname ); - + if( is_object( $text ) ) { wfDebugDieBacktrace( 'Title::newFromText given an object' ); } - + /** * Wiki pages often contain multiple links to the same page. * Title normalization and parsing can become expensive on @@ -115,10 +120,8 @@ class Title { * * In theory these are value objects and won't get changed... */ - static $titleCache = array(); - if( $defaultNamespace == NS_MAIN && isset( $titleCache[$text] ) ) { - wfProfileOut( $fname ); - return $titleCache[$text]; + if( $defaultNamespace == NS_MAIN && isset( $wgTitleCache[$text] ) ) { + return $wgTitleCache[$text]; } /** @@ -130,19 +133,21 @@ class Title { $t->mDbkeyform = str_replace( ' ', '_', $filteredText ); $t->mDefaultNamespace = $defaultNamespace; + static $cachedcount = 0 ; if( $t->secureAndSplit() ) { if( $defaultNamespace == NS_MAIN ) { - if( count( $titleCache ) >= MW_TITLECACHE_MAX ) { + if( $cachedcount >= MW_TITLECACHE_MAX ) { # Avoid memory leaks on mass operations... - $titleCache = array(); + $wgTitleCache = array(); + $cachedcount=0; } - $titleCache[$text] =& $t; + $cachedcount++; + $wgTitleCache[$text] =& $t; } - wfProfileOut( $fname ); return $t; } else { - wfProfileOut( $fname ); - return NULL; + $ret = NULL; + return $ret; } } @@ -155,22 +160,24 @@ class Title { * @access public */ function newFromURL( $url ) { - global $wgLang, $wgServer; + global $wgLegalTitleChars; $t = new Title(); - - # For compatibility with old buggy URLs. "+" is not valid in titles, + + # For compatibility with old buggy URLs. "+" is usually not valid in titles, # but some URLs used it as a space replacement and they still come # from some external search tools. - $s = str_replace( '+', ' ', $url ); - - $t->mDbkeyform = str_replace( ' ', '_', $s ); + if ( strpos( $wgLegalTitleChars, '+' ) === false ) { + $url = str_replace( '+', ' ', $url ); + } + + $t->mDbkeyform = str_replace( ' ', '_', $url ); if( $t->secureAndSplit() ) { return $t; } else { return NULL; } } - + /** * Create a new Title from an article ID * @@ -185,7 +192,7 @@ class Title { function newFromID( $id ) { $fname = 'Title::newFromID'; $dbr =& wfGetDB( DB_SLAVE ); - $row = $dbr->selectRow( 'page', array( 'page_namespace', 'page_title' ), + $row = $dbr->selectRow( 'page', array( 'page_namespace', 'page_title' ), array( 'page_id' => $id ), $fname ); if ( $row !== false ) { $title = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -194,7 +201,7 @@ class Title { } return $title; } - + /** * Create a new Title from a namespace index and a DB key. * It's assumed that $ns and $title are *valid*, for instance when @@ -212,7 +219,7 @@ class Title { $t =& new Title(); $t->mInterwiki = ''; $t->mFragment = ''; - $t->mNamespace = IntVal( $ns ); + $t->mNamespace = intval( $ns ); $t->mDbkeyform = str_replace( ' ', '_', $title ); $t->mArticleID = ( $ns >= 0 ) ? -1 : 0; $t->mUrlform = wfUrlencode( $t->mDbkeyform ); @@ -271,7 +278,7 @@ class Title { # We don't want to keep the ':' $m[1] = substr( $m[1], 1 ); } - + $rt = Title::newFromText( $m[1] ); # Disallow redirects to Special:Userlogout if ( !is_null($rt) && $rt->getNamespace() == NS_SPECIAL && preg_match( '/^Userlogout/i', $rt->getText() ) ) { @@ -281,7 +288,7 @@ class Title { } return $rt; } - + #---------------------------------------------------------------------------- # Static functions #---------------------------------------------------------------------------- @@ -297,7 +304,7 @@ class Title { function nameOf( $id ) { $fname = 'Title::nameOf'; $dbr =& wfGetDB( DB_SLAVE ); - + $s = $dbr->selectRow( 'page', array( 'page_namespace','page_title' ), array( 'page_id' => $id ), $fname ); if ( $s === false ) { return NULL; } @@ -311,27 +318,11 @@ class Title { * @static * @access public */ - /* static */ function legalChars() { - # Missing characters: - # * []|# Needed for link syntax - # * % and + are corrupted by Apache when they appear in the path - # - # % seems to work though - # - # The problem with % is that URLs are double-unescaped: once by Apache's - # path conversion code, and again by PHP. So %253F, for example, becomes "?". - # Our code does not double-escape to compensate for this, indeed double escaping - # would break if the double-escaped title was passed in the query string - # rather than the path. This is a minor security issue because articles can be - # created such that they are hard to view or edit. -- TS - # - # Theoretically 0x80-0x9F of ISO 8859-1 should be disallowed, but - # this breaks interlanguage links - - $set = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF"; - return $set; + function legalChars() { + global $wgLegalTitleChars; + return $wgLegalTitleChars; } - + /** * Get a string representation of a title suitable for * including in a search index @@ -342,7 +333,7 @@ class Title { * search index */ /* static */ function indexTitle( $ns, $title ) { - global $wgDBminWordLen, $wgContLang; + global $wgContLang; require_once( 'SearchEngine.php' ); $lc = SearchEngine::legalSearchChars() . '&#;'; @@ -361,7 +352,7 @@ class Title { } return trim( $t ); } - + /* * Make a prefixed DB key from a DB key and a namespace index * @param int $ns numerical representation of the namespace @@ -374,7 +365,7 @@ class Title { $n = $wgContLang->getNsText( $ns ); return $n == '' ? $title : "$n:$title"; } - + /** * Returns the URL associated with an interwiki prefix * @param string $key the interwiki prefix (e.g. "MeatBall") @@ -383,53 +374,99 @@ class Title { * @static (arguably) * @access public */ - function getInterwikiLink( $key ) { + function getInterwikiLink( $key ) { global $wgMemc, $wgDBname, $wgInterwikiExpiry, $wgTitleInterwikiCache; + global $wgInterwikiCache; $fname = 'Title::getInterwikiLink'; - - wfProfileIn( $fname ); - + + $key = strtolower( $key ); + $k = $wgDBname.':interwiki:'.$key; if( array_key_exists( $k, $wgTitleInterwikiCache ) ) { - wfProfileOut( $fname ); return $wgTitleInterwikiCache[$k]->iw_url; } - $s = $wgMemc->get( $k ); + if ($wgInterwikiCache) { + return Title::getInterwikiCached( $key ); + } + + $s = $wgMemc->get( $k ); # Ignore old keys with no iw_local - if( $s && isset( $s->iw_local ) ) { + if( $s && isset( $s->iw_local ) && isset($s->iw_trans)) { $wgTitleInterwikiCache[$k] = $s; - wfProfileOut( $fname ); return $s->iw_url; } - + $dbr =& wfGetDB( DB_SLAVE ); $res = $dbr->select( 'interwiki', - array( 'iw_url', 'iw_local' ), + array( 'iw_url', 'iw_local', 'iw_trans' ), array( 'iw_prefix' => $key ), $fname ); if( !$res ) { - wfProfileOut( $fname ); return ''; } - + $s = $dbr->fetchObject( $res ); if( !$s ) { # Cache non-existence: create a blank object and save it to memcached $s = (object)false; $s->iw_url = ''; $s->iw_local = 0; + $s->iw_trans = 0; } $wgMemc->set( $k, $s, $wgInterwikiExpiry ); $wgTitleInterwikiCache[$k] = $s; - - wfProfileOut( $fname ); + + return $s->iw_url; + } + + /** + * Fetch interwiki prefix data from local cache in constant database + * + * More logic is explained in DefaultSettings + * + * @return string URL of interwiki site + * @access public + */ + function getInterwikiCached( $key ) { + global $wgDBname, $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite; + global $wgTitleInterwikiCache; + static $db, $site; + + if (!$db) + $db=dba_open($wgInterwikiCache,'r','cdb'); + /* Resolve site name */ + if ($wgInterwikiScopes>=3 and !$site) { + $site = dba_fetch("__sites:{$wgDBname}", $db); + if ($site=="") + $site = $wgInterwikiFallbackSite; + } + $value = dba_fetch("{$wgDBname}:{$key}", $db); + if ($value=='' and $wgInterwikiScopes>=3) { + /* try site-level */ + $value = dba_fetch("_{$site}:{$key}", $db); + } + if ($value=='' and $wgInterwikiScopes>=2) { + /* try globals */ + $value = dba_fetch("__global:{$key}", $db); + } + if ($value=='undef') + $value=''; + $s = (object)false; + $s->iw_url = ''; + $s->iw_local = 0; + $s->iw_trans = 0; + if ($value!='') { + list($local,$url)=explode(' ',$value,2); + $s->iw_url=$url; + $s->iw_local=(int)$local; + } + $wgTitleInterwikiCache[$wgDBname.':interwiki:'.$key] = $s; return $s->iw_url; } - /** * Determine whether the object refers to a page within - * this project. - * + * this project. + * * @return bool TRUE if this is an in-project interwiki link * or a wikilink, FALSE otherwise * @access public @@ -447,6 +484,24 @@ class Title { } } + /** + * Determine whether the object refers to a page within + * this project and is transcludable. + * + * @return bool TRUE if this is transcludable + * @access public + */ + function isTrans() { + global $wgTitleInterwikiCache, $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); + } + /** * Update the page_touched field for an array of title objects * @todo Inefficient unless the IDs are already loaded into the @@ -458,6 +513,7 @@ class Title { * @access public */ function touchArray( $titles, $timestamp = '' ) { + if ( count( $titles ) == 0 ) { return; } @@ -465,13 +521,19 @@ class Title { if ( $timestamp == '' ) { $timestamp = $dbw->timestamp(); } + /* $page = $dbw->tableName( 'page' ); $sql = "UPDATE $page SET page_touched='{$timestamp}' WHERE page_id IN ("; $first = true; foreach ( $titles as $title ) { - if ( ! $first ) { - $sql .= ','; + if ( $wgUseFileCache ) { + $cm = new CacheManager($title); + @unlink($cm->fileCacheName()); + } + + if ( ! $first ) { + $sql .= ','; } $first = false; $sql .= $title->getArticleID(); @@ -480,6 +542,18 @@ class Title { if ( ! $first ) { $dbw->query( $sql, 'Title::touchArray' ); } + */ + // hack hack hack -- brion 2005-07-11. this was unfriendly to db. + // do them in small chunks: + $fname = 'Title::touchArray'; + foreach( $titles as $title ) { + $dbw->update( 'page', + array( 'page_touched' => $timestamp ), + array( + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDBkey() ), + $fname ); + } } #---------------------------------------------------------------------------- @@ -511,6 +585,25 @@ class Title { * @access public */ function getNamespace() { return $this->mNamespace; } + /** + * Get the namespace text + * @return string + * @access public + */ + function getNsText() { + global $wgContLang; + return $wgContLang->getNsText( $this->mNamespace ); + } + /** + * Get the namespace text of the subject (rather than talk) page + * @return string + * @access public + */ + function getSubjectNsText() { + global $wgContLang; + return $wgContLang->getNsText( Namespace::getSubject( $this->mNamespace ) ); + } + /** * Get the interwiki prefix (or null string) * @return string @@ -558,15 +651,14 @@ class Title { * @access public */ function getPrefixedText() { - global $wgContLang; - if ( empty( $this->mPrefixedText ) ) { + if ( empty( $this->mPrefixedText ) ) { // FIXME: bad usage of empty() ? $s = $this->prefix( $this->mTextform ); $s = str_replace( '_', ' ', $s ); $this->mPrefixedText = $s; } return $this->mPrefixedText; } - + /** * Get the prefixed title with spaces, plus any fragment * (part beginning with '#') @@ -575,7 +667,6 @@ class Title { * @access public */ function getFullText() { - global $wgContLang; $text = $this->getPrefixedText(); if( '' != $this->mFragment ) { $text .= '#' . $this->mFragment; @@ -583,6 +674,20 @@ class Title { return $text; } + /** + * Get the lowest-level subpage name, i.e. the rightmost part after / + * @return string Subpage name + */ + function getSubpageText() { + global $wgNamespacesWithSubpages; + if( $wgNamespacesWithSubpages[ $this->mNamespace ] ) { + $parts = explode( '/', $this->mTextform ); + return( $parts[ count( $parts ) - 1 ] ); + } else { + return( $this->mTextform ); + } + } + /** * Get a URL-encoded title (not an actual URL) including interwiki * @return string the URL-encoded form @@ -593,7 +698,7 @@ class Title { $s = str_replace( ' ', '_', $s ); $s = wfUrlencode ( $s ) ; - + # Cleaning up URL to make it look nice -- is this safe? $s = str_replace( '%28', '(', $s ); $s = str_replace( '%29', ')', $s ); @@ -611,132 +716,94 @@ class Title { * @access public */ function getFullURL( $query = '' ) { - global $wgContLang, $wgServer, $wgScript, $wgMakeDumpLinks, $wgArticlePath; + global $wgContLang, $wgServer, $wgRequest; if ( '' == $this->mInterwiki ) { - return $wgServer . $this->getLocalUrl( $query ); - } elseif ( $wgMakeDumpLinks && $wgContLang->getLanguageName( $this->mInterwiki ) ) { - $baseUrl = str_replace( '$1', "../../{$this->mInterwiki}/$1", $wgArticlePath ); - $baseUrl = str_replace( '$1', $this->getHashedDirectory() . '/$1', $baseUrl ); - } else { - $baseUrl = $this->getInterwikiLink( $this->mInterwiki ); - } - - $namespace = $wgContLang->getNsText( $this->mNamespace ); - if ( '' != $namespace ) { - # Can this actually happen? Interwikis shouldn't be parsed. - $namepace .= ':'; - } - $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl ); - if( $query != '' ) { - if( false === strpos( $url, '?' ) ) { - $url .= '?'; - } else { - $url .= '&'; - } - $url .= $query; - } - if ( '' != $this->mFragment ) { - $url .= '#' . $this->mFragment; - } - return $url; - } - - /** - * Get a relative directory for putting an HTML version of this article into - */ - function getHashedDirectory() { - global $wgMakeDumpLinks, $wgInputEncoding; - $dbkey = $this->getDBkey(); + $url = $this->getLocalUrl( $query ); - # Split into characters - if ( $wgInputEncoding == 'UTF-8' ) { - preg_match_all( '/./us', $dbkey, $m ); + // Ugly quick hack to avoid duplicate prefixes (bug 4571 etc) + // Correct fix would be to move the prepending elsewhere. + if ($wgRequest->getVal('action') != 'render') { + $url = $wgServer . $url; + } } else { - preg_match_all( '/./s', $dbkey, $m ); - } - $chars = $m[0]; - $length = count( $chars ); - $dir = ''; + $baseUrl = $this->getInterwikiLink( $this->mInterwiki ); - for ( $i = 0; $i < $wgMakeDumpLinks; $i++ ) { - if ( $i ) { - $dir .= '/'; + $namespace = $wgContLang->getNsText( $this->mNamespace ); + if ( '' != $namespace ) { + # Can this actually happen? Interwikis shouldn't be parsed. + $namespace .= ':'; } - if ( $i >= $length ) { - $dir .= '_'; - } elseif ( ord( $chars[$i] ) > 32 ) { - $dir .= strtolower( $chars[$i] ); - } else { - $dir .= sprintf( "%02X", ord( $chars[$i] ) ); + $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl ); + if( $query != '' ) { + if( false === strpos( $url, '?' ) ) { + $url .= '?'; + } else { + $url .= '&'; + } + $url .= $query; + } + if ( '' != $this->mFragment ) { + $url .= '#' . $this->mFragment; } } - return $dir; + wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) ); + return $url; } - - function getHashedFilename() { - $dbkey = $this->getPrefixedDBkey(); - $mainPage = Title::newMainPage(); - if ( $mainPage->getPrefixedDBkey() == $dbkey ) { - return 'index.html'; - } - - $dir = $this->getHashedDirectory(); - - # Replace illegal charcters for Windows paths with underscores - $friendlyName = strtr( $dbkey, '/\\*?"<>|~', '_________' ); - # Work out lower case form. We assume we're on a system with case-insensitive - # filenames, so unless the case is of a special form, we have to disambiguate - $lowerCase = $this->prefix( ucfirst( strtolower( $this->getDBkey() ) ) ); - - # Make it mostly unique - if ( $lowerCase != $friendlyName ) { - $friendlyName .= '_' . substr(md5( $dbkey ), 0, 4); - } - # Handle colon specially by replacing it with tilde - # Thus we reduce the number of paths with hashes appended - $friendlyName = str_replace( ':', '~', $friendlyName ); - return "$dir/$friendlyName.html"; - } - /** - * Get a URL with no fragment or server name + * Get a URL with no fragment or server name. If this page is generated + * with action=render, $wgServer is prepended. * @param string $query an optional query string; if not specified, * $wgArticlePath will be used. * @return string the URL * @access public */ function getLocalURL( $query = '' ) { - global $wgLang, $wgArticlePath, $wgScript, $wgMakeDumpLinks; - + global $wgArticlePath, $wgScript, $wgServer, $wgRequest; + if ( $this->isExternal() ) { - return $this->getFullURL(); - } - - $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); - if ( $wgMakeDumpLinks ) { - $url = str_replace( '$1', wfUrlencode( $this->getHashedFilename() ), $wgArticlePath ); - } elseif ( $query == '' ) { - $url = str_replace( '$1', $dbkey, $wgArticlePath ); + $url = $this->getFullURL(); + if ( $query ) { + // 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"; + } } else { - global $wgActionPaths; - if( !empty( $wgActionPaths ) && - preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) ) { - $action = urldecode( $matches[2] ); - if( isset( $wgActionPaths[$action] ) ) { - $query = $matches[1]; - if( isset( $matches[4] ) ) $query .= $matches[4]; - $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] ); - if( $query != '' ) $url .= '?' . $query; - return $url; + $dbkey = wfUrlencode( $this->getPrefixedDBkey() ); + if ( $query == '' ) { + $url = str_replace( '$1', $dbkey, $wgArticlePath ); + } else { + global $wgActionPaths; + $url = false; + if( !empty( $wgActionPaths ) && + preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) ) + { + $action = urldecode( $matches[2] ); + if( isset( $wgActionPaths[$action] ) ) { + $query = $matches[1]; + if( isset( $matches[4] ) ) $query .= $matches[4]; + $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] ); + if( $query != '' ) $url .= '?' . $query; + } + } + if ( $url === false ) { + if ( $query == '-' ) { + $query = ''; + } + $url = "{$wgScript}?title={$dbkey}&{$query}"; } } - if ( $query == '-' ) { - $query = ''; + + // FIXME: this causes breakage in various places when we + // actually expected a local URL and end up with dupe prefixes. + if ($wgRequest->getVal('action') == 'render') { + $url = $wgServer . $url; } - $url = "{$wgScript}?title={$dbkey}&{$query}"; } + wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) ); return $url; } @@ -763,7 +830,7 @@ class Title { return htmlspecialchars( $this->getFullURL( $query ) ); } - /** + /** * Get the URL form for an internal link. * - Used in various Squid-related code, in case we have a different * internal hostname for the server from the exposed one. @@ -774,7 +841,9 @@ class Title { */ function getInternalURL( $query = '' ) { global $wgInternalServer; - return $wgInternalServer . $this->getLocalURL( $query ); + $url = $wgInternalServer . $this->getLocalURL( $query ); + wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) ); + return $url; } /** @@ -784,14 +853,12 @@ class Title { * @access public */ function getEditURL() { - global $wgServer, $wgScript; - if ( '' != $this->mInterwiki ) { return ''; } $s = $this->getLocalURL( 'action=edit' ); return $s; } - + /** * Get the HTML-escaped displayable text form. * Used for the title field in tags. @@ -801,7 +868,7 @@ class Title { function getEscapedText() { return htmlspecialchars( $this->getPrefixedText() ); } - + /** * Is this Title interwiki? * @return boolean @@ -809,6 +876,23 @@ class Title { */ function isExternal() { return ( '' != $this->mInterwiki ); } + /** + * Is this page "semi-protected" - the *only* protection is autoconfirm? + * + * @param string Action to check (default: edit) + * @return bool + */ + function isSemiProtected( $action = 'edit' ) { + $restrictions = $this->getRestrictions( $action ); + # We do a full compare because this could be an array + foreach( $restrictions as $restriction ) { + if( strtolower( $restriction ) != 'autoconfirmed' ) { + return( false ); + } + } + return( true ); + } + /** * Does the title correspond to a protected article? * @param string $what the action the page is protected from, @@ -816,16 +900,28 @@ class Title { * @return boolean * @access public */ - function isProtected($action = '') { + function isProtected( $action = '' ) { + global $wgRestrictionLevels; if ( -1 == $this->mNamespace ) { return true; } - if($action == 'edit' || $action == '') { - $a = $this->getRestrictions("edit"); - if ( in_array( 'sysop', $a ) ) { return true; } - } - if($action == 'move' || $action == '') { - $a = $this->getRestrictions("move"); - if ( in_array( 'sysop', $a ) ) { return true; } - } + + if( $action == 'edit' || $action == '' ) { + $r = $this->getRestrictions( 'edit' ); + foreach( $wgRestrictionLevels as $level ) { + if( in_array( $level, $r ) && $level != '' ) { + return( true ); + } + } + } + + if( $action == 'move' || $action == '' ) { + $r = $this->getRestrictions( 'move' ); + foreach( $wgRestrictionLevels as $level ) { + if( in_array( $level, $r ) && $level != '' ) { + return( true ); + } + } + } + return false; } @@ -848,39 +944,49 @@ class Title { } /** - * Is $wgUser perform $action this page? + * Can $wgUser perform $action this page? * @param string $action action that permission needs to be checked for * @return boolean * @access private */ function userCan($action) { - $fname = 'Title::userCanEdit'; + $fname = 'Title::userCan'; wfProfileIn( $fname ); global $wgUser; + + $result = true; + if ( !wfRunHooks( 'userCan', array( &$this, &$wgUser, $action, &$result ) ) ) { + wfProfileOut( $fname ); + return $result; + } + if( NS_SPECIAL == $this->mNamespace ) { wfProfileOut( $fname ); return false; } + // XXX: This is the code that prevents unprotecting a page in NS_MEDIAWIKI + // from taking effect -ævar if( NS_MEDIAWIKI == $this->mNamespace && !$wgUser->isAllowed('editinterface') ) { wfProfileOut( $fname ); return false; } + if( $this->mDbkeyform == '_' ) { # FIXME: Is this necessary? Shouldn't be allowed anyway... wfProfileOut( $fname ); return false; } - + # protect global styles and js - if ( NS_MEDIAWIKI == $this->mNamespace - && preg_match("/\\.(css|js)$/", $this->mTextform ) + 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 @@ -902,12 +1008,20 @@ class Title { return false; } } - - if( $action == 'move' && !$this->isMovable() ) { + + if( $action == 'move' && + !( $this->isMovable() && $wgUser->isAllowed( 'move' ) ) ) { wfProfileOut( $fname ); return false; } - + + if( $action == 'create' ) { + if( ( $this->isTalkPage() && !$wgUser->isAllowed( 'createtalk' ) ) || + ( !$this->isTalkPage() && !$wgUser->isAllowed( 'createpage' ) ) ) { + return false; + } + } + wfProfileOut( $fname ); return true; } @@ -920,12 +1034,12 @@ class Title { function userCanEdit() { return $this->userCan('edit'); } - + /** * Can $wgUser move this page? * @return boolean * @access public - */ + */ function userCanMove() { return $this->userCan('move'); } @@ -941,7 +1055,7 @@ class Title { return Namespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == ''; } - + /** * Can $wgUser read this page? * @return boolean @@ -949,12 +1063,17 @@ class Title { */ function userCanRead() { global $wgUser; - + + $result = true; + if ( !wfRunHooks( 'userCan', array( &$this, &$wgUser, "read", &$result ) ) ) { + return $result; + } + if( $wgUser->isAllowed('read') ) { return true; } else { global $wgWhitelistRead; - + /** If anon users can create an account, they need to reach the login page first! */ if( $wgUser->isAllowed( 'createaccount' ) @@ -968,7 +1087,7 @@ class Title { if( $wgWhitelistRead && in_array( $name, $wgWhitelistRead ) ) { return true; } - + # Compatibility with old settings if( $wgWhitelistRead && $this->getNamespace() == NS_MAIN ) { if( in_array( ':' . $name, $wgWhitelistRead ) ) { @@ -978,7 +1097,7 @@ class Title { } return false; } - + /** * Is this a talk page of some sort? * @return bool @@ -996,6 +1115,22 @@ class Title { function isCssJsSubpage() { return ( NS_USER == $this->mNamespace and preg_match("/\\.(css|js)$/", $this->mTextform ) ); } + /** + * Is this a *valid* .css or .js subpage of a user page? + * Check that the corresponding skin exists + */ + function isValidCssJsSubpage() { + global $wgValidSkinNames; + return( $this->isCssJsSubpage() && array_key_exists( $this->getSkinFromCssJsSubpage(), $wgValidSkinNames ) ); + } + /** + * Trim down a .css or .js subpage title to get the corresponding skin name + */ + function getSkinFromCssJsSubpage() { + $subpage = explode( '/', $this->mTextform ); + $subpage = $subpage[ count( $subpage ) - 1 ]; + return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) ); + } /** * Is this a .css subpage of a user page? * @return bool @@ -1027,7 +1162,7 @@ class Title { /** * Loads a string into mRestrictions array - * @param string $res restrictions in string format + * @param string $res restrictions in string format * @access public */ function loadRestrictions( $res ) { @@ -1046,7 +1181,7 @@ class Title { /** * Accessor/initialisation for mRestrictions - * @param string $action action that permission needs to be checked for + * @param string $action action that permission needs to be checked for * @return array the array of groups allowed to edit this article * @access public */ @@ -1064,7 +1199,7 @@ class Title { } return array(); } - + /** * Is there a version of this page in the deletion archive? * @return int the number of archived revisions @@ -1076,7 +1211,7 @@ class Title { $n = 0; } else { $dbr =& wfGetDB( DB_SLAVE ); - $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(), + $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), $fname ); } return (int)$n; @@ -1091,23 +1226,33 @@ class Title { * @access public */ function getArticleID( $flags = 0 ) { - global $wgLinkCache; - + $linkCache =& LinkCache::singleton(); if ( $flags & GAID_FOR_UPDATE ) { - $oldUpdate = $wgLinkCache->forUpdate( true ); - $this->mArticleID = $wgLinkCache->addLinkObj( $this ); - $wgLinkCache->forUpdate( $oldUpdate ); + $oldUpdate = $linkCache->forUpdate( true ); + $this->mArticleID = $linkCache->addLinkObj( $this ); + $linkCache->forUpdate( $oldUpdate ); } else { if ( -1 == $this->mArticleID ) { - $this->mArticleID = $wgLinkCache->addLinkObj( $this ); + $this->mArticleID = $linkCache->addLinkObj( $this ); } } return $this->mArticleID; } + function getLatestRevID() { + if ($this->mLatestID !== false) + return $this->mLatestID; + + $db =& wfGetDB(DB_SLAVE); + return $this->mLatestID = $db->selectField( 'revision', + "max(rev_id)", + array('rev_page' => $this->getArticleID()), + 'Title::getLatestRevID' ); + } + /** * This clears some fields in this object, and clears any associated - * keys in the "bad links" section of $wgLinkCache. + * keys in the "bad links" section of the link cache. * * - This is called from Article::insertNewArticle() to allow * loading of the new page_id. It's also called from @@ -1117,35 +1262,42 @@ class Title { * @access public */ function resetArticleID( $newid ) { - global $wgLinkCache; - $wgLinkCache->clearBadLink( $this->getPrefixedDBkey() ); + $linkCache =& LinkCache::singleton(); + $linkCache->clearBadLink( $this->getPrefixedDBkey() ); if ( 0 == $newid ) { $this->mArticleID = -1; } else { $this->mArticleID = $newid; } $this->mRestrictionsLoaded = false; $this->mRestrictions = array(); } - + /** * Updates page_touched for this page; called from LinksUpdate.php * @return bool true if the update succeded * @access public */ function invalidateCache() { + global $wgUseFileCache; + if ( wfReadOnly() ) { return; } - $now = wfTimestampNow(); $dbw =& wfGetDB( DB_MASTER ); - $success = $dbw->update( 'page', - array( /* SET */ + $success = $dbw->update( 'page', + array( /* SET */ 'page_touched' => $dbw->timestamp() - ), array( /* WHERE */ + ), array( /* WHERE */ 'page_namespace' => $this->getNamespace() , 'page_title' => $this->getDBkey() ), 'Title::invalidateCache' ); + + if ($wgUseFileCache) { + $cache = new CacheManager($this); + @unlink($cache->fileCacheName()); + } + return $success; } @@ -1184,8 +1336,7 @@ class Title { /* private */ function secureAndSplit() { global $wgContLang, $wgLocalInterwiki, $wgCapitalLinks; $fname = 'Title::secureAndSplit'; - wfProfileIn( $fname ); - + # Initialisation static $rxTc = false; if( !$rxTc ) { @@ -1202,69 +1353,73 @@ class Title { $t = trim( $t, '_' ); if ( '' == $t ) { - wfProfileOut( $fname ); return false; } - + if( false !== strpos( $t, UTF8_REPLACEMENT ) ) { # Contained illegal UTF-8 sequences or forbidden Unicode chars. - wfProfileOut( $fname ); return false; } $this->mDbkeyform = $t; - # Initial colon indicating main namespace + # Initial colon indicates main namespace rather than specified default + # but should not create invalid {ns,title} pairs such as {0,Project:Foo} if ( ':' == $t{0} ) { - $r = substr( $t, 1 ); $this->mNamespace = NS_MAIN; - } else { - # Namespace or interwiki prefix - $firstPass = true; - do { - if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $t, $m ) ) { - $p = $m[1]; - $lowerNs = strtolower( $p ); - if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) { - # Canonical namespace - $t = $m[2]; - $this->mNamespace = $ns; - } elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) { - # Ordinary namespace - $t = $m[2]; - $this->mNamespace = $ns; - } elseif( $this->getInterwikiLink( $p ) ) { - if( !$firstPass ) { - # Can't make a local interwiki link to an interwiki link. - # That's just crazy! - wfProfileOut( $fname ); + $t = substr( $t, 1 ); # remove the colon but continue processing + } + + # Namespace or interwiki prefix + $firstPass = true; + do { + if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $t, $m ) ) { + $p = $m[1]; + $lowerNs = strtolower( $p ); + if ( $ns = Namespace::getCanonicalIndex( $lowerNs ) ) { + # Canonical namespace + $t = $m[2]; + $this->mNamespace = $ns; + } elseif ( $ns = $wgContLang->getNsIndex( $lowerNs )) { + # Ordinary namespace + $t = $m[2]; + $this->mNamespace = $ns; + } elseif( $this->getInterwikiLink( $p ) ) { + if( !$firstPass ) { + # Can't make a local interwiki link to an interwiki link. + # That's just crazy! + return false; + } + + # Interwiki link + $t = $m[2]; + $this->mInterwiki = strtolower( $p ); + + # Redundant interwiki prefix to the local wiki + if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) { + if( $t == '' ) { + # Can't have an empty self-link return false; } - - # Interwiki link - $t = $m[2]; - $this->mInterwiki = $p; - - # Redundant interwiki prefix to the local wiki - if ( 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) ) { - if( $t == '' ) { - # Can't have an empty self-link - wfProfileOut( $fname ); - return false; - } - $this->mInterwiki = ''; - $firstPass = false; - # Do another namespace split... - continue; - } + $this->mInterwiki = ''; + $firstPass = false; + # Do another namespace split... + continue; + } + + # If there's an initial colon after the interwiki, that also + # resets the default namespace + if ( $t !== '' && $t[0] == ':' ) { + $this->mNamespace = NS_MAIN; + $t = substr( $t, 1 ); } - # If there's no recognized interwiki or namespace, - # then let the colon expression be part of the title. } - break; - } while( true ); - $r = $t; - } + # If there's no recognized interwiki or namespace, + # then let the colon expression be part of the title. + } + break; + } while( true ); + $r = $t; # We already know that some pages won't be in the database! # @@ -1283,10 +1438,9 @@ class Title { # Reject illegal characters. # if( preg_match( $rxTc, $r ) ) { - wfProfileOut( $fname ); return false; } - + /** * Pages with "/./" or "/../" appearing in the URLs will * often be unreachable due to the way web browsers deal @@ -1299,14 +1453,12 @@ class Title { strpos( $r, '/./' ) !== false || strpos( $r, '/../' ) !== false ) ) { - wfProfileOut( $fname ); return false; } # We shouldn't need to query the DB for the size. #$maxSize = $dbr->textFieldSize( 'page', 'page_title' ); if ( strlen( $r ) > 255 ) { - wfProfileOut( $fname ); return false; } @@ -1323,7 +1475,7 @@ class Title { } else { $t = $r; } - + /** * Can't make a link to a namespace alone... * "empty" local links can only be self-links @@ -1332,20 +1484,18 @@ class Title { if( $t == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) { - wfProfileOut( $fname ); return false; } - + # Fill fields $this->mDbkeyform = $t; $this->mUrlform = wfUrlencode( $t ); - + $this->mTextform = str_replace( '_', ' ', $t ); - - wfProfileOut( $fname ); + return true; } - + /** * Get a Title object associated with the talk page of this article * @return Title the object for the talk page @@ -1354,7 +1504,7 @@ class Title { function getTalkPage() { return Title::makeTitle( Namespace::getTalk( $this->getNamespace() ), $this->getDBkey() ); } - + /** * Get a title object associated with the subject page of this * talk page @@ -1370,34 +1520,34 @@ class Title { * Get an array of Title objects linking to this Title * Also stores the IDs in the link cache. * - * @param string $options may be FOR UPDATE + * @param string $options may be FOR UPDATE * @return array the Title objects linking here * @access public */ - function getLinksTo( $options = '' ) { - global $wgLinkCache; + function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) { + $linkCache =& LinkCache::singleton(); $id = $this->getArticleID(); - + if ( $options ) { $db =& wfGetDB( DB_MASTER ); } else { $db =& wfGetDB( DB_SLAVE ); } - - $res = $db->select( array( 'page', 'pagelinks' ), + + $res = $db->select( array( 'page', $table ), array( 'page_namespace', 'page_title', 'page_id' ), array( - 'pl_from=page_id', - 'pl_namespace' => $this->getNamespace(), - 'pl_title' => $this->getDbKey() ), + "{$prefix}_from=page_id", + "{$prefix}_namespace" => $this->getNamespace(), + "{$prefix}_title" => $this->getDbKey() ), 'Title::getLinksTo', $options ); - + $retVal = array(); if ( $db->numRows( $res ) ) { while ( $row = $db->fetchObject( $res ) ) { if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) { - $wgLinkCache->addGoodLinkObj( $row->page_id, $titleObj ); + $linkCache->addGoodLinkObj( $row->page_id, $titleObj ); $retVal[] = $titleObj; } } @@ -1406,22 +1556,32 @@ class Title { return $retVal; } + /** + * Get an array of Title objects using this Title as a template + * Also stores the IDs in the link cache. + * + * @param string $options may be FOR UPDATE + * @return array the Title objects linking here + * @access public + */ + function getTemplateLinksTo( $options = '' ) { + return $this->getLinksTo( $options, 'templatelinks', 'tl' ); + } + /** * Get an array of Title objects referring to non-existent articles linked from this page * - * @param string $options may be FOR UPDATE + * @param string $options may be FOR UPDATE * @return array the Title objects * @access public */ function getBrokenLinksFrom( $options = '' ) { - global $wgLinkCache; - if ( $options ) { $db =& wfGetDB( DB_MASTER ); } else { $db =& wfGetDB( DB_SLAVE ); } - + $res = $db->safeQuery( "SELECT pl_namespace, pl_title FROM ! @@ -1430,12 +1590,12 @@ class Title { AND pl_title=page_title WHERE pl_from=? AND page_namespace IS NULL - !", + !", $db->tableName( 'pagelinks' ), $db->tableName( 'page' ), $this->getArticleId(), $options ); - + $retVal = array(); if ( $db->numRows( $res ) ) { while ( $row = $db->fetchObject( $res ) ) { @@ -1469,7 +1629,7 @@ class Title { function moveNoAuth( &$nt ) { return $this->moveTo( $nt, false ); } - + /** * Check whether a given move operation would be valid. * Returns true if ok, or a message key string for an error message @@ -1480,8 +1640,7 @@ class Title { * @return mixed true on success, message name on failure * @access public */ - function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) { - global $wgUser; + function isValidMoveOperation( &$nt, $auth = true ) { if( !$this or !$nt ) { return 'badtitletext'; } @@ -1492,7 +1651,6 @@ class Title { return 'immobile_namespace'; } - $fname = 'Title::move'; $oldid = $this->getArticleID(); $newid = $nt->getArticleID(); @@ -1510,7 +1668,7 @@ class Title { !$this->userCanMove() || !$nt->userCanMove() ) ) { return 'protectedpage'; } - + # The move is allowed only if (1) the target doesn't exist, or # (2) the target is a redirect to the source, and has no history # (so we can undo bad moves right after they're done). @@ -1522,7 +1680,7 @@ class Title { } return true; } - + /** * Move a title to a new location * @param Title &$nt the new title @@ -1532,17 +1690,17 @@ class Title { * @access public */ function moveTo( &$nt, $auth = true, $reason = '' ) { - $err = $this->isValidMoveOperation( $nt, $auth, $reason ); + $err = $this->isValidMoveOperation( $nt, $auth ); if( is_string( $err ) ) { return $err; } - + $pageid = $this->getArticleID(); if( $nt->exists() ) { $this->moveOverExistingRedirect( $nt, $reason ); $pageCountChange = 0; } else { # Target didn't exist, do normal move. - $this->moveToNewTitle( $nt, $newid, $reason ); + $this->moveToNewTitle( $nt, $reason ); $pageCountChange = 1; } $redirid = $this->getArticleID(); @@ -1556,7 +1714,7 @@ class Title { $dbw->query( $sql, 'SpecialMovepage::doSubmit' ); # Update watchlists - + $oldnamespace = $this->getNamespace() & ~1; $newnamespace = $nt->getNamespace() & ~1; $oldtitle = $this->getDBkey(); @@ -1571,16 +1729,16 @@ class Title { $u->doUpdate(); $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' ); $u->doUpdate(); - + # Update site_stats if ( $this->getNamespace() == NS_MAIN and $nt->getNamespace() != NS_MAIN ) { # Moved out of main namespace # not viewed, edited, removing - $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange); + $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange); } elseif ( $this->getNamespace() != NS_MAIN and $nt->getNamespace() == NS_MAIN ) { # Moved into main namespace # not viewed, edited, adding - $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange ); + $u = new SiteStatsUpdate( 0, 1, +1, $pageCountChange ); } elseif ( $pageCountChange ) { # Added redirect $u = new SiteStatsUpdate( 0, 0, 0, 1 ); @@ -1591,10 +1749,11 @@ class Title { $u->doUpdate(); } + global $wgUser; wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) ); return true; } - + /** * Move page to a title which is at present a redirect to the * source page @@ -1604,62 +1763,60 @@ class Title { * @access private */ function moveOverExistingRedirect( &$nt, $reason = '' ) { - global $wgUser, $wgLinkCache, $wgUseSquid, $wgMwRedir; + global $wgUseSquid, $wgMwRedir; $fname = 'Title::moveOverExistingRedirect'; $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); if ( $reason ) { $comment .= ": $reason"; } - + $now = wfTimestampNow(); $rand = wfRandom(); $newid = $nt->getArticleID(); $oldid = $this->getArticleID(); $dbw =& wfGetDB( DB_MASTER ); - $links = $dbw->tableName( 'links' ); + $linkCache =& LinkCache::singleton(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete( 'page', array( 'page_id' => $newid ), $fname ); - + # Save a null revision in the page's history notifying of the move - $nullRevision = Revision::newNullRevision( $dbw, $oldid, - wfMsg( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ), - true ); + $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true ); $nullRevId = $nullRevision->insertOn( $dbw ); - + # Change the name of the target page: $dbw->update( 'page', - /* SET */ array( - 'page_touched' => $dbw->timestamp($now), + /* SET */ array( + 'page_touched' => $dbw->timestamp($now), 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_latest' => $nullRevId, - ), + ), /* WHERE */ array( 'page_id' => $oldid ), $fname ); - $wgLinkCache->clearLink( $nt->getPrefixedDBkey() ); + $linkCache->clearLink( $nt->getPrefixedDBkey() ); # Recreate the redirect, this time in the other direction. $redirectText = $wgMwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article( $this ); $newid = $redirectArticle->insertOn( $dbw ); $redirectRevision = new Revision( array( - 'page' => $newid, + 'page' => $newid, 'comment' => $comment, 'text' => $redirectText ) ); $revid = $redirectRevision->insertOn( $dbw ); $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 ); - $wgLinkCache->clearLink( $this->getPrefixedDBkey() ); + $linkCache->clearLink( $this->getPrefixedDBkey() ); # Log the move $log = new LogPage( 'move' ); $log->addEntry( 'move_redir', $this, $reason, array( 1 => $nt->getPrefixedText() ) ); - + # Now, we record the link from the redirect to the new title. # It should have no other outgoing links... $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), $fname ); @@ -1669,7 +1826,7 @@ class Title { 'pl_namespace' => $nt->getNamespace(), 'pl_title' => $nt->getDbKey() ), $fname ); - + # Purge squid if ( $wgUseSquid ) { $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() ); @@ -1681,11 +1838,10 @@ class Title { /** * Move page to non-existing title. * @param Title &$nt the new Title - * @param int &$newid set to be the new article ID * @access private */ - function moveToNewTitle( &$nt, &$newid, $reason = '' ) { - global $wgUser, $wgLinkCache, $wgUseSquid; + function moveToNewTitle( &$nt, $reason = '' ) { + global $wgUseSquid; global $wgMwRedir; $fname = 'MovePageForm::moveToNewTitle'; $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); @@ -1697,15 +1853,13 @@ class Title { $oldid = $this->getArticleID(); $dbw =& wfGetDB( DB_MASTER ); $now = $dbw->timestamp(); - wfSeedRandom(); $rand = wfRandom(); + $linkCache =& LinkCache::singleton(); # Save a null revision in the page's history notifying of the move - $nullRevision = Revision::newNullRevision( $dbw, $oldid, - wfMsg( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ), - true ); + $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true ); $nullRevId = $nullRevision->insertOn( $dbw ); - + # Rename cur entry $dbw->update( 'page', /* SET */ array( @@ -1717,20 +1871,20 @@ class Title { /* WHERE */ array( 'page_id' => $oldid ), $fname ); - - $wgLinkCache->clearLink( $nt->getPrefixedDBkey() ); + + $linkCache->clearLink( $nt->getPrefixedDBkey() ); # Insert redirect $redirectText = $wgMwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article( $this ); $newid = $redirectArticle->insertOn( $dbw ); $redirectRevision = new Revision( array( - 'page' => $newid, + 'page' => $newid, 'comment' => $comment, 'text' => $redirectText ) ); $revid = $redirectRevision->insertOn( $dbw ); $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 ); - $wgLinkCache->clearLink( $this->getPrefixedDBkey() ); + $linkCache->clearLink( $this->getPrefixedDBkey() ); # Log the move $log = new LogPage( 'move' ); @@ -1772,44 +1926,48 @@ class Title { * @access public */ function isValidMoveTarget( $nt ) { - + $fname = 'Title::isValidMoveTarget'; $dbw =& wfGetDB( DB_MASTER ); # Is it a redirect? $id = $nt->getArticleID(); $obj = $dbw->selectRow( array( 'page', 'revision', 'text'), - array( 'page_is_redirect','old_text' ), + array( 'page_is_redirect','old_text','old_flags' ), array( 'page_id' => $id, 'page_latest=rev_id', 'rev_text_id=old_id' ), $fname, 'FOR UPDATE' ); - if ( !$obj || 0 == $obj->page_is_redirect ) { + if ( !$obj || 0 == $obj->page_is_redirect ) { # Not a redirect - return false; + return false; } + $text = Revision::getRevisionText( $obj ); # Does the redirect point to the source? - if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $obj->old_text, $m ) ) { + if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) { $redirTitle = Title::newFromText( $m[1] ); if( !is_object( $redirTitle ) || $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() ) { return false; } + } else { + # Fail safe + return false; } # Does the article have a history? $row = $dbw->selectRow( array( 'page', 'revision'), - array( 'rev_id' ), + array( 'rev_id' ), array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_id=rev_page AND page_latest != rev_id' - ), $fname, 'FOR UPDATE' + ), $fname, 'FOR UPDATE' ); # Return true if there was no history return $row === false; } - + /** * Create a redirect; fails if the title already exists; does * not notify RC @@ -1820,14 +1978,13 @@ class Title { * @access public */ function createRedirect( $dest, $comment ) { - global $wgUser; if ( $this->getArticleID() ) { return false; } - + $fname = 'Title::createRedirect'; $dbw =& wfGetDB( DB_MASTER ); - + $article = new Article( $this ); $newid = $article->insertOn( $dbw ); $revision = new Revision( array( @@ -1837,20 +1994,20 @@ class Title { ) ); $revisionId = $revision->insertOn( $dbw ); $article->updateRevisionOn( $dbw, $revision, 0 ); - + # Link table - $dbw->insert( 'pagelinks', + $dbw->insert( 'pagelinks', array( 'pl_from' => $newid, 'pl_namespace' => $dest->getNamespace(), 'pl_title' => $dest->getDbKey() - ), $fname + ), $fname ); Article::onArticleCreate( $this ); return true; } - + /** * Get categories to which this Title belongs and return an array of * categories' names. @@ -1860,11 +2017,9 @@ class Title { * @access public */ function getParentCategories() { - global $wgContLang,$wgUser; - + global $wgContLang; + $titlekey = $this->getArticleId(); - $sk =& $wgUser->getSkin(); - $parents = array(); $dbr =& wfGetDB( DB_SLAVE ); $categorylinks = $dbr->tableName( 'categorylinks' ); @@ -1873,9 +2028,9 @@ class Title { ." WHERE cl_from='$titlekey'" ." AND cl_from <> '0'" ." ORDER BY cl_sortkey"; - + $res = $dbr->query ( $sql ) ; - + if($dbr->numRows($res) > 0) { while ( $x = $dbr->fetchObject ( $res ) ) //$data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$x->cl_to); @@ -1895,10 +2050,9 @@ class Title { */ function getParentCategoryTree( $children = array() ) { $parents = $this->getParentCategories(); - + if($parents != '') { - foreach($parents as $parent => $current) - { + foreach($parents as $parent => $current) { if ( array_key_exists( $parent, $children ) ) { # Circular reference $stack[$parent] = array(); @@ -1912,30 +2066,17 @@ class Title { return array(); } } - - - /** - * Get an associative array for selecting this title from - * the "cur" table - * - * @return array - * @access public - */ - function curCond() { - wfDebugDieBacktrace( 'curCond called' ); - return array( 'cur_namespace' => $this->mNamespace, 'cur_title' => $this->mDbkeyform ); - } + /** - * Get an associative array for selecting this title from the - * "old" table + * Get an associative array for selecting this title from + * the "page" table * * @return array * @access public */ - function oldCond() { - wfDebugDieBacktrace( 'oldCond called' ); - return array( 'old_namespace' => $this->mNamespace, 'old_title' => $this->mDbkeyform ); + function pageCond() { + return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform ); } /** @@ -1947,8 +2088,8 @@ class Title { function getPreviousRevisionID( $revision ) { $dbr =& wfGetDB( DB_SLAVE ); return $dbr->selectField( 'revision', 'rev_id', - 'rev_page=' . IntVal( $this->getArticleId() ) . - ' AND rev_id<' . IntVal( $revision ) . ' ORDER BY rev_id DESC' ); + 'rev_page=' . intval( $this->getArticleId() ) . + ' AND rev_id<' . intval( $revision ) . ' ORDER BY rev_id DESC' ); } /** @@ -1960,22 +2101,23 @@ class Title { function getNextRevisionID( $revision ) { $dbr =& wfGetDB( DB_SLAVE ); return $dbr->selectField( 'revision', 'rev_id', - 'rev_page=' . IntVal( $this->getArticleId() ) . - ' AND rev_id>' . IntVal( $revision ) . ' ORDER BY rev_id' ); + 'rev_page=' . intval( $this->getArticleId() ) . + ' AND rev_id>' . intval( $revision ) . ' ORDER BY rev_id' ); } - + /** * Compare with another title. * * @param Title $title * @return bool */ - function equals( &$title ) { - return $this->getInterwiki() == $title->getInterwiki() + function equals( $title ) { + // Note: === is necessary for proper matching of number-like titles. + return $this->getInterwiki() === $title->getInterwiki() && $this->getNamespace() == $title->getNamespace() - && $this->getDbkey() == $title->getDbkey(); + && $this->getDbkey() === $title->getDbkey(); } - + /** * Check if page exists * @return bool @@ -1986,15 +2128,13 @@ class Title { /** * Should a link should be displayed as a known link, just based on its title? - * - * Currently, a self-link with a fragment, special pages and image pages are in - * this category. Special pages never exist in the database. Some images do not - * have description pages in the database, but the description page contains - * useful history information that the user may want to link to. + * + * Currently, a self-link with a fragment and special pages are in + * this category. Special pages never exist in the database. */ function isAlwaysKnown() { - return $this->isExternal() || ( 0 == $this->mNamespace && "" == $this->mDbkeyform ) - || NS_SPECIAL == $this->mNamespace || NS_IMAGE == $this->mNamespace; + return $this->isExternal() || ( 0 == $this->mNamespace && "" == $this->mDbkeyform ) + || NS_SPECIAL == $this->mNamespace; } /** @@ -2006,25 +2146,99 @@ class Title { $fname = 'Title::touchLinks'; $dbw =& wfGetDB( DB_MASTER ); - + $res = $dbw->select( 'pagelinks', array( 'pl_from' ), array( 'pl_namespace' => $this->getNamespace(), - 'pl_title' => $this->getDbKey() ), + 'pl_title' => $this->getDbKey() ), $fname ); - if ( 0 == $dbw->numRows( $res ) ) { - return; - } - $arr = array(); $toucharr = array(); while( $row = $dbw->fetchObject( $res ) ) { $toucharr[] = $row->pl_from; } - - $dbw->update( 'page', /* SET */ array( 'page_touched' => $dbw->timestamp() ), + $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; + } + $dbw->freeResult( $res ); + } + + if (!count($toucharr)) + return; + $dbw->update( 'page', /* SET */ array( 'page_touched' => $dbw->timestamp() ), /* WHERE */ array( 'page_id' => $toucharr ),$fname); } + + function trackbackURL() { + global $wgTitle, $wgScriptPath, $wgServer; + + return "$wgServer$wgScriptPath/trackback.php?article=" + . htmlspecialchars(urlencode($wgTitle->getPrefixedDBkey())); + } + + function trackbackRDF() { + $url = htmlspecialchars($this->getFullURL()); + $title = htmlspecialchars($this->getText()); + $tburl = $this->trackbackURL(); + + return " + + +"; + } + + /** + * 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-wp'; + 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() ); + } + } } ?>