MediaWiki  REL1_20
Title.php
Go to the documentation of this file.
00001 <?php
00033 class Title {
00035         // @{
00036         static private $titleCache = array();
00037         // @}
00038 
00044         const CACHE_MAX = 1000;
00045 
00050         const GAID_FOR_UPDATE = 1;
00051 
00057         // @{
00058 
00059         var $mTextform = '';              // /< Text form (spaces not underscores) of the main part
00060         var $mUrlform = '';               // /< URL-encoded form of the main part
00061         var $mDbkeyform = '';             // /< Main part with underscores
00062         var $mUserCaseDBKey;              // /< DB key with the initial letter in the case specified by the user
00063         var $mNamespace = NS_MAIN;        // /< Namespace index, i.e. one of the NS_xxxx constants
00064         var $mInterwiki = '';             // /< Interwiki prefix (or null string)
00065         var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
00066         var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
00067         var $mLatestID = false;           // /< ID of most recent revision
00068         private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
00069         var $mRestrictions = array();     // /< Array of groups allowed to edit this article
00070         var $mOldRestrictions = false;
00071         var $mCascadeRestriction;         
00072         var $mCascadingRestrictions;      // Caching the results of getCascadeProtectionSources
00073         var $mRestrictionsExpiry = array(); 
00074         var $mHasCascadingRestrictions;   
00075         var $mCascadeSources;             
00076         var $mRestrictionsLoaded = false; 
00077         var $mPrefixedText;               
00078         var $mTitleProtection;            
00079         # Don't change the following default, NS_MAIN is hardcoded in several
00080         # places.  See bug 696.
00081         var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
00082                                                                           # Zero except in {{transclusion}} tags
00083         var $mWatched = null;             // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
00084         var $mLength = -1;                // /< The page length, 0 for special pages
00085         var $mRedirect = null;            // /< Is the article at this title a redirect?
00086         var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false
00087         var $mHasSubpage;                 // /< Whether a page has any subpages
00088         // @}
00089 
00090 
00094         /*protected*/ function __construct() { }
00095 
00104         public static function newFromDBkey( $key ) {
00105                 $t = new Title();
00106                 $t->mDbkeyform = $key;
00107                 if ( $t->secureAndSplit() ) {
00108                         return $t;
00109                 } else {
00110                         return null;
00111                 }
00112         }
00113 
00127         public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00128                 if ( is_object( $text ) ) {
00129                         throw new MWException( 'Title::newFromText given an object' );
00130                 }
00131 
00140                 if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00141                         return Title::$titleCache[$text];
00142                 }
00143 
00144                 # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
00145                 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
00146 
00147                 $t = new Title();
00148                 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00149                 $t->mDefaultNamespace = $defaultNamespace;
00150 
00151                 static $cachedcount = 0 ;
00152                 if ( $t->secureAndSplit() ) {
00153                         if ( $defaultNamespace == NS_MAIN ) {
00154                                 if ( $cachedcount >= self::CACHE_MAX ) {
00155                                         # Avoid memory leaks on mass operations...
00156                                         Title::$titleCache = array();
00157                                         $cachedcount = 0;
00158                                 }
00159                                 $cachedcount++;
00160                                 Title::$titleCache[$text] =& $t;
00161                         }
00162                         return $t;
00163                 } else {
00164                         $ret = null;
00165                         return $ret;
00166                 }
00167         }
00168 
00184         public static function newFromURL( $url ) {
00185                 $t = new Title();
00186 
00187                 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00188                 # but some URLs used it as a space replacement and they still come
00189                 # from some external search tools.
00190                 if ( strpos( self::legalChars(), '+' ) === false ) {
00191                         $url = str_replace( '+', ' ', $url );
00192                 }
00193 
00194                 $t->mDbkeyform = str_replace( ' ', '_', $url );
00195                 if ( $t->secureAndSplit() ) {
00196                         return $t;
00197                 } else {
00198                         return null;
00199                 }
00200         }
00201 
00209         public static function newFromID( $id, $flags = 0 ) {
00210                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00211                 $row = $db->selectRow(
00212                         'page',
00213                         array(
00214                                 'page_namespace', 'page_title', 'page_id',
00215                                 'page_len', 'page_is_redirect', 'page_latest',
00216                         ),
00217                         array( 'page_id' => $id ),
00218                         __METHOD__
00219                 );
00220                 if ( $row !== false ) {
00221                         $title = Title::newFromRow( $row );
00222                 } else {
00223                         $title = null;
00224                 }
00225                 return $title;
00226         }
00227 
00234         public static function newFromIDs( $ids ) {
00235                 if ( !count( $ids ) ) {
00236                         return array();
00237                 }
00238                 $dbr = wfGetDB( DB_SLAVE );
00239 
00240                 $res = $dbr->select(
00241                         'page',
00242                         array(
00243                                 'page_namespace', 'page_title', 'page_id',
00244                                 'page_len', 'page_is_redirect', 'page_latest',
00245                         ),
00246                         array( 'page_id' => $ids ),
00247                         __METHOD__
00248                 );
00249 
00250                 $titles = array();
00251                 foreach ( $res as $row ) {
00252                         $titles[] = Title::newFromRow( $row );
00253                 }
00254                 return $titles;
00255         }
00256 
00263         public static function newFromRow( $row ) {
00264                 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00265                 $t->loadFromRow( $row );
00266                 return $t;
00267         }
00268 
00275         public function loadFromRow( $row ) {
00276                 if ( $row ) { // page found
00277                         if ( isset( $row->page_id ) )
00278                                 $this->mArticleID = (int)$row->page_id;
00279                         if ( isset( $row->page_len ) )
00280                                 $this->mLength = (int)$row->page_len;
00281                         if ( isset( $row->page_is_redirect ) )
00282                                 $this->mRedirect = (bool)$row->page_is_redirect;
00283                         if ( isset( $row->page_latest ) )
00284                                 $this->mLatestID = (int)$row->page_latest;
00285                 } else { // page not found
00286                         $this->mArticleID = 0;
00287                         $this->mLength = 0;
00288                         $this->mRedirect = false;
00289                         $this->mLatestID = 0;
00290                 }
00291         }
00292 
00306         public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
00307                 $t = new Title();
00308                 $t->mInterwiki = $interwiki;
00309                 $t->mFragment = $fragment;
00310                 $t->mNamespace = $ns = intval( $ns );
00311                 $t->mDbkeyform = str_replace( ' ', '_', $title );
00312                 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00313                 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00314                 $t->mTextform = str_replace( '_', ' ', $title );
00315                 return $t;
00316         }
00317 
00329         public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
00330                 if ( !MWNamespace::exists( $ns ) ) {
00331                         return null;
00332                 }
00333 
00334                 $t = new Title();
00335                 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
00336                 if ( $t->secureAndSplit() ) {
00337                         return $t;
00338                 } else {
00339                         return null;
00340                 }
00341         }
00342 
00348         public static function newMainPage() {
00349                 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
00350                 // Don't give fatal errors if the message is broken
00351                 if ( !$title ) {
00352                         $title = Title::newFromText( 'Main Page' );
00353                 }
00354                 return $title;
00355         }
00356 
00366         public static function newFromRedirect( $text ) {
00367                 return self::newFromRedirectInternal( $text );
00368         }
00369 
00379         public static function newFromRedirectRecurse( $text ) {
00380                 $titles = self::newFromRedirectArray( $text );
00381                 return $titles ? array_pop( $titles ) : null;
00382         }
00383 
00393         public static function newFromRedirectArray( $text ) {
00394                 global $wgMaxRedirects;
00395                 $title = self::newFromRedirectInternal( $text );
00396                 if ( is_null( $title ) ) {
00397                         return null;
00398                 }
00399                 // recursive check to follow double redirects
00400                 $recurse = $wgMaxRedirects;
00401                 $titles = array( $title );
00402                 while ( --$recurse > 0 ) {
00403                         if ( $title->isRedirect() ) {
00404                                 $page = WikiPage::factory( $title );
00405                                 $newtitle = $page->getRedirectTarget();
00406                         } else {
00407                                 break;
00408                         }
00409                         // Redirects to some special pages are not permitted
00410                         if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
00411                                 // the new title passes the checks, so make that our current title so that further recursion can be checked
00412                                 $title = $newtitle;
00413                                 $titles[] = $newtitle;
00414                         } else {
00415                                 break;
00416                         }
00417                 }
00418                 return $titles;
00419         }
00420 
00428         protected static function newFromRedirectInternal( $text ) {
00429                 global $wgMaxRedirects;
00430                 if ( $wgMaxRedirects < 1 ) {
00431                         //redirects are disabled, so quit early
00432                         return null;
00433                 }
00434                 $redir = MagicWord::get( 'redirect' );
00435                 $text = trim( $text );
00436                 if ( $redir->matchStartAndRemove( $text ) ) {
00437                         // Extract the first link and see if it's usable
00438                         // Ensure that it really does come directly after #REDIRECT
00439                         // Some older redirects included a colon, so don't freak about that!
00440                         $m = array();
00441                         if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
00442                                 // Strip preceding colon used to "escape" categories, etc.
00443                                 // and URL-decode links
00444                                 if ( strpos( $m[1], '%' ) !== false ) {
00445                                         // Match behavior of inline link parsing here;
00446                                         $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
00447                                 }
00448                                 $title = Title::newFromText( $m[1] );
00449                                 // If the title is a redirect to bad special pages or is invalid, return null
00450                                 if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
00451                                         return null;
00452                                 }
00453                                 return $title;
00454                         }
00455                 }
00456                 return null;
00457         }
00458 
00465         public static function nameOf( $id ) {
00466                 $dbr = wfGetDB( DB_SLAVE );
00467 
00468                 $s = $dbr->selectRow(
00469                         'page',
00470                         array( 'page_namespace', 'page_title' ),
00471                         array( 'page_id' => $id ),
00472                         __METHOD__
00473                 );
00474                 if ( $s === false ) {
00475                         return null;
00476                 }
00477 
00478                 $n = self::makeName( $s->page_namespace, $s->page_title );
00479                 return $n;
00480         }
00481 
00487         public static function legalChars() {
00488                 global $wgLegalTitleChars;
00489                 return $wgLegalTitleChars;
00490         }
00491 
00499         static function getTitleInvalidRegex() {
00500                 static $rxTc = false;
00501                 if ( !$rxTc ) {
00502                         # Matching titles will be held as illegal.
00503                         $rxTc = '/' .
00504                                 # Any character not allowed is forbidden...
00505                                 '[^' . self::legalChars() . ']' .
00506                                 # URL percent encoding sequences interfere with the ability
00507                                 # to round-trip titles -- you can't link to them consistently.
00508                                 '|%[0-9A-Fa-f]{2}' .
00509                                 # XML/HTML character references produce similar issues.
00510                                 '|&[A-Za-z0-9\x80-\xff]+;' .
00511                                 '|&#[0-9]+;' .
00512                                 '|&#x[0-9A-Fa-f]+;' .
00513                                 '/S';
00514                 }
00515 
00516                 return $rxTc;
00517         }
00518 
00527         public static function indexTitle( $ns, $title ) {
00528                 global $wgContLang;
00529 
00530                 $lc = SearchEngine::legalSearchChars() . '&#;';
00531                 $t = $wgContLang->normalizeForSearch( $title );
00532                 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00533                 $t = $wgContLang->lc( $t );
00534 
00535                 # Handle 's, s'
00536                 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00537                 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00538 
00539                 $t = preg_replace( "/\\s+/", ' ', $t );
00540 
00541                 if ( $ns == NS_FILE ) {
00542                         $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00543                 }
00544                 return trim( $t );
00545         }
00546 
00556         public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
00557                 global $wgContLang;
00558 
00559                 $namespace = $wgContLang->getNsText( $ns );
00560                 $name = $namespace == '' ? $title : "$namespace:$title";
00561                 if ( strval( $interwiki ) != '' ) {
00562                         $name = "$interwiki:$name";
00563                 }
00564                 if ( strval( $fragment ) != '' ) {
00565                         $name .= '#' . $fragment;
00566                 }
00567                 return $name;
00568         }
00569 
00576         static function escapeFragmentForURL( $fragment ) {
00577                 # Note that we don't urlencode the fragment.  urlencoded Unicode
00578                 # fragments appear not to work in IE (at least up to 7) or in at least
00579                 # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
00580                 # to care if they aren't encoded.
00581                 return Sanitizer::escapeId( $fragment, 'noninitial' );
00582         }
00583 
00592         public static function compare( $a, $b ) {
00593                 if ( $a->getNamespace() == $b->getNamespace() ) {
00594                         return strcmp( $a->getText(), $b->getText() );
00595                 } else {
00596                         return $a->getNamespace() - $b->getNamespace();
00597                 }
00598         }
00599 
00606         public function isLocal() {
00607                 if ( $this->mInterwiki != '' ) {
00608                         return Interwiki::fetch( $this->mInterwiki )->isLocal();
00609                 } else {
00610                         return true;
00611                 }
00612         }
00613 
00619         public function isExternal() {
00620                 return ( $this->mInterwiki != '' );
00621         }
00622 
00628         public function getInterwiki() {
00629                 return $this->mInterwiki;
00630         }
00631 
00638         public function isTrans() {
00639                 if ( $this->mInterwiki == '' ) {
00640                         return false;
00641                 }
00642 
00643                 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00644         }
00645 
00651         public function getTransWikiID() {
00652                 if ( $this->mInterwiki == '' ) {
00653                         return false;
00654                 }
00655 
00656                 return Interwiki::fetch( $this->mInterwiki )->getWikiID();
00657         }
00658 
00664         public function getText() {
00665                 return $this->mTextform;
00666         }
00667 
00673         public function getPartialURL() {
00674                 return $this->mUrlform;
00675         }
00676 
00682         public function getDBkey() {
00683                 return $this->mDbkeyform;
00684         }
00685 
00691         function getUserCaseDBKey() {
00692                 return $this->mUserCaseDBKey;
00693         }
00694 
00700         public function getNamespace() {
00701                 return $this->mNamespace;
00702         }
00703 
00709         public function getNsText() {
00710                 global $wgContLang;
00711 
00712                 if ( $this->mInterwiki != '' ) {
00713                         // This probably shouldn't even happen. ohh man, oh yuck.
00714                         // But for interwiki transclusion it sometimes does.
00715                         // Shit. Shit shit shit.
00716                         //
00717                         // Use the canonical namespaces if possible to try to
00718                         // resolve a foreign namespace.
00719                         if ( MWNamespace::exists( $this->mNamespace ) ) {
00720                                 return MWNamespace::getCanonicalName( $this->mNamespace );
00721                         }
00722                 }
00723 
00724                 if ( $wgContLang->needsGenderDistinction() &&
00725                                 MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
00726                         $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
00727                         return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
00728                 }
00729 
00730                 return $wgContLang->getNsText( $this->mNamespace );
00731         }
00732 
00738         public function getSubjectNsText() {
00739                 global $wgContLang;
00740                 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00741         }
00742 
00748         public function getTalkNsText() {
00749                 global $wgContLang;
00750                 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00751         }
00752 
00758         public function canTalk() {
00759                 return( MWNamespace::canTalk( $this->mNamespace ) );
00760         }
00761 
00768         public function canExist() {
00769                 return $this->mNamespace >= NS_MAIN;
00770         }
00771 
00777         public function isWatchable() {
00778                 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
00779         }
00780 
00786         public function isSpecialPage() {
00787                 return $this->getNamespace() == NS_SPECIAL;
00788         }
00789 
00796         public function isSpecial( $name ) {
00797                 if ( $this->isSpecialPage() ) {
00798                         list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
00799                         if ( $name == $thisName ) {
00800                                 return true;
00801                         }
00802                 }
00803                 return false;
00804         }
00805 
00812         public function fixSpecialName() {
00813                 if ( $this->isSpecialPage() ) {
00814                         list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
00815                         if ( $canonicalName ) {
00816                                 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
00817                                 if ( $localName != $this->mDbkeyform ) {
00818                                         return Title::makeTitle( NS_SPECIAL, $localName );
00819                                 }
00820                         }
00821                 }
00822                 return $this;
00823         }
00824 
00835         public function inNamespace( $ns ) {
00836                 return MWNamespace::equals( $this->getNamespace(), $ns );
00837         }
00838 
00846         public function inNamespaces( /* ... */ ) {
00847                 $namespaces = func_get_args();
00848                 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
00849                         $namespaces = $namespaces[0];
00850                 }
00851 
00852                 foreach ( $namespaces as $ns ) {
00853                         if ( $this->inNamespace( $ns ) ) {
00854                                 return true;
00855                         }
00856                 }
00857 
00858                 return false;
00859         }
00860 
00874         public function hasSubjectNamespace( $ns ) {
00875                 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
00876         }
00877 
00885         public function isContentPage() {
00886                 return MWNamespace::isContent( $this->getNamespace() );
00887         }
00888 
00895         public function isMovable() {
00896                 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
00897                         // Interwiki title or immovable namespace. Hooks don't get to override here
00898                         return false;
00899                 }
00900 
00901                 $result = true;
00902                 wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
00903                 return $result;
00904         }
00905 
00916         public function isMainPage() {
00917                 return $this->equals( Title::newMainPage() );
00918         }
00919 
00925         public function isSubpage() {
00926                 return MWNamespace::hasSubpages( $this->mNamespace )
00927                         ? strpos( $this->getText(), '/' ) !== false
00928                         : false;
00929         }
00930 
00936         public function isConversionTable() {
00937                 return $this->getNamespace() == NS_MEDIAWIKI &&
00938                         strpos( $this->getText(), 'Conversiontable/' ) === 0;
00939         }
00940 
00946         public function isWikitextPage() {
00947                 $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
00948                 wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
00949                 return $retval;
00950         }
00951 
00958         public function isCssOrJsPage() {
00959                 $retval = $this->mNamespace == NS_MEDIAWIKI
00960                         && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
00961                 wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
00962                 return $retval;
00963         }
00964 
00969         public function isCssJsSubpage() {
00970                 return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
00971         }
00972 
00978         public function getSkinFromCssJsSubpage() {
00979                 $subpage = explode( '/', $this->mTextform );
00980                 $subpage = $subpage[ count( $subpage ) - 1 ];
00981                 $lastdot = strrpos( $subpage, '.' );
00982                 if ( $lastdot === false )
00983                         return $subpage; # Never happens: only called for names ending in '.css' or '.js'
00984                 return substr( $subpage, 0, $lastdot );
00985         }
00986 
00992         public function isCssSubpage() {
00993                 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
00994         }
00995 
01001         public function isJsSubpage() {
01002                 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
01003         }
01004 
01010         public function isTalkPage() {
01011                 return MWNamespace::isTalk( $this->getNamespace() );
01012         }
01013 
01019         public function getTalkPage() {
01020                 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
01021         }
01022 
01029         public function getSubjectPage() {
01030                 // Is this the same title?
01031                 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
01032                 if ( $this->getNamespace() == $subjectNS ) {
01033                         return $this;
01034                 }
01035                 return Title::makeTitle( $subjectNS, $this->getDBkey() );
01036         }
01037 
01043         public function getDefaultNamespace() {
01044                 return $this->mDefaultNamespace;
01045         }
01046 
01053         public function getIndexTitle() {
01054                 return Title::indexTitle( $this->mNamespace, $this->mTextform );
01055         }
01056 
01062         public function getFragment() {
01063                 return $this->mFragment;
01064         }
01065 
01070         public function getFragmentForURL() {
01071                 if ( $this->mFragment == '' ) {
01072                         return '';
01073                 } else {
01074                         return '#' . Title::escapeFragmentForURL( $this->mFragment );
01075                 }
01076         }
01077 
01088         public function setFragment( $fragment ) {
01089                 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
01090         }
01091 
01100         private function prefix( $name ) {
01101                 $p = '';
01102                 if ( $this->mInterwiki != '' ) {
01103                         $p = $this->mInterwiki . ':';
01104                 }
01105 
01106                 if ( 0 != $this->mNamespace ) {
01107                         $p .= $this->getNsText() . ':';
01108                 }
01109                 return $p . $name;
01110         }
01111 
01118         public function getPrefixedDBkey() {
01119                 $s = $this->prefix( $this->mDbkeyform );
01120                 $s = str_replace( ' ', '_', $s );
01121                 return $s;
01122         }
01123 
01130         public function getPrefixedText() {
01131                 // @todo FIXME: Bad usage of empty() ?
01132                 if ( empty( $this->mPrefixedText ) ) {
01133                         $s = $this->prefix( $this->mTextform );
01134                         $s = str_replace( '_', ' ', $s );
01135                         $this->mPrefixedText = $s;
01136                 }
01137                 return $this->mPrefixedText;
01138         }
01139 
01145         public function __toString() {
01146                 return $this->getPrefixedText();
01147         }
01148 
01155         public function getFullText() {
01156                 $text = $this->getPrefixedText();
01157                 if ( $this->mFragment != '' ) {
01158                         $text .= '#' . $this->mFragment;
01159                 }
01160                 return $text;
01161         }
01162 
01168         public function getBaseText() {
01169                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01170                         return $this->getText();
01171                 }
01172 
01173                 $parts = explode( '/', $this->getText() );
01174                 # Don't discard the real title if there's no subpage involved
01175                 if ( count( $parts ) > 1 ) {
01176                         unset( $parts[count( $parts ) - 1] );
01177                 }
01178                 return implode( '/', $parts );
01179         }
01180 
01186         public function getSubpageText() {
01187                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01188                         return( $this->mTextform );
01189                 }
01190                 $parts = explode( '/', $this->mTextform );
01191                 return( $parts[count( $parts ) - 1] );
01192         }
01193 
01200         public function getEscapedText() {
01201                 wfDeprecated( __METHOD__, '1.19' );
01202                 return htmlspecialchars( $this->getPrefixedText() );
01203         }
01204 
01210         public function getSubpageUrlForm() {
01211                 $text = $this->getSubpageText();
01212                 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
01213                 return( $text );
01214         }
01215 
01221         public function getPrefixedURL() {
01222                 $s = $this->prefix( $this->mDbkeyform );
01223                 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
01224                 return $s;
01225         }
01226 
01240         private static function fixUrlQueryArgs( $query, $query2 = false ) {
01241                 if( $query2 !== false ) {
01242                         wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" );
01243                 }
01244                 if ( is_array( $query ) ) {
01245                         $query = wfArrayToCGI( $query );
01246                 }
01247                 if ( $query2 ) {
01248                         if ( is_string( $query2 ) ) {
01249                                 // $query2 is a string, we will consider this to be
01250                                 // a deprecated $variant argument and add it to the query
01251                                 $query2 = wfArrayToCGI( array( 'variant' => $query2 ) );
01252                         } else {
01253                                 $query2 = wfArrayToCGI( $query2 );
01254                         }
01255                         // If we have $query content add a & to it first
01256                         if ( $query ) {
01257                                 $query .= '&';
01258                         }
01259                         // Now append the queries together
01260                         $query .= $query2;
01261                 }
01262                 return $query;
01263         }
01264 
01276         public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01277                 $query = self::fixUrlQueryArgs( $query, $query2 );
01278 
01279                 # Hand off all the decisions on urls to getLocalURL
01280                 $url = $this->getLocalURL( $query );
01281 
01282                 # Expand the url to make it a full url. Note that getLocalURL has the
01283                 # potential to output full urls for a variety of reasons, so we use
01284                 # wfExpandUrl instead of simply prepending $wgServer
01285                 $url = wfExpandUrl( $url, $proto );
01286 
01287                 # Finally, add the fragment.
01288                 $url .= $this->getFragmentForURL();
01289 
01290                 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
01291                 return $url;
01292         }
01293 
01312         public function getLocalURL( $query = '', $query2 = false ) {
01313                 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
01314 
01315                 $query = self::fixUrlQueryArgs( $query, $query2 );
01316 
01317                 $interwiki = Interwiki::fetch( $this->mInterwiki );
01318                 if ( $interwiki ) {
01319                         $namespace = $this->getNsText();
01320                         if ( $namespace != '' ) {
01321                                 # Can this actually happen? Interwikis shouldn't be parsed.
01322                                 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
01323                                 $namespace .= ':';
01324                         }
01325                         $url = $interwiki->getURL( $namespace . $this->getDBkey() );
01326                         $url = wfAppendQuery( $url, $query );
01327                 } else {
01328                         $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
01329                         if ( $query == '' ) {
01330                                 $url = str_replace( '$1', $dbkey, $wgArticlePath );
01331                                 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
01332                         } else {
01333                                 global $wgVariantArticlePath, $wgActionPaths;
01334                                 $url = false;
01335                                 $matches = array();
01336 
01337                                 if ( !empty( $wgActionPaths ) &&
01338                                         preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
01339                                 {
01340                                         $action = urldecode( $matches[2] );
01341                                         if ( isset( $wgActionPaths[$action] ) ) {
01342                                                 $query = $matches[1];
01343                                                 if ( isset( $matches[4] ) ) {
01344                                                         $query .= $matches[4];
01345                                                 }
01346                                                 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
01347                                                 if ( $query != '' ) {
01348                                                         $url = wfAppendQuery( $url, $query );
01349                                                 }
01350                                         }
01351                                 }
01352 
01353                                 if ( $url === false &&
01354                                         $wgVariantArticlePath &&
01355                                         $this->getPageLanguage()->hasVariants() &&
01356                                         preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
01357                                 {
01358                                         $variant = urldecode( $matches[1] );
01359                                         if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
01360                                                 // Only do the variant replacement if the given variant is a valid
01361                                                 // variant for the page's language.
01362                                                 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
01363                                                 $url = str_replace( '$1', $dbkey, $url );
01364                                         }
01365                                 }
01366 
01367                                 if ( $url === false ) {
01368                                         if ( $query == '-' ) {
01369                                                 $query = '';
01370                                         }
01371                                         $url = "{$wgScript}?title={$dbkey}&{$query}";
01372                                 }
01373                         }
01374 
01375                         wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
01376 
01377                         // @todo FIXME: This causes breakage in various places when we
01378                         // actually expected a local URL and end up with dupe prefixes.
01379                         if ( $wgRequest->getVal( 'action' ) == 'render' ) {
01380                                 $url = $wgServer . $url;
01381                         }
01382                 }
01383                 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
01384                 return $url;
01385         }
01386 
01402         public function getLinkURL( $query = '', $query2 = false ) {
01403                 wfProfileIn( __METHOD__ );
01404                 if ( $this->isExternal() ) {
01405                         $ret = $this->getFullURL( $query, $query2 );
01406                 } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
01407                         $ret = $this->getFragmentForURL();
01408                 } else {
01409                         $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
01410                 }
01411                 wfProfileOut( __METHOD__ );
01412                 return $ret;
01413         }
01414 
01426         public function escapeLocalURL( $query = '', $query2 = false ) {
01427                 wfDeprecated( __METHOD__, '1.19' );
01428                 return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
01429         }
01430 
01440         public function escapeFullURL( $query = '', $query2 = false ) {
01441                 wfDeprecated( __METHOD__, '1.19' );
01442                 return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
01443         }
01444 
01459         public function getInternalURL( $query = '', $query2 = false ) {
01460                 global $wgInternalServer, $wgServer;
01461                 $query = self::fixUrlQueryArgs( $query, $query2 );
01462                 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
01463                 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
01464                 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
01465                 return $url;
01466         }
01467 
01481         public function getCanonicalURL( $query = '', $query2 = false ) {
01482                 $query = self::fixUrlQueryArgs( $query, $query2 );
01483                 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
01484                 wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
01485                 return $url;
01486         }
01487 
01497         public function escapeCanonicalURL( $query = '', $query2 = false ) {
01498                 wfDeprecated( __METHOD__, '1.19' );
01499                 return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
01500         }
01501 
01508         public function getEditURL() {
01509                 if ( $this->mInterwiki != '' ) {
01510                         return '';
01511                 }
01512                 $s = $this->getLocalURL( 'action=edit' );
01513 
01514                 return $s;
01515         }
01516 
01523         public function userIsWatching() {
01524                 global $wgUser;
01525 
01526                 if ( is_null( $this->mWatched ) ) {
01527                         if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
01528                                 $this->mWatched = false;
01529                         } else {
01530                                 $this->mWatched = $wgUser->isWatched( $this );
01531                         }
01532                 }
01533                 return $this->mWatched;
01534         }
01535 
01543         public function userCanRead() {
01544                 wfDeprecated( __METHOD__, '1.19' );
01545                 return $this->userCan( 'read' );
01546         }
01547 
01563         public function quickUserCan( $action, $user = null ) {
01564                 return $this->userCan( $action, $user, false );
01565         }
01566 
01577         public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
01578                 if ( !$user instanceof User ) {
01579                         global $wgUser;
01580                         $user = $wgUser;
01581                 }
01582                 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
01583         }
01584 
01598         public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01599                 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01600 
01601                 // Remove the errors being ignored.
01602                 foreach ( $errors as $index => $error ) {
01603                         $error_key = is_array( $error ) ? $error[0] : $error;
01604 
01605                         if ( in_array( $error_key, $ignoreErrors ) ) {
01606                                 unset( $errors[$index] );
01607                         }
01608                 }
01609 
01610                 return $errors;
01611         }
01612 
01624         private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01625                 if ( $action == 'create' ) {
01626                         if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01627                                  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
01628                                 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
01629                         }
01630                 } elseif ( $action == 'move' ) {
01631                         if ( !$user->isAllowed( 'move-rootuserpages' )
01632                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01633                                 // Show user page-specific message only if the user can move other pages
01634                                 $errors[] = array( 'cant-move-user-page' );
01635                         }
01636 
01637                         // Check if user is allowed to move files if it's a file
01638                         if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01639                                 $errors[] = array( 'movenotallowedfile' );
01640                         }
01641 
01642                         if ( !$user->isAllowed( 'move' ) ) {
01643                                 // User can't move anything
01644                                 global $wgGroupPermissions;
01645                                 $userCanMove = false;
01646                                 if ( isset( $wgGroupPermissions['user']['move'] ) ) {
01647                                         $userCanMove = $wgGroupPermissions['user']['move'];
01648                                 }
01649                                 $autoconfirmedCanMove = false;
01650                                 if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
01651                                         $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
01652                                 }
01653                                 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01654                                         // custom message if logged-in users without any special rights can move
01655                                         $errors[] = array( 'movenologintext' );
01656                                 } else {
01657                                         $errors[] = array( 'movenotallowed' );
01658                                 }
01659                         }
01660                 } elseif ( $action == 'move-target' ) {
01661                         if ( !$user->isAllowed( 'move' ) ) {
01662                                 // User can't move anything
01663                                 $errors[] = array( 'movenotallowed' );
01664                         } elseif ( !$user->isAllowed( 'move-rootuserpages' )
01665                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01666                                 // Show user page-specific message only if the user can move other pages
01667                                 $errors[] = array( 'cant-move-to-user-page' );
01668                         }
01669                 } elseif ( !$user->isAllowed( $action ) ) {
01670                         $errors[] = $this->missingPermissionError( $action, $short );
01671                 }
01672 
01673                 return $errors;
01674         }
01675 
01684         private function resultToError( $errors, $result ) {
01685                 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
01686                         // A single array representing an error
01687                         $errors[] = $result;
01688                 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
01689                         // A nested array representing multiple errors
01690                         $errors = array_merge( $errors, $result );
01691                 } elseif ( $result !== '' && is_string( $result ) ) {
01692                         // A string representing a message-id
01693                         $errors[] = array( $result );
01694                 } elseif ( $result === false ) {
01695                         // a generic "We don't want them to do that"
01696                         $errors[] = array( 'badaccess-group0' );
01697                 }
01698                 return $errors;
01699         }
01700 
01712         private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
01713                 // Use getUserPermissionsErrors instead
01714                 $result = '';
01715                 if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01716                         return $result ? array() : array( array( 'badaccess-group0' ) );
01717                 }
01718                 // Check getUserPermissionsErrors hook
01719                 if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
01720                         $errors = $this->resultToError( $errors, $result );
01721                 }
01722                 // Check getUserPermissionsErrorsExpensive hook
01723                 if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) &&
01724                          !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
01725                         $errors = $this->resultToError( $errors, $result );
01726                 }
01727 
01728                 return $errors;
01729         }
01730 
01742         private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01743                 # Only 'createaccount' and 'execute' can be performed on
01744                 # special pages, which don't actually exist in the DB.
01745                 $specialOKActions = array( 'createaccount', 'execute', 'read' );
01746                 if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
01747                         $errors[] = array( 'ns-specialprotected' );
01748                 }
01749 
01750                 # Check $wgNamespaceProtection for restricted namespaces
01751                 if ( $this->isNamespaceProtected( $user ) ) {
01752                         $ns = $this->mNamespace == NS_MAIN ?
01753                                 wfMessage( 'nstab-main' )->text() : $this->getNsText();
01754                         $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
01755                                 array( 'protectedinterface' ) : array( 'namespaceprotected',  $ns );
01756                 }
01757 
01758                 return $errors;
01759         }
01760 
01772         private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01773                 # Protect css/js subpages of user pages
01774                 # XXX: this might be better using restrictions
01775                 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
01776                 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
01777                                 && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
01778                         if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
01779                                 $errors[] = array( 'customcssprotected' );
01780                         } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
01781                                 $errors[] = array( 'customjsprotected' );
01782                         }
01783                 }
01784 
01785                 return $errors;
01786         }
01787 
01801         private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01802                 foreach ( $this->getRestrictions( $action ) as $right ) {
01803                         // Backwards compatibility, rewrite sysop -> protect
01804                         if ( $right == 'sysop' ) {
01805                                 $right = 'protect';
01806                         }
01807                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01808                                 // Users with 'editprotected' permission can edit protected pages
01809                                 // without cascading option turned on.
01810                                 if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
01811                                         || $this->mCascadeRestriction )
01812                                 {
01813                                         $errors[] = array( 'protectedpagetext', $right );
01814                                 }
01815                         }
01816                 }
01817 
01818                 return $errors;
01819         }
01820 
01832         private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01833                 if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01834                         # We /could/ use the protection level on the source page, but it's
01835                         # fairly ugly as we have to establish a precedence hierarchy for pages
01836                         # included by multiple cascade-protected pages. So just restrict
01837                         # it to people with 'protect' permission, as they could remove the
01838                         # protection anyway.
01839                         list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01840                         # Cascading protection depends on more than this page...
01841                         # Several cascading protected pages may include this page...
01842                         # Check each cascading level
01843                         # This is only for protection restrictions, not for all actions
01844                         if ( isset( $restrictions[$action] ) ) {
01845                                 foreach ( $restrictions[$action] as $right ) {
01846                                         $right = ( $right == 'sysop' ) ? 'protect' : $right;
01847                                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01848                                                 $pages = '';
01849                                                 foreach ( $cascadingSources as $page )
01850                                                         $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01851                                                 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01852                                         }
01853                                 }
01854                         }
01855                 }
01856 
01857                 return $errors;
01858         }
01859 
01871         private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01872                 global $wgDeleteRevisionsLimit, $wgLang;
01873 
01874                 if ( $action == 'protect' ) {
01875                         if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
01876                                 // If they can't edit, they shouldn't protect.
01877                                 $errors[] = array( 'protect-cantedit' );
01878                         }
01879                 } elseif ( $action == 'create' ) {
01880                         $title_protection = $this->getTitleProtection();
01881                         if( $title_protection ) {
01882                                 if( $title_protection['pt_create_perm'] == 'sysop' ) {
01883                                         $title_protection['pt_create_perm'] = 'protect'; // B/C
01884                                 }
01885                                 if( $title_protection['pt_create_perm'] == '' ||
01886                                         !$user->isAllowed( $title_protection['pt_create_perm'] ) )
01887                                 {
01888                                         $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
01889                                 }
01890                         }
01891                 } elseif ( $action == 'move' ) {
01892                         // Check for immobile pages
01893                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01894                                 // Specific message for this case
01895                                 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
01896                         } elseif ( !$this->isMovable() ) {
01897                                 // Less specific message for rarer cases
01898                                 $errors[] = array( 'immobile-source-page' );
01899                         }
01900                 } elseif ( $action == 'move-target' ) {
01901                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01902                                 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
01903                         } elseif ( !$this->isMovable() ) {
01904                                 $errors[] = array( 'immobile-target-page' );
01905                         }
01906                 } elseif ( $action == 'delete' ) {
01907                         if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
01908                                 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
01909                         {
01910                                 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
01911                         }
01912                 }
01913                 return $errors;
01914         }
01915 
01927         private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
01928                 // Account creation blocks handled at userlogin.
01929                 // Unblocking handled in SpecialUnblock
01930                 if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
01931                         return $errors;
01932                 }
01933 
01934                 global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
01935 
01936                 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
01937                         $errors[] = array( 'confirmedittext' );
01938                 }
01939 
01940                 if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
01941                         // Don't block the user from editing their own talk page unless they've been
01942                         // explicitly blocked from that too.
01943                 } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
01944                         $block = $user->getBlock();
01945 
01946                         // This is from OutputPage::blockedPage
01947                         // Copied at r23888 by werdna
01948 
01949                         $id = $user->blockedBy();
01950                         $reason = $user->blockedFor();
01951                         if ( $reason == '' ) {
01952                                 $reason = wfMessage( 'blockednoreason' )->text();
01953                         }
01954                         $ip = $user->getRequest()->getIP();
01955 
01956                         if ( is_numeric( $id ) ) {
01957                                 $name = User::whoIs( $id );
01958                         } else {
01959                                 $name = $id;
01960                         }
01961 
01962                         $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
01963                         $blockid = $block->getId();
01964                         $blockExpiry = $block->getExpiry();
01965                         $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
01966                         if ( $blockExpiry == 'infinity' ) {
01967                                 $blockExpiry = wfMessage( 'infiniteblock' )->text();
01968                         } else {
01969                                 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
01970                         }
01971 
01972                         $intended = strval( $block->getTarget() );
01973 
01974                         $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
01975                                 $blockid, $blockExpiry, $intended, $blockTimestamp );
01976                 }
01977 
01978                 return $errors;
01979         }
01980 
01992         private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01993                 global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
01994                 static $useShortcut = null;
01995 
01996                 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
01997                 if ( is_null( $useShortcut ) ) {
01998                         $useShortcut = true;
01999                         if ( empty( $wgGroupPermissions['*']['read'] ) ) {
02000                                 # Not a public wiki, so no shortcut
02001                                 $useShortcut = false;
02002                         } elseif ( !empty( $wgRevokePermissions ) ) {
02009                                 foreach ( $wgRevokePermissions as $perms ) {
02010                                         if ( !empty( $perms['read'] ) ) {
02011                                                 # We might be removing the read right from the user, so no shortcut
02012                                                 $useShortcut = false;
02013                                                 break;
02014                                         }
02015                                 }
02016                         }
02017                 }
02018 
02019                 $whitelisted = false;
02020                 if ( $useShortcut ) {
02021                         # Shortcut for public wikis, allows skipping quite a bit of code
02022                         $whitelisted = true;
02023                 } elseif ( $user->isAllowed( 'read' ) ) {
02024                         # If the user is allowed to read pages, he is allowed to read all pages
02025                         $whitelisted = true;
02026                 } elseif ( $this->isSpecial( 'Userlogin' )
02027                         || $this->isSpecial( 'ChangePassword' )
02028                         || $this->isSpecial( 'PasswordReset' )
02029                 ) {
02030                         # Always grant access to the login page.
02031                         # Even anons need to be able to log in.
02032                         $whitelisted = true;
02033                 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
02034                         # Time to check the whitelist
02035                         # Only do these checks is there's something to check against
02036                         $name = $this->getPrefixedText();
02037                         $dbName = $this->getPrefixedDBKey();
02038 
02039                         // Check for explicit whitelisting with and without underscores
02040                         if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
02041                                 $whitelisted = true;
02042                         } elseif ( $this->getNamespace() == NS_MAIN ) {
02043                                 # Old settings might have the title prefixed with
02044                                 # a colon for main-namespace pages
02045                                 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
02046                                         $whitelisted = true;
02047                                 }
02048                         } elseif ( $this->isSpecialPage() ) {
02049                                 # If it's a special page, ditch the subpage bit and check again
02050                                 $name = $this->getDBkey();
02051                                 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
02052                                 if ( $name !== false ) {
02053                                         $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
02054                                         if ( in_array( $pure, $wgWhitelistRead, true ) ) {
02055                                                 $whitelisted = true;
02056                                         }
02057                                 }
02058                         }
02059                 }
02060 
02061                 if ( !$whitelisted ) {
02062                         # If the title is not whitelisted, give extensions a chance to do so...
02063                         wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
02064                         if ( !$whitelisted ) {
02065                                 $errors[] = $this->missingPermissionError( $action, $short );
02066                         }
02067                 }
02068 
02069                 return $errors;
02070         }
02071 
02080         private function missingPermissionError( $action, $short ) {
02081                 // We avoid expensive display logic for quickUserCan's and such
02082                 if ( $short ) {
02083                         return array( 'badaccess-group0' );
02084                 }
02085 
02086                 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
02087                         User::getGroupsWithPermission( $action ) );
02088 
02089                 if ( count( $groups ) ) {
02090                         global $wgLang;
02091                         return array(
02092                                 'badaccess-groups',
02093                                 $wgLang->commaList( $groups ),
02094                                 count( $groups )
02095                         );
02096                 } else {
02097                         return array( 'badaccess-group0' );
02098                 }
02099         }
02100 
02112         protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
02113                 wfProfileIn( __METHOD__ );
02114 
02115                 # Read has special handling
02116                 if ( $action == 'read' ) {
02117                         $checks = array(
02118                                 'checkPermissionHooks',
02119                                 'checkReadPermissions',
02120                         );
02121                 } else {
02122                         $checks = array(
02123                                 'checkQuickPermissions',
02124                                 'checkPermissionHooks',
02125                                 'checkSpecialsAndNSPermissions',
02126                                 'checkCSSandJSPermissions',
02127                                 'checkPageRestrictions',
02128                                 'checkCascadingSourcesRestrictions',
02129                                 'checkActionPermissions',
02130                                 'checkUserBlock'
02131                         );
02132                 }
02133 
02134                 $errors = array();
02135                 while( count( $checks ) > 0 &&
02136                                 !( $short && count( $errors ) > 0 ) ) {
02137                         $method = array_shift( $checks );
02138                         $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
02139                 }
02140 
02141                 wfProfileOut( __METHOD__ );
02142                 return $errors;
02143         }
02144 
02152         public function userCanEditCssSubpage() {
02153                 global $wgUser;
02154                 wfDeprecated( __METHOD__, '1.19' );
02155                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
02156                         || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02157         }
02158 
02166         public function userCanEditJsSubpage() {
02167                 global $wgUser;
02168                 wfDeprecated( __METHOD__, '1.19' );
02169                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
02170                            || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02171         }
02172 
02180         public static function getFilteredRestrictionTypes( $exists = true ) {
02181                 global $wgRestrictionTypes;
02182                 $types = $wgRestrictionTypes;
02183                 if ( $exists ) {
02184                         # Remove the create restriction for existing titles
02185                         $types = array_diff( $types, array( 'create' ) );
02186                 } else {
02187                         # Only the create and upload restrictions apply to non-existing titles
02188                         $types = array_intersect( $types, array( 'create', 'upload' ) );
02189                 }
02190                 return $types;
02191         }
02192 
02198         public function getRestrictionTypes() {
02199                 if ( $this->isSpecialPage() ) {
02200                         return array();
02201                 }
02202 
02203                 $types = self::getFilteredRestrictionTypes( $this->exists() );
02204 
02205                 if ( $this->getNamespace() != NS_FILE ) {
02206                         # Remove the upload restriction for non-file titles
02207                         $types = array_diff( $types, array( 'upload' ) );
02208                 }
02209 
02210                 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
02211 
02212                 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
02213                         $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
02214 
02215                 return $types;
02216         }
02217 
02225         private function getTitleProtection() {
02226                 // Can't protect pages in special namespaces
02227                 if ( $this->getNamespace() < 0 ) {
02228                         return false;
02229                 }
02230 
02231                 // Can't protect pages that exist.
02232                 if ( $this->exists() ) {
02233                         return false;
02234                 }
02235 
02236                 if ( !isset( $this->mTitleProtection ) ) {
02237                         $dbr = wfGetDB( DB_SLAVE );
02238                         $res = $dbr->select( 'protected_titles', '*',
02239                                 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02240                                 __METHOD__ );
02241 
02242                         // fetchRow returns false if there are no rows.
02243                         $this->mTitleProtection = $dbr->fetchRow( $res );
02244                 }
02245                 return $this->mTitleProtection;
02246         }
02247 
02257         public function updateTitleProtection( $create_perm, $reason, $expiry ) {
02258                 wfDeprecated( __METHOD__, '1.19' );
02259 
02260                 global $wgUser;
02261 
02262                 $limit = array( 'create' => $create_perm );
02263                 $expiry = array( 'create' => $expiry );
02264 
02265                 $page = WikiPage::factory( $this );
02266                 $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser );
02267 
02268                 return $status->isOK();
02269         }
02270 
02274         public function deleteTitleProtection() {
02275                 $dbw = wfGetDB( DB_MASTER );
02276 
02277                 $dbw->delete(
02278                         'protected_titles',
02279                         array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02280                         __METHOD__
02281                 );
02282                 $this->mTitleProtection = false;
02283         }
02284 
02291         public function isSemiProtected( $action = 'edit' ) {
02292                 if ( $this->exists() ) {
02293                         $restrictions = $this->getRestrictions( $action );
02294                         if ( count( $restrictions ) > 0 ) {
02295                                 foreach ( $restrictions as $restriction ) {
02296                                         if ( strtolower( $restriction ) != 'autoconfirmed' ) {
02297                                                 return false;
02298                                         }
02299                                 }
02300                         } else {
02301                                 # Not protected
02302                                 return false;
02303                         }
02304                         return true;
02305                 } else {
02306                         # If it doesn't exist, it can't be protected
02307                         return false;
02308                 }
02309         }
02310 
02318         public function isProtected( $action = '' ) {
02319                 global $wgRestrictionLevels;
02320 
02321                 $restrictionTypes = $this->getRestrictionTypes();
02322 
02323                 # Special pages have inherent protection
02324                 if( $this->isSpecialPage() ) {
02325                         return true;
02326                 }
02327 
02328                 # Check regular protection levels
02329                 foreach ( $restrictionTypes as $type ) {
02330                         if ( $action == $type || $action == '' ) {
02331                                 $r = $this->getRestrictions( $type );
02332                                 foreach ( $wgRestrictionLevels as $level ) {
02333                                         if ( in_array( $level, $r ) && $level != '' ) {
02334                                                 return true;
02335                                         }
02336                                 }
02337                         }
02338                 }
02339 
02340                 return false;
02341         }
02342 
02350         public function isNamespaceProtected( User $user ) {
02351                 global $wgNamespaceProtection;
02352 
02353                 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
02354                         foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
02355                                 if ( $right != '' && !$user->isAllowed( $right ) ) {
02356                                         return true;
02357                                 }
02358                         }
02359                 }
02360                 return false;
02361         }
02362 
02368         public function isCascadeProtected() {
02369                 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
02370                 return ( $sources > 0 );
02371         }
02372 
02383         public function getCascadeProtectionSources( $getPages = true ) {
02384                 global $wgContLang;
02385                 $pagerestrictions = array();
02386 
02387                 if ( isset( $this->mCascadeSources ) && $getPages ) {
02388                         return array( $this->mCascadeSources, $this->mCascadingRestrictions );
02389                 } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
02390                         return array( $this->mHasCascadingRestrictions, $pagerestrictions );
02391                 }
02392 
02393                 wfProfileIn( __METHOD__ );
02394 
02395                 $dbr = wfGetDB( DB_SLAVE );
02396 
02397                 if ( $this->getNamespace() == NS_FILE ) {
02398                         $tables = array( 'imagelinks', 'page_restrictions' );
02399                         $where_clauses = array(
02400                                 'il_to' => $this->getDBkey(),
02401                                 'il_from=pr_page',
02402                                 'pr_cascade' => 1
02403                         );
02404                 } else {
02405                         $tables = array( 'templatelinks', 'page_restrictions' );
02406                         $where_clauses = array(
02407                                 'tl_namespace' => $this->getNamespace(),
02408                                 'tl_title' => $this->getDBkey(),
02409                                 'tl_from=pr_page',
02410                                 'pr_cascade' => 1
02411                         );
02412                 }
02413 
02414                 if ( $getPages ) {
02415                         $cols = array( 'pr_page', 'page_namespace', 'page_title',
02416                                                    'pr_expiry', 'pr_type', 'pr_level' );
02417                         $where_clauses[] = 'page_id=pr_page';
02418                         $tables[] = 'page';
02419                 } else {
02420                         $cols = array( 'pr_expiry' );
02421                 }
02422 
02423                 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
02424 
02425                 $sources = $getPages ? array() : false;
02426                 $now = wfTimestampNow();
02427                 $purgeExpired = false;
02428 
02429                 foreach ( $res as $row ) {
02430                         $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02431                         if ( $expiry > $now ) {
02432                                 if ( $getPages ) {
02433                                         $page_id = $row->pr_page;
02434                                         $page_ns = $row->page_namespace;
02435                                         $page_title = $row->page_title;
02436                                         $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
02437                                         # Add groups needed for each restriction type if its not already there
02438                                         # Make sure this restriction type still exists
02439 
02440                                         if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
02441                                                 $pagerestrictions[$row->pr_type] = array();
02442                                         }
02443 
02444                                         if ( isset( $pagerestrictions[$row->pr_type] ) &&
02445                                                  !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) {
02446                                                 $pagerestrictions[$row->pr_type][] = $row->pr_level;
02447                                         }
02448                                 } else {
02449                                         $sources = true;
02450                                 }
02451                         } else {
02452                                 // Trigger lazy purge of expired restrictions from the db
02453                                 $purgeExpired = true;
02454                         }
02455                 }
02456                 if ( $purgeExpired ) {
02457                         Title::purgeExpiredRestrictions();
02458                 }
02459 
02460                 if ( $getPages ) {
02461                         $this->mCascadeSources = $sources;
02462                         $this->mCascadingRestrictions = $pagerestrictions;
02463                 } else {
02464                         $this->mHasCascadingRestrictions = $sources;
02465                 }
02466 
02467                 wfProfileOut( __METHOD__ );
02468                 return array( $sources, $pagerestrictions );
02469         }
02470 
02477         public function getRestrictions( $action ) {
02478                 if ( !$this->mRestrictionsLoaded ) {
02479                         $this->loadRestrictions();
02480                 }
02481                 return isset( $this->mRestrictions[$action] )
02482                                 ? $this->mRestrictions[$action]
02483                                 : array();
02484         }
02485 
02493         public function getRestrictionExpiry( $action ) {
02494                 if ( !$this->mRestrictionsLoaded ) {
02495                         $this->loadRestrictions();
02496                 }
02497                 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02498         }
02499 
02505         function areRestrictionsCascading() {
02506                 if ( !$this->mRestrictionsLoaded ) {
02507                         $this->loadRestrictions();
02508                 }
02509 
02510                 return $this->mCascadeRestriction;
02511         }
02512 
02520         private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
02521                 $rows = array();
02522 
02523                 foreach ( $res as $row ) {
02524                         $rows[] = $row;
02525                 }
02526 
02527                 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
02528         }
02529 
02539         public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
02540                 global $wgContLang;
02541                 $dbr = wfGetDB( DB_SLAVE );
02542 
02543                 $restrictionTypes = $this->getRestrictionTypes();
02544 
02545                 foreach ( $restrictionTypes as $type ) {
02546                         $this->mRestrictions[$type] = array();
02547                         $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
02548                 }
02549 
02550                 $this->mCascadeRestriction = false;
02551 
02552                 # Backwards-compatibility: also load the restrictions from the page record (old format).
02553 
02554                 if ( $oldFashionedRestrictions === null ) {
02555                         $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
02556                                 array( 'page_id' => $this->getArticleID() ), __METHOD__ );
02557                 }
02558 
02559                 if ( $oldFashionedRestrictions != '' ) {
02560 
02561                         foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
02562                                 $temp = explode( '=', trim( $restrict ) );
02563                                 if ( count( $temp ) == 1 ) {
02564                                         // old old format should be treated as edit/move restriction
02565                                         $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
02566                                         $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
02567                                 } else {
02568                                         $restriction = trim( $temp[1] );
02569                                         if( $restriction != '' ) { //some old entries are empty
02570                                                 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
02571                                         }
02572                                 }
02573                         }
02574 
02575                         $this->mOldRestrictions = true;
02576 
02577                 }
02578 
02579                 if ( count( $rows ) ) {
02580                         # Current system - load second to make them override.
02581                         $now = wfTimestampNow();
02582                         $purgeExpired = false;
02583 
02584                         # Cycle through all the restrictions.
02585                         foreach ( $rows as $row ) {
02586 
02587                                 // Don't take care of restrictions types that aren't allowed
02588                                 if ( !in_array( $row->pr_type, $restrictionTypes ) )
02589                                         continue;
02590 
02591                                 // This code should be refactored, now that it's being used more generally,
02592                                 // But I don't really see any harm in leaving it in Block for now -werdna
02593                                 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02594 
02595                                 // Only apply the restrictions if they haven't expired!
02596                                 if ( !$expiry || $expiry > $now ) {
02597                                         $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
02598                                         $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
02599 
02600                                         $this->mCascadeRestriction |= $row->pr_cascade;
02601                                 } else {
02602                                         // Trigger a lazy purge of expired restrictions
02603                                         $purgeExpired = true;
02604                                 }
02605                         }
02606 
02607                         if ( $purgeExpired ) {
02608                                 Title::purgeExpiredRestrictions();
02609                         }
02610                 }
02611 
02612                 $this->mRestrictionsLoaded = true;
02613         }
02614 
02621         public function loadRestrictions( $oldFashionedRestrictions = null ) {
02622                 global $wgContLang;
02623                 if ( !$this->mRestrictionsLoaded ) {
02624                         if ( $this->exists() ) {
02625                                 $dbr = wfGetDB( DB_SLAVE );
02626 
02627                                 $res = $dbr->select(
02628                                         'page_restrictions',
02629                                         '*',
02630                                         array( 'pr_page' => $this->getArticleID() ),
02631                                         __METHOD__
02632                                 );
02633 
02634                                 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
02635                         } else {
02636                                 $title_protection = $this->getTitleProtection();
02637 
02638                                 if ( $title_protection ) {
02639                                         $now = wfTimestampNow();
02640                                         $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
02641 
02642                                         if ( !$expiry || $expiry > $now ) {
02643                                                 // Apply the restrictions
02644                                                 $this->mRestrictionsExpiry['create'] = $expiry;
02645                                                 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
02646                                         } else { // Get rid of the old restrictions
02647                                                 Title::purgeExpiredRestrictions();
02648                                                 $this->mTitleProtection = false;
02649                                         }
02650                                 } else {
02651                                         $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
02652                                 }
02653                                 $this->mRestrictionsLoaded = true;
02654                         }
02655                 }
02656         }
02657 
02662         public function flushRestrictions() {
02663                 $this->mRestrictionsLoaded = false;
02664                 $this->mTitleProtection = null;
02665         }
02666 
02670         static function purgeExpiredRestrictions() {
02671                 $dbw = wfGetDB( DB_MASTER );
02672                 $dbw->delete(
02673                         'page_restrictions',
02674                         array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02675                         __METHOD__
02676                 );
02677 
02678                 $dbw->delete(
02679                         'protected_titles',
02680                         array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02681                         __METHOD__
02682                 );
02683         }
02684 
02690         public function hasSubpages() {
02691                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
02692                         # Duh
02693                         return false;
02694                 }
02695 
02696                 # We dynamically add a member variable for the purpose of this method
02697                 # alone to cache the result.  There's no point in having it hanging
02698                 # around uninitialized in every Title object; therefore we only add it
02699                 # if needed and don't declare it statically.
02700                 if ( isset( $this->mHasSubpages ) ) {
02701                         return $this->mHasSubpages;
02702                 }
02703 
02704                 $subpages = $this->getSubpages( 1 );
02705                 if ( $subpages instanceof TitleArray ) {
02706                         return $this->mHasSubpages = (bool)$subpages->count();
02707                 }
02708                 return $this->mHasSubpages = false;
02709         }
02710 
02718         public function getSubpages( $limit = -1 ) {
02719                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
02720                         return array();
02721                 }
02722 
02723                 $dbr = wfGetDB( DB_SLAVE );
02724                 $conds['page_namespace'] = $this->getNamespace();
02725                 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
02726                 $options = array();
02727                 if ( $limit > -1 ) {
02728                         $options['LIMIT'] = $limit;
02729                 }
02730                 return $this->mSubpages = TitleArray::newFromResult(
02731                         $dbr->select( 'page',
02732                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
02733                                 $conds,
02734                                 __METHOD__,
02735                                 $options
02736                         )
02737                 );
02738         }
02739 
02745         public function isDeleted() {
02746                 if ( $this->getNamespace() < 0 ) {
02747                         $n = 0;
02748                 } else {
02749                         $dbr = wfGetDB( DB_SLAVE );
02750 
02751                         $n = $dbr->selectField( 'archive', 'COUNT(*)',
02752                                 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02753                                 __METHOD__
02754                         );
02755                         if ( $this->getNamespace() == NS_FILE ) {
02756                                 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
02757                                         array( 'fa_name' => $this->getDBkey() ),
02758                                         __METHOD__
02759                                 );
02760                         }
02761                 }
02762                 return (int)$n;
02763         }
02764 
02770         public function isDeletedQuick() {
02771                 if ( $this->getNamespace() < 0 ) {
02772                         return false;
02773                 }
02774                 $dbr = wfGetDB( DB_SLAVE );
02775                 $deleted = (bool)$dbr->selectField( 'archive', '1',
02776                         array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02777                         __METHOD__
02778                 );
02779                 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
02780                         $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02781                                 array( 'fa_name' => $this->getDBkey() ),
02782                                 __METHOD__
02783                         );
02784                 }
02785                 return $deleted;
02786         }
02787 
02796         public function getArticleID( $flags = 0 ) {
02797                 if ( $this->getNamespace() < 0 ) {
02798                         return $this->mArticleID = 0;
02799                 }
02800                 $linkCache = LinkCache::singleton();
02801                 if ( $flags & self::GAID_FOR_UPDATE ) {
02802                         $oldUpdate = $linkCache->forUpdate( true );
02803                         $linkCache->clearLink( $this );
02804                         $this->mArticleID = $linkCache->addLinkObj( $this );
02805                         $linkCache->forUpdate( $oldUpdate );
02806                 } else {
02807                         if ( -1 == $this->mArticleID ) {
02808                                 $this->mArticleID = $linkCache->addLinkObj( $this );
02809                         }
02810                 }
02811                 return $this->mArticleID;
02812         }
02813 
02821         public function isRedirect( $flags = 0 ) {
02822                 if ( !is_null( $this->mRedirect ) ) {
02823                         return $this->mRedirect;
02824                 }
02825                 # Calling getArticleID() loads the field from cache as needed
02826                 if ( !$this->getArticleID( $flags ) ) {
02827                         return $this->mRedirect = false;
02828                 }
02829                 $linkCache = LinkCache::singleton();
02830                 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02831 
02832                 return $this->mRedirect;
02833         }
02834 
02842         public function getLength( $flags = 0 ) {
02843                 if ( $this->mLength != -1 ) {
02844                         return $this->mLength;
02845                 }
02846                 # Calling getArticleID() loads the field from cache as needed
02847                 if ( !$this->getArticleID( $flags ) ) {
02848                         return $this->mLength = 0;
02849                 }
02850                 $linkCache = LinkCache::singleton();
02851                 $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
02852 
02853                 return $this->mLength;
02854         }
02855 
02862         public function getLatestRevID( $flags = 0 ) {
02863                 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
02864                         return intval( $this->mLatestID );
02865                 }
02866                 # Calling getArticleID() loads the field from cache as needed
02867                 if ( !$this->getArticleID( $flags ) ) {
02868                         return $this->mLatestID = 0;
02869                 }
02870                 $linkCache = LinkCache::singleton();
02871                 $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
02872 
02873                 return $this->mLatestID;
02874         }
02875 
02886         public function resetArticleID( $newid ) {
02887                 $linkCache = LinkCache::singleton();
02888                 $linkCache->clearLink( $this );
02889 
02890                 if ( $newid === false ) {
02891                         $this->mArticleID = -1;
02892                 } else {
02893                         $this->mArticleID = intval( $newid );
02894                 }
02895                 $this->mRestrictionsLoaded = false;
02896                 $this->mRestrictions = array();
02897                 $this->mRedirect = null;
02898                 $this->mLength = -1;
02899                 $this->mLatestID = false;
02900                 $this->mEstimateRevisions = null;
02901         }
02902 
02910         public static function capitalize( $text, $ns = NS_MAIN ) {
02911                 global $wgContLang;
02912 
02913                 if ( MWNamespace::isCapitalized( $ns ) ) {
02914                         return $wgContLang->ucfirst( $text );
02915                 } else {
02916                         return $text;
02917                 }
02918         }
02919 
02931         private function secureAndSplit() {
02932                 global $wgContLang, $wgLocalInterwiki;
02933 
02934                 # Initialisation
02935                 $this->mInterwiki = $this->mFragment = '';
02936                 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
02937 
02938                 $dbkey = $this->mDbkeyform;
02939 
02940                 # Strip Unicode bidi override characters.
02941                 # Sometimes they slip into cut-n-pasted page titles, where the
02942                 # override chars get included in list displays.
02943                 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
02944 
02945                 # Clean up whitespace
02946                 # Note: use of the /u option on preg_replace here will cause
02947                 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
02948                 # conveniently disabling them.
02949                 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
02950                 $dbkey = trim( $dbkey, '_' );
02951 
02952                 if ( $dbkey == '' ) {
02953                         return false;
02954                 }
02955 
02956                 if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
02957                         # Contained illegal UTF-8 sequences or forbidden Unicode chars.
02958                         return false;
02959                 }
02960 
02961                 $this->mDbkeyform = $dbkey;
02962 
02963                 # Initial colon indicates main namespace rather than specified default
02964                 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
02965                 if ( ':' == $dbkey[0] ) {
02966                         $this->mNamespace = NS_MAIN;
02967                         $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
02968                         $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
02969                 }
02970 
02971                 # Namespace or interwiki prefix
02972                 $firstPass = true;
02973                 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
02974                 do {
02975                         $m = array();
02976                         if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
02977                                 $p = $m[1];
02978                                 if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
02979                                         # Ordinary namespace
02980                                         $dbkey = $m[2];
02981                                         $this->mNamespace = $ns;
02982                                         # For Talk:X pages, check if X has a "namespace" prefix
02983                                         if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
02984                                                 if ( $wgContLang->getNsIndex( $x[1] ) ) {
02985                                                         # Disallow Talk:File:x type titles...
02986                                                         return false;
02987                                                 } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
02988                                                         # Disallow Talk:Interwiki:x type titles...
02989                                                         return false;
02990                                                 }
02991                                         }
02992                                 } elseif ( Interwiki::isValidInterwiki( $p ) ) {
02993                                         if ( !$firstPass ) {
02994                                                 # Can't make a local interwiki link to an interwiki link.
02995                                                 # That's just crazy!
02996                                                 return false;
02997                                         }
02998 
02999                                         # Interwiki link
03000                                         $dbkey = $m[2];
03001                                         $this->mInterwiki = $wgContLang->lc( $p );
03002 
03003                                         # Redundant interwiki prefix to the local wiki
03004                                         if ( $wgLocalInterwiki !== false
03005                                                 && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
03006                                         {
03007                                                 if ( $dbkey == '' ) {
03008                                                         # Can't have an empty self-link
03009                                                         return false;
03010                                                 }
03011                                                 $this->mInterwiki = '';
03012                                                 $firstPass = false;
03013                                                 # Do another namespace split...
03014                                                 continue;
03015                                         }
03016 
03017                                         # If there's an initial colon after the interwiki, that also
03018                                         # resets the default namespace
03019                                         if ( $dbkey !== '' && $dbkey[0] == ':' ) {
03020                                                 $this->mNamespace = NS_MAIN;
03021                                                 $dbkey = substr( $dbkey, 1 );
03022                                         }
03023                                 }
03024                                 # If there's no recognized interwiki or namespace,
03025                                 # then let the colon expression be part of the title.
03026                         }
03027                         break;
03028                 } while ( true );
03029 
03030                 # We already know that some pages won't be in the database!
03031                 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
03032                         $this->mArticleID = 0;
03033                 }
03034                 $fragment = strstr( $dbkey, '#' );
03035                 if ( false !== $fragment ) {
03036                         $this->setFragment( $fragment );
03037                         $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
03038                         # remove whitespace again: prevents "Foo_bar_#"
03039                         # becoming "Foo_bar_"
03040                         $dbkey = preg_replace( '/_*$/', '', $dbkey );
03041                 }
03042 
03043                 # Reject illegal characters.
03044                 $rxTc = self::getTitleInvalidRegex();
03045                 if ( preg_match( $rxTc, $dbkey ) ) {
03046                         return false;
03047                 }
03048 
03049                 # Pages with "/./" or "/../" appearing in the URLs will often be un-
03050                 # reachable due to the way web browsers deal with 'relative' URLs.
03051                 # Also, they conflict with subpage syntax.  Forbid them explicitly.
03052                 if ( strpos( $dbkey, '.' ) !== false &&
03053                          ( $dbkey === '.' || $dbkey === '..' ||
03054                            strpos( $dbkey, './' ) === 0  ||
03055                            strpos( $dbkey, '../' ) === 0 ||
03056                            strpos( $dbkey, '/./' ) !== false ||
03057                            strpos( $dbkey, '/../' ) !== false  ||
03058                            substr( $dbkey, -2 ) == '/.' ||
03059                            substr( $dbkey, -3 ) == '/..' ) )
03060                 {
03061                         return false;
03062                 }
03063 
03064                 # Magic tilde sequences? Nu-uh!
03065                 if ( strpos( $dbkey, '~~~' ) !== false ) {
03066                         return false;
03067                 }
03068 
03069                 # Limit the size of titles to 255 bytes. This is typically the size of the
03070                 # underlying database field. We make an exception for special pages, which
03071                 # don't need to be stored in the database, and may edge over 255 bytes due
03072                 # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
03073                 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
03074                   strlen( $dbkey ) > 512 )
03075                 {
03076                         return false;
03077                 }
03078 
03079                 # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
03080                 # and [[Foo]] point to the same place.  Don't force it for interwikis, since the
03081                 # other site might be case-sensitive.
03082                 $this->mUserCaseDBKey = $dbkey;
03083                 if ( $this->mInterwiki == '' ) {
03084                         $dbkey = self::capitalize( $dbkey, $this->mNamespace );
03085                 }
03086 
03087                 # Can't make a link to a namespace alone... "empty" local links can only be
03088                 # self-links with a fragment identifier.
03089                 if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
03090                         return false;
03091                 }
03092 
03093                 // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
03094                 // IP names are not allowed for accounts, and can only be referring to
03095                 // edits from the IP. Given '::' abbreviations and caps/lowercaps,
03096                 // there are numerous ways to present the same IP. Having sp:contribs scan
03097                 // them all is silly and having some show the edits and others not is
03098                 // inconsistent. Same for talk/userpages. Keep them normalized instead.
03099                 $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
03100                         ? IP::sanitizeIP( $dbkey )
03101                         : $dbkey;
03102 
03103                 // Any remaining initial :s are illegal.
03104                 if ( $dbkey !== '' && ':' == $dbkey[0] ) {
03105                         return false;
03106                 }
03107 
03108                 # Fill fields
03109                 $this->mDbkeyform = $dbkey;
03110                 $this->mUrlform = wfUrlencode( $dbkey );
03111 
03112                 $this->mTextform = str_replace( '_', ' ', $dbkey );
03113 
03114                 return true;
03115         }
03116 
03129         public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03130                 if ( count( $options ) > 0 ) {
03131                         $db = wfGetDB( DB_MASTER );
03132                 } else {
03133                         $db = wfGetDB( DB_SLAVE );
03134                 }
03135 
03136                 $res = $db->select(
03137                         array( 'page', $table ),
03138                         array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
03139                         array(
03140                                 "{$prefix}_from=page_id",
03141                                 "{$prefix}_namespace" => $this->getNamespace(),
03142                                 "{$prefix}_title"     => $this->getDBkey() ),
03143                         __METHOD__,
03144                         $options
03145                 );
03146 
03147                 $retVal = array();
03148                 if ( $res->numRows() ) {
03149                         $linkCache = LinkCache::singleton();
03150                         foreach ( $res as $row ) {
03151                                 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
03152                                 if ( $titleObj ) {
03153                                         $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03154                                         $retVal[] = $titleObj;
03155                                 }
03156                         }
03157                 }
03158                 return $retVal;
03159         }
03160 
03171         public function getTemplateLinksTo( $options = array() ) {
03172                 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
03173         }
03174 
03187         public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03188                 $id = $this->getArticleID();
03189 
03190                 # If the page doesn't exist; there can't be any link from this page
03191                 if ( !$id ) {
03192                         return array();
03193                 }
03194 
03195                 if ( count( $options ) > 0 ) {
03196                         $db = wfGetDB( DB_MASTER );
03197                 } else {
03198                         $db = wfGetDB( DB_SLAVE );
03199                 }
03200 
03201                 $namespaceFiled = "{$prefix}_namespace";
03202                 $titleField = "{$prefix}_title";
03203 
03204                 $res = $db->select(
03205                         array( $table, 'page' ),
03206                         array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
03207                         array( "{$prefix}_from" => $id ),
03208                         __METHOD__,
03209                         $options,
03210                         array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
03211                 );
03212 
03213                 $retVal = array();
03214                 if ( $res->numRows() ) {
03215                         $linkCache = LinkCache::singleton();
03216                         foreach ( $res as $row ) {
03217                                 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
03218                                 if ( $titleObj ) {
03219                                         if ( $row->page_id ) {
03220                                                 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03221                                         } else {
03222                                                 $linkCache->addBadLinkObj( $titleObj );
03223                                         }
03224                                         $retVal[] = $titleObj;
03225                                 }
03226                         }
03227                 }
03228                 return $retVal;
03229         }
03230 
03241         public function getTemplateLinksFrom( $options = array() ) {
03242                 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
03243         }
03244 
03251         public function getBrokenLinksFrom() {
03252                 if ( $this->getArticleID() == 0 ) {
03253                         # All links from article ID 0 are false positives
03254                         return array();
03255                 }
03256 
03257                 $dbr = wfGetDB( DB_SLAVE );
03258                 $res = $dbr->select(
03259                         array( 'page', 'pagelinks' ),
03260                         array( 'pl_namespace', 'pl_title' ),
03261                         array(
03262                                 'pl_from' => $this->getArticleID(),
03263                                 'page_namespace IS NULL'
03264                         ),
03265                         __METHOD__, array(),
03266                         array(
03267                                 'page' => array(
03268                                         'LEFT JOIN',
03269                                         array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
03270                                 )
03271                         )
03272                 );
03273 
03274                 $retVal = array();
03275                 foreach ( $res as $row ) {
03276                         $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
03277                 }
03278                 return $retVal;
03279         }
03280 
03281 
03288         public function getSquidURLs() {
03289                 $urls = array(
03290                         $this->getInternalURL(),
03291                         $this->getInternalURL( 'action=history' )
03292                 );
03293 
03294                 $pageLang = $this->getPageLanguage();
03295                 if ( $pageLang->hasVariants() ) {
03296                         $variants = $pageLang->getVariants();
03297                         foreach ( $variants as $vCode ) {
03298                                 $urls[] = $this->getInternalURL( '', $vCode );
03299                         }
03300                 }
03301 
03302                 return $urls;
03303         }
03304 
03308         public function purgeSquid() {
03309                 global $wgUseSquid;
03310                 if ( $wgUseSquid ) {
03311                         $urls = $this->getSquidURLs();
03312                         $u = new SquidUpdate( $urls );
03313                         $u->doUpdate();
03314                 }
03315         }
03316 
03323         public function moveNoAuth( &$nt ) {
03324                 return $this->moveTo( $nt, false );
03325         }
03326 
03337         public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
03338                 global $wgUser;
03339 
03340                 $errors = array();
03341                 if ( !$nt ) {
03342                         // Normally we'd add this to $errors, but we'll get
03343                         // lots of syntax errors if $nt is not an object
03344                         return array( array( 'badtitletext' ) );
03345                 }
03346                 if ( $this->equals( $nt ) ) {
03347                         $errors[] = array( 'selfmove' );
03348                 }
03349                 if ( !$this->isMovable() ) {
03350                         $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
03351                 }
03352                 if ( $nt->getInterwiki() != '' ) {
03353                         $errors[] = array( 'immobile-target-namespace-iw' );
03354                 }
03355                 if ( !$nt->isMovable() ) {
03356                         $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
03357                 }
03358 
03359                 $oldid = $this->getArticleID();
03360                 $newid = $nt->getArticleID();
03361 
03362                 if ( strlen( $nt->getDBkey() ) < 1 ) {
03363                         $errors[] = array( 'articleexists' );
03364                 }
03365                 if ( ( $this->getDBkey() == '' ) ||
03366                          ( !$oldid ) ||
03367                          ( $nt->getDBkey() == '' ) ) {
03368                         $errors[] = array( 'badarticleerror' );
03369                 }
03370 
03371                 // Image-specific checks
03372                 if ( $this->getNamespace() == NS_FILE ) {
03373                         $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
03374                 }
03375 
03376                 if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
03377                         $errors[] = array( 'nonfile-cannot-move-to-file' );
03378                 }
03379 
03380                 if ( $auth ) {
03381                         $errors = wfMergeErrorArrays( $errors,
03382                                 $this->getUserPermissionsErrors( 'move', $wgUser ),
03383                                 $this->getUserPermissionsErrors( 'edit', $wgUser ),
03384                                 $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
03385                                 $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
03386                 }
03387 
03388                 $match = EditPage::matchSummarySpamRegex( $reason );
03389                 if ( $match !== false ) {
03390                         // This is kind of lame, won't display nice
03391                         $errors[] = array( 'spamprotectiontext' );
03392                 }
03393 
03394                 $err = null;
03395                 if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
03396                         $errors[] = array( 'hookaborted', $err );
03397                 }
03398 
03399                 # The move is allowed only if (1) the target doesn't exist, or
03400                 # (2) the target is a redirect to the source, and has no history
03401                 # (so we can undo bad moves right after they're done).
03402 
03403                 if ( 0 != $newid ) { # Target exists; check for validity
03404                         if ( !$this->isValidMoveTarget( $nt ) ) {
03405                                 $errors[] = array( 'articleexists' );
03406                         }
03407                 } else {
03408                         $tp = $nt->getTitleProtection();
03409                         $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
03410                         if ( $tp and !$wgUser->isAllowed( $right ) ) {
03411                                 $errors[] = array( 'cantmove-titleprotected' );
03412                         }
03413                 }
03414                 if ( empty( $errors ) ) {
03415                         return true;
03416                 }
03417                 return $errors;
03418         }
03419 
03425         protected function validateFileMoveOperation( $nt ) {
03426                 global $wgUser;
03427 
03428                 $errors = array();
03429 
03430                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
03431 
03432                 $file = wfLocalFile( $this );
03433                 if ( $file->exists() ) {
03434                         if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
03435                                 $errors[] = array( 'imageinvalidfilename' );
03436                         }
03437                         if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
03438                                 $errors[] = array( 'imagetypemismatch' );
03439                         }
03440                 }
03441 
03442                 if ( $nt->getNamespace() != NS_FILE ) {
03443                         $errors[] = array( 'imagenocrossnamespace' );
03444                         // From here we want to do checks on a file object, so if we can't
03445                         // create one, we must return.
03446                         return $errors;
03447                 }
03448 
03449                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
03450 
03451                 $destFile = wfLocalFile( $nt );
03452                 if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
03453                         $errors[] = array( 'file-exists-sharedrepo' );
03454                 }
03455 
03456                 return $errors;
03457         }
03458 
03470         public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
03471                 global $wgUser;
03472                 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
03473                 if ( is_array( $err ) ) {
03474                         // Auto-block user's IP if the account was "hard" blocked
03475                         $wgUser->spreadAnyEditBlock();
03476                         return $err;
03477                 }
03478                 // Check suppressredirect permission
03479                 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
03480                         $createRedirect = true;
03481                 }
03482 
03483                 // If it is a file, move it first.
03484                 // It is done before all other moving stuff is done because it's hard to revert.
03485                 $dbw = wfGetDB( DB_MASTER );
03486                 if ( $this->getNamespace() == NS_FILE ) {
03487                         $file = wfLocalFile( $this );
03488                         if ( $file->exists() ) {
03489                                 $status = $file->move( $nt );
03490                                 if ( !$status->isOk() ) {
03491                                         return $status->getErrorsArray();
03492                                 }
03493                         }
03494                         // Clear RepoGroup process cache
03495                         RepoGroup::singleton()->clearCache( $this );
03496                         RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
03497                 }
03498 
03499                 $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
03500                 $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
03501                 $protected = $this->isProtected();
03502 
03503                 // Do the actual move
03504                 $this->moveToInternal( $nt, $reason, $createRedirect );
03505 
03506                 // Refresh the sortkey for this row.  Be careful to avoid resetting
03507                 // cl_timestamp, which may disturb time-based lists on some sites.
03508                 $prefixes = $dbw->select(
03509                         'categorylinks',
03510                         array( 'cl_sortkey_prefix', 'cl_to' ),
03511                         array( 'cl_from' => $pageid ),
03512                         __METHOD__
03513                 );
03514                 foreach ( $prefixes as $prefixRow ) {
03515                         $prefix = $prefixRow->cl_sortkey_prefix;
03516                         $catTo = $prefixRow->cl_to;
03517                         $dbw->update( 'categorylinks',
03518                                 array(
03519                                         'cl_sortkey' => Collation::singleton()->getSortKey(
03520                                                 $nt->getCategorySortkey( $prefix ) ),
03521                                         'cl_timestamp=cl_timestamp' ),
03522                                 array(
03523                                         'cl_from' => $pageid,
03524                                         'cl_to' => $catTo ),
03525                                 __METHOD__
03526                         );
03527                 }
03528 
03529                 $redirid = $this->getArticleID();
03530 
03531                 if ( $protected ) {
03532                         # Protect the redirect title as the title used to be...
03533                         $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
03534                                 array(
03535                                         'pr_page'    => $redirid,
03536                                         'pr_type'    => 'pr_type',
03537                                         'pr_level'   => 'pr_level',
03538                                         'pr_cascade' => 'pr_cascade',
03539                                         'pr_user'    => 'pr_user',
03540                                         'pr_expiry'  => 'pr_expiry'
03541                                 ),
03542                                 array( 'pr_page' => $pageid ),
03543                                 __METHOD__,
03544                                 array( 'IGNORE' )
03545                         );
03546                         # Update the protection log
03547                         $log = new LogPage( 'protect' );
03548                         $comment = wfMessage(
03549                                 'prot_1movedto2',
03550                                 $this->getPrefixedText(),
03551                                 $nt->getPrefixedText()
03552                         )->inContentLanguage()->text();
03553                         if ( $reason ) {
03554                                 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03555                         }
03556                         // @todo FIXME: $params?
03557                         $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
03558                 }
03559 
03560                 # Update watchlists
03561                 $oldnamespace = $this->getNamespace() & ~1;
03562                 $newnamespace = $nt->getNamespace() & ~1;
03563                 $oldtitle = $this->getDBkey();
03564                 $newtitle = $nt->getDBkey();
03565 
03566                 if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
03567                         WatchedItem::duplicateEntries( $this, $nt );
03568                 }
03569 
03570                 $dbw->commit( __METHOD__ );
03571 
03572                 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
03573                 return true;
03574         }
03575 
03586         private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
03587                 global $wgUser, $wgContLang;
03588 
03589                 if ( $nt->exists() ) {
03590                         $moveOverRedirect = true;
03591                         $logType = 'move_redir';
03592                 } else {
03593                         $moveOverRedirect = false;
03594                         $logType = 'move';
03595                 }
03596 
03597                 $redirectSuppressed = !$createRedirect;
03598 
03599                 $logEntry = new ManualLogEntry( 'move', $logType );
03600                 $logEntry->setPerformer( $wgUser );
03601                 $logEntry->setTarget( $this );
03602                 $logEntry->setComment( $reason );
03603                 $logEntry->setParameters( array(
03604                         '4::target' => $nt->getPrefixedText(),
03605                         '5::noredir' => $redirectSuppressed ? '1': '0',
03606                 ) );
03607 
03608                 $formatter = LogFormatter::newFromEntry( $logEntry );
03609                 $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
03610                 $comment = $formatter->getPlainActionText();
03611                 if ( $reason ) {
03612                         $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03613                 }
03614                 # Truncate for whole multibyte characters.
03615                 $comment = $wgContLang->truncate( $comment, 255 );
03616 
03617                 $oldid = $this->getArticleID();
03618 
03619                 $dbw = wfGetDB( DB_MASTER );
03620 
03621                 $newpage = WikiPage::factory( $nt );
03622 
03623                 if ( $moveOverRedirect ) {
03624                         $newid = $nt->getArticleID();
03625 
03626                         # Delete the old redirect. We don't save it to history since
03627                         # by definition if we've got here it's rather uninteresting.
03628                         # We have to remove it so that the next step doesn't trigger
03629                         # a conflict on the unique namespace+title index...
03630                         $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
03631 
03632                         $newpage->doDeleteUpdates( $newid );
03633                 }
03634 
03635                 # Save a null revision in the page's history notifying of the move
03636                 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
03637                 if ( !is_object( $nullRevision ) ) {
03638                         throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
03639                 }
03640                 $nullRevId = $nullRevision->insertOn( $dbw );
03641 
03642                 # Change the name of the target page:
03643                 $dbw->update( 'page',
03644                         /* SET */ array(
03645                                 'page_namespace' => $nt->getNamespace(),
03646                                 'page_title'     => $nt->getDBkey(),
03647                         ),
03648                         /* WHERE */ array( 'page_id' => $oldid ),
03649                         __METHOD__
03650                 );
03651 
03652                 $this->resetArticleID( 0 );
03653                 $nt->resetArticleID( $oldid );
03654 
03655                 $newpage->updateRevisionOn( $dbw, $nullRevision );
03656 
03657                 wfRunHooks( 'NewRevisionFromEditComplete',
03658                         array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
03659 
03660                 $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
03661 
03662                 if ( !$moveOverRedirect ) {
03663                         WikiPage::onArticleCreate( $nt );
03664                 }
03665 
03666                 # Recreate the redirect, this time in the other direction.
03667                 if ( $redirectSuppressed ) {
03668                         WikiPage::onArticleDelete( $this );
03669                 } else {
03670                         $mwRedir = MagicWord::get( 'redirect' );
03671                         $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
03672                         $redirectArticle = WikiPage::factory( $this );
03673                         $newid = $redirectArticle->insertOn( $dbw );
03674                         if ( $newid ) { // sanity
03675                                 $redirectRevision = new Revision( array(
03676                                         'page'    => $newid,
03677                                         'comment' => $comment,
03678                                         'text'    => $redirectText ) );
03679                                 $redirectRevision->insertOn( $dbw );
03680                                 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
03681 
03682                                 wfRunHooks( 'NewRevisionFromEditComplete',
03683                                         array( $redirectArticle, $redirectRevision, false, $wgUser ) );
03684 
03685                                 $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
03686                         }
03687                 }
03688 
03689                 # Log the move
03690                 $logid = $logEntry->insert();
03691                 $logEntry->publish( $logid );
03692         }
03693 
03706         public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03707                 global $wgMaximumMovedPages;
03708                 // Check permissions
03709                 if ( !$this->userCan( 'move-subpages' ) ) {
03710                         return array( 'cant-move-subpages' );
03711                 }
03712                 // Do the source and target namespaces support subpages?
03713                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03714                         return array( 'namespace-nosubpages',
03715                                 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03716                 }
03717                 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
03718                         return array( 'namespace-nosubpages',
03719                                 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03720                 }
03721 
03722                 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
03723                 $retval = array();
03724                 $count = 0;
03725                 foreach ( $subpages as $oldSubpage ) {
03726                         $count++;
03727                         if ( $count > $wgMaximumMovedPages ) {
03728                                 $retval[$oldSubpage->getPrefixedTitle()] =
03729                                                 array( 'movepage-max-pages',
03730                                                         $wgMaximumMovedPages );
03731                                 break;
03732                         }
03733 
03734                         // We don't know whether this function was called before
03735                         // or after moving the root page, so check both
03736                         // $this and $nt
03737                         if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
03738                                         $oldSubpage->getArticleID() == $nt->getArticleID() )
03739                         {
03740                                 // When moving a page to a subpage of itself,
03741                                 // don't move it twice
03742                                 continue;
03743                         }
03744                         $newPageName = preg_replace(
03745                                         '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
03746                                         StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03747                                         $oldSubpage->getDBkey() );
03748                         if ( $oldSubpage->isTalkPage() ) {
03749                                 $newNs = $nt->getTalkPage()->getNamespace();
03750                         } else {
03751                                 $newNs = $nt->getSubjectPage()->getNamespace();
03752                         }
03753                         # Bug 14385: we need makeTitleSafe because the new page names may
03754                         # be longer than 255 characters.
03755                         $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03756 
03757                         $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03758                         if ( $success === true ) {
03759                                 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03760                         } else {
03761                                 $retval[$oldSubpage->getPrefixedText()] = $success;
03762                         }
03763                 }
03764                 return $retval;
03765         }
03766 
03773         public function isSingleRevRedirect() {
03774                 $dbw = wfGetDB( DB_MASTER );
03775                 # Is it a redirect?
03776                 $row = $dbw->selectRow( 'page',
03777                         array( 'page_is_redirect', 'page_latest', 'page_id' ),
03778                         $this->pageCond(),
03779                         __METHOD__,
03780                         array( 'FOR UPDATE' )
03781                 );
03782                 # Cache some fields we may want
03783                 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
03784                 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03785                 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
03786                 if ( !$this->mRedirect ) {
03787                         return false;
03788                 }
03789                 # Does the article have a history?
03790                 $row = $dbw->selectField( array( 'page', 'revision' ),
03791                         'rev_id',
03792                         array( 'page_namespace' => $this->getNamespace(),
03793                                 'page_title' => $this->getDBkey(),
03794                                 'page_id=rev_page',
03795                                 'page_latest != rev_id'
03796                         ),
03797                         __METHOD__,
03798                         array( 'FOR UPDATE' )
03799                 );
03800                 # Return true if there was no history
03801                 return ( $row === false );
03802         }
03803 
03811         public function isValidMoveTarget( $nt ) {
03812                 # Is it an existing file?
03813                 if ( $nt->getNamespace() == NS_FILE ) {
03814                         $file = wfLocalFile( $nt );
03815                         if ( $file->exists() ) {
03816                                 wfDebug( __METHOD__ . ": file exists\n" );
03817                                 return false;
03818                         }
03819                 }
03820                 # Is it a redirect with no history?
03821                 if ( !$nt->isSingleRevRedirect() ) {
03822                         wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03823                         return false;
03824                 }
03825                 # Get the article text
03826                 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
03827                 if( !is_object( $rev ) ){
03828                         return false;
03829                 }
03830                 $text = $rev->getText();
03831                 # Does the redirect point to the source?
03832                 # Or is it a broken self-redirect, usually caused by namespace collisions?
03833                 $m = array();
03834                 if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
03835                         $redirTitle = Title::newFromText( $m[1] );
03836                         if ( !is_object( $redirTitle ) ||
03837                                 ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03838                                 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
03839                                 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03840                                 return false;
03841                         }
03842                 } else {
03843                         # Fail safe
03844                         wfDebug( __METHOD__ . ": failsafe\n" );
03845                         return false;
03846                 }
03847                 return true;
03848         }
03849 
03857         public function getParentCategories() {
03858                 global $wgContLang;
03859 
03860                 $data = array();
03861 
03862                 $titleKey = $this->getArticleID();
03863 
03864                 if ( $titleKey === 0 ) {
03865                         return $data;
03866                 }
03867 
03868                 $dbr = wfGetDB( DB_SLAVE );
03869 
03870                 $res = $dbr->select( 'categorylinks', '*',
03871                         array(
03872                                 'cl_from' => $titleKey,
03873                         ),
03874                         __METHOD__,
03875                         array()
03876                 );
03877 
03878                 if ( $dbr->numRows( $res ) > 0 ) {
03879                         foreach ( $res as $row ) {
03880                                 // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
03881                                 $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
03882                         }
03883                 }
03884                 return $data;
03885         }
03886 
03893         public function getParentCategoryTree( $children = array() ) {
03894                 $stack = array();
03895                 $parents = $this->getParentCategories();
03896 
03897                 if ( $parents ) {
03898                         foreach ( $parents as $parent => $current ) {
03899                                 if ( array_key_exists( $parent, $children ) ) {
03900                                         # Circular reference
03901                                         $stack[$parent] = array();
03902                                 } else {
03903                                         $nt = Title::newFromText( $parent );
03904                                         if ( $nt ) {
03905                                                 $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
03906                                         }
03907                                 }
03908                         }
03909                 }
03910 
03911                 return $stack;
03912         }
03913 
03920         public function pageCond() {
03921                 if ( $this->mArticleID > 0 ) {
03922                         // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
03923                         return array( 'page_id' => $this->mArticleID );
03924                 } else {
03925                         return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
03926                 }
03927         }
03928 
03936         public function getPreviousRevisionID( $revId, $flags = 0 ) {
03937                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03938                 $revId = $db->selectField( 'revision', 'rev_id',
03939                         array(
03940                                 'rev_page' => $this->getArticleID( $flags ),
03941                                 'rev_id < ' . intval( $revId )
03942                         ),
03943                         __METHOD__,
03944                         array( 'ORDER BY' => 'rev_id DESC' )
03945                 );
03946 
03947                 if ( $revId === false ) {
03948                         return false;
03949                 } else {
03950                         return intval( $revId );
03951                 }
03952         }
03953 
03961         public function getNextRevisionID( $revId, $flags = 0 ) {
03962                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03963                 $revId = $db->selectField( 'revision', 'rev_id',
03964                         array(
03965                                 'rev_page' => $this->getArticleID( $flags ),
03966                                 'rev_id > ' . intval( $revId )
03967                         ),
03968                         __METHOD__,
03969                         array( 'ORDER BY' => 'rev_id' )
03970                 );
03971 
03972                 if ( $revId === false ) {
03973                         return false;
03974                 } else {
03975                         return intval( $revId );
03976                 }
03977         }
03978 
03985         public function getFirstRevision( $flags = 0 ) {
03986                 $pageId = $this->getArticleID( $flags );
03987                 if ( $pageId ) {
03988                         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03989                         $row = $db->selectRow( 'revision', Revision::selectFields(),
03990                                 array( 'rev_page' => $pageId ),
03991                                 __METHOD__,
03992                                 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
03993                         );
03994                         if ( $row ) {
03995                                 return new Revision( $row );
03996                         }
03997                 }
03998                 return null;
03999         }
04000 
04007         public function getEarliestRevTime( $flags = 0 ) {
04008                 $rev = $this->getFirstRevision( $flags );
04009                 return $rev ? $rev->getTimestamp() : null;
04010         }
04011 
04017         public function isNewPage() {
04018                 $dbr = wfGetDB( DB_SLAVE );
04019                 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
04020         }
04021 
04027         public function isBigDeletion() {
04028                 global $wgDeleteRevisionsLimit;
04029 
04030                 if ( !$wgDeleteRevisionsLimit ) {
04031                         return false;
04032                 }
04033 
04034                 $revCount = $this->estimateRevisionCount();
04035                 return $revCount > $wgDeleteRevisionsLimit;
04036         }
04037 
04043         public function estimateRevisionCount() {
04044                 if ( !$this->exists() ) {
04045                         return 0;
04046                 }
04047 
04048                 if ( $this->mEstimateRevisions === null ) {
04049                         $dbr = wfGetDB( DB_SLAVE );
04050                         $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
04051                                 array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
04052                 }
04053 
04054                 return $this->mEstimateRevisions;
04055         }
04056 
04065         public function countRevisionsBetween( $old, $new ) {
04066                 if ( !( $old instanceof Revision ) ) {
04067                         $old = Revision::newFromTitle( $this, (int)$old );
04068                 }
04069                 if ( !( $new instanceof Revision ) ) {
04070                         $new = Revision::newFromTitle( $this, (int)$new );
04071                 }
04072                 if ( !$old || !$new ) {
04073                         return 0; // nothing to compare
04074                 }
04075                 $dbr = wfGetDB( DB_SLAVE );
04076                 return (int)$dbr->selectField( 'revision', 'count(*)',
04077                         array(
04078                                 'rev_page' => $this->getArticleID(),
04079                                 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04080                                 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04081                         ),
04082                         __METHOD__
04083                 );
04084         }
04085 
04100         public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
04101                 if ( !( $old instanceof Revision ) ) {
04102                         $old = Revision::newFromTitle( $this, (int)$old );
04103                 }
04104                 if ( !( $new instanceof Revision ) ) {
04105                         $new = Revision::newFromTitle( $this, (int)$new );
04106                 }
04107                 // XXX: what if Revision objects are passed in, but they don't refer to this title?
04108                 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
04109                 // in the sanity check below?
04110                 if ( !$old || !$new ) {
04111                         return 0; // nothing to compare
04112                 }
04113                 $old_cmp = '>';
04114                 $new_cmp = '<';
04115                 $options = (array) $options;
04116                 if ( in_array( 'include_old', $options ) ) {
04117                         $old_cmp = '>=';
04118                 }
04119                 if ( in_array( 'include_new', $options ) ) {
04120                         $new_cmp = '<=';
04121                 }
04122                 if ( in_array( 'include_both', $options ) ) {
04123                         $old_cmp = '>=';
04124                         $new_cmp = '<=';
04125                 }
04126                 // No DB query needed if $old and $new are the same or successive revisions:
04127                 if ( $old->getId() === $new->getId() ) {
04128                         return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04129                 } else if ( $old->getId() === $new->getParentId() ) {
04130                         if ( $old_cmp === '>' || $new_cmp === '<' ) {
04131                                 return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04132                         }
04133                         return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
04134                 }
04135                 $dbr = wfGetDB( DB_SLAVE );
04136                 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
04137                         array(
04138                                 'rev_page' => $this->getArticleID(),
04139                                 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04140                                 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04141                         ), __METHOD__,
04142                         array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
04143                 );
04144                 return (int)$dbr->numRows( $res );
04145         }
04146 
04153         public function equals( Title $title ) {
04154                 // Note: === is necessary for proper matching of number-like titles.
04155                 return $this->getInterwiki() === $title->getInterwiki()
04156                         && $this->getNamespace() == $title->getNamespace()
04157                         && $this->getDBkey() === $title->getDBkey();
04158         }
04159 
04166         public function isSubpageOf( Title $title ) {
04167                 return $this->getInterwiki() === $title->getInterwiki()
04168                         && $this->getNamespace() == $title->getNamespace()
04169                         && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
04170         }
04171 
04181         public function exists() {
04182                 return $this->getArticleID() != 0;
04183         }
04184 
04201         public function isAlwaysKnown() {
04202                 $isKnown = null;
04203 
04214                 wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
04215 
04216                 if ( !is_null( $isKnown ) ) {
04217                         return $isKnown;
04218                 }
04219 
04220                 if ( $this->mInterwiki != '' ) {
04221                         return true;  // any interwiki link might be viewable, for all we know
04222                 }
04223 
04224                 switch( $this->mNamespace ) {
04225                         case NS_MEDIA:
04226                         case NS_FILE:
04227                                 // file exists, possibly in a foreign repo
04228                                 return (bool)wfFindFile( $this );
04229                         case NS_SPECIAL:
04230                                 // valid special page
04231                                 return SpecialPageFactory::exists( $this->getDBkey() );
04232                         case NS_MAIN:
04233                                 // selflink, possibly with fragment
04234                                 return $this->mDbkeyform == '';
04235                         case NS_MEDIAWIKI:
04236                                 // known system message
04237                                 return $this->hasSourceText() !== false;
04238                         default:
04239                                 return false;
04240                 }
04241         }
04242 
04254         public function isKnown() {
04255                 return $this->isAlwaysKnown() || $this->exists();
04256         }
04257 
04263         public function hasSourceText() {
04264                 if ( $this->exists() ) {
04265                         return true;
04266                 }
04267 
04268                 if ( $this->mNamespace == NS_MEDIAWIKI ) {
04269                         // If the page doesn't exist but is a known system message, default
04270                         // message content will be displayed, same for language subpages-
04271                         // Use always content language to avoid loading hundreds of languages
04272                         // to get the link color.
04273                         global $wgContLang;
04274                         list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04275                         $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
04276                         return $message->exists();
04277                 }
04278 
04279                 return false;
04280         }
04281 
04287         public function getDefaultMessageText() {
04288                 global $wgContLang;
04289 
04290                 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
04291                         return false;
04292                 }
04293 
04294                 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04295                 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
04296 
04297                 if ( $message->exists() ) {
04298                         return $message->plain();
04299                 } else {
04300                         return false;
04301                 }
04302         }
04303 
04309         public function invalidateCache() {
04310                 if ( wfReadOnly() ) {
04311                         return false;
04312                 }
04313                 $dbw = wfGetDB( DB_MASTER );
04314                 $success = $dbw->update(
04315                         'page',
04316                         array( 'page_touched' => $dbw->timestamp() ),
04317                         $this->pageCond(),
04318                         __METHOD__
04319                 );
04320                 HTMLFileCache::clearFileCache( $this );
04321                 return $success;
04322         }
04323 
04329         public function touchLinks() {
04330                 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
04331                 $u->doUpdate();
04332 
04333                 if ( $this->getNamespace() == NS_CATEGORY ) {
04334                         $u = new HTMLCacheUpdate( $this, 'categorylinks' );
04335                         $u->doUpdate();
04336                 }
04337         }
04338 
04345         public function getTouched( $db = null ) {
04346                 $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
04347                 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
04348                 return $touched;
04349         }
04350 
04357         public function getNotificationTimestamp( $user = null ) {
04358                 global $wgUser, $wgShowUpdatedMarker;
04359                 // Assume current user if none given
04360                 if ( !$user ) {
04361                         $user = $wgUser;
04362                 }
04363                 // Check cache first
04364                 $uid = $user->getId();
04365                 // avoid isset here, as it'll return false for null entries
04366                 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
04367                         return $this->mNotificationTimestamp[$uid];
04368                 }
04369                 if ( !$uid || !$wgShowUpdatedMarker ) {
04370                         return $this->mNotificationTimestamp[$uid] = false;
04371                 }
04372                 // Don't cache too much!
04373                 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
04374                         $this->mNotificationTimestamp = array();
04375                 }
04376                 $dbr = wfGetDB( DB_SLAVE );
04377                 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
04378                         'wl_notificationtimestamp',
04379                         array(
04380                                 'wl_user' => $user->getId(),
04381                                 'wl_namespace' => $this->getNamespace(),
04382                                 'wl_title' => $this->getDBkey(),
04383                         ),
04384                         __METHOD__
04385                 );
04386                 return $this->mNotificationTimestamp[$uid];
04387         }
04388 
04395         public function getNamespaceKey( $prepend = 'nstab-' ) {
04396                 global $wgContLang;
04397                 // Gets the subject namespace if this title
04398                 $namespace = MWNamespace::getSubject( $this->getNamespace() );
04399                 // Checks if cononical namespace name exists for namespace
04400                 if ( MWNamespace::exists( $this->getNamespace() ) ) {
04401                         // Uses canonical namespace name
04402                         $namespaceKey = MWNamespace::getCanonicalName( $namespace );
04403                 } else {
04404                         // Uses text of namespace
04405                         $namespaceKey = $this->getSubjectNsText();
04406                 }
04407                 // Makes namespace key lowercase
04408                 $namespaceKey = $wgContLang->lc( $namespaceKey );
04409                 // Uses main
04410                 if ( $namespaceKey == '' ) {
04411                         $namespaceKey = 'main';
04412                 }
04413                 // Changes file to image for backwards compatibility
04414                 if ( $namespaceKey == 'file' ) {
04415                         $namespaceKey = 'image';
04416                 }
04417                 return $prepend . $namespaceKey;
04418         }
04419 
04426         public function getRedirectsHere( $ns = null ) {
04427                 $redirs = array();
04428 
04429                 $dbr = wfGetDB( DB_SLAVE );
04430                 $where = array(
04431                         'rd_namespace' => $this->getNamespace(),
04432                         'rd_title' => $this->getDBkey(),
04433                         'rd_from = page_id'
04434                 );
04435                 if ( $this->isExternal() ) {
04436                         $where['rd_interwiki'] = $this->getInterwiki();
04437                 } else {
04438                         $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
04439                 }
04440                 if ( !is_null( $ns ) ) {
04441                         $where['page_namespace'] = $ns;
04442                 }
04443 
04444                 $res = $dbr->select(
04445                         array( 'redirect', 'page' ),
04446                         array( 'page_namespace', 'page_title' ),
04447                         $where,
04448                         __METHOD__
04449                 );
04450 
04451                 foreach ( $res as $row ) {
04452                         $redirs[] = self::newFromRow( $row );
04453                 }
04454                 return $redirs;
04455         }
04456 
04462         public function isValidRedirectTarget() {
04463                 global $wgInvalidRedirectTargets;
04464 
04465                 // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
04466                 if ( $this->isSpecial( 'Userlogout' ) ) {
04467                         return false;
04468                 }
04469 
04470                 foreach ( $wgInvalidRedirectTargets as $target ) {
04471                         if ( $this->isSpecial( $target ) ) {
04472                                 return false;
04473                         }
04474                 }
04475 
04476                 return true;
04477         }
04478 
04484         public function getBacklinkCache() {
04485                 return BacklinkCache::get( $this );
04486         }
04487 
04493         public function canUseNoindex() {
04494                 global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
04495 
04496                 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
04497                         ? $wgContentNamespaces
04498                         : $wgExemptFromUserRobotsControl;
04499 
04500                 return !in_array( $this->mNamespace, $bannedNamespaces );
04501 
04502         }
04503 
04514         public function getCategorySortkey( $prefix = '' ) {
04515                 $unprefixed = $this->getText();
04516 
04517                 // Anything that uses this hook should only depend
04518                 // on the Title object passed in, and should probably
04519                 // tell the users to run updateCollations.php --force
04520                 // in order to re-sort existing category relations.
04521                 wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
04522                 if ( $prefix !== '' ) {
04523                         # Separate with a line feed, so the unprefixed part is only used as
04524                         # a tiebreaker when two pages have the exact same prefix.
04525                         # In UCA, tab is the only character that can sort above LF
04526                         # so we strip both of them from the original prefix.
04527                         $prefix = strtr( $prefix, "\n\t", '  ' );
04528                         return "$prefix\n$unprefixed";
04529                 }
04530                 return $unprefixed;
04531         }
04532 
04541         public function getPageLanguage() {
04542                 global $wgLang;
04543                 if ( $this->isSpecialPage() ) {
04544                         // special pages are in the user language
04545                         return $wgLang;
04546                 } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
04547                         // css/js should always be LTR and is, in fact, English
04548                         return wfGetLangObj( 'en' );
04549                 } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
04550                         // Parse mediawiki messages with correct target language
04551                         list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
04552                         return wfGetLangObj( $lang );
04553                 }
04554                 global $wgContLang;
04555                 // If nothing special, it should be in the wiki content language
04556                 $pageLang = $wgContLang;
04557                 // Hook at the end because we don't want to override the above stuff
04558                 wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
04559                 return wfGetLangObj( $pageLang );
04560         }
04561 
04570         public function getPageViewLanguage() {
04571                 $pageLang = $this->getPageLanguage();
04572                 // If this is nothing special (so the content is converted when viewed)
04573                 if ( !$this->isSpecialPage()
04574                         && !$this->isCssOrJsPage() && !$this->isCssJsSubpage()
04575                         && $this->getNamespace() !== NS_MEDIAWIKI
04576                 ) {
04577                         // If the user chooses a variant, the content is actually
04578                         // in a language whose code is the variant code.
04579                         $variant = $pageLang->getPreferredVariant();
04580                         if ( $pageLang->getCode() !== $variant ) {
04581                                 $pageLang = Language::factory( $variant );
04582                         }
04583                 }
04584                 return $pageLang;
04585         }
04586 }