MediaWiki  master
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         var $mContentModel = false;       // /< ID of the page's content model, i.e. one of the CONTENT_MODEL_XXX constants
00069         private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
00070         var $mRestrictions = array();     // /< Array of groups allowed to edit this article
00071         var $mOldRestrictions = false;
00072         var $mCascadeRestriction;         
00073         var $mCascadingRestrictions;      // Caching the results of getCascadeProtectionSources
00074         var $mRestrictionsExpiry = array(); 
00075         var $mHasCascadingRestrictions;   
00076         var $mCascadeSources;             
00077         var $mRestrictionsLoaded = false; 
00078         var $mPrefixedText;               
00079         var $mTitleProtection;            
00080         # Don't change the following default, NS_MAIN is hardcoded in several
00081         # places.  See bug 696.
00082         var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
00083                                                                           # Zero except in {{transclusion}} tags
00084         var $mWatched = null;             // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
00085         var $mLength = -1;                // /< The page length, 0 for special pages
00086         var $mRedirect = null;            // /< Is the article at this title a redirect?
00087         var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false
00088         var $mHasSubpage;                 // /< Whether a page has any subpages
00089         // @}
00090 
00091 
00095         /*protected*/ function __construct() { }
00096 
00105         public static function newFromDBkey( $key ) {
00106                 $t = new Title();
00107                 $t->mDbkeyform = $key;
00108                 if ( $t->secureAndSplit() ) {
00109                         return $t;
00110                 } else {
00111                         return null;
00112                 }
00113         }
00114 
00128         public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00129                 if ( is_object( $text ) ) {
00130                         throw new MWException( 'Title::newFromText given an object' );
00131                 }
00132 
00141                 if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00142                         return Title::$titleCache[$text];
00143                 }
00144 
00145                 # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
00146                 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
00147 
00148                 $t = new Title();
00149                 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00150                 $t->mDefaultNamespace = $defaultNamespace;
00151 
00152                 static $cachedcount = 0 ;
00153                 if ( $t->secureAndSplit() ) {
00154                         if ( $defaultNamespace == NS_MAIN ) {
00155                                 if ( $cachedcount >= self::CACHE_MAX ) {
00156                                         # Avoid memory leaks on mass operations...
00157                                         Title::$titleCache = array();
00158                                         $cachedcount = 0;
00159                                 }
00160                                 $cachedcount++;
00161                                 Title::$titleCache[$text] =& $t;
00162                         }
00163                         return $t;
00164                 } else {
00165                         $ret = null;
00166                         return $ret;
00167                 }
00168         }
00169 
00185         public static function newFromURL( $url ) {
00186                 $t = new Title();
00187 
00188                 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00189                 # but some URLs used it as a space replacement and they still come
00190                 # from some external search tools.
00191                 if ( strpos( self::legalChars(), '+' ) === false ) {
00192                         $url = str_replace( '+', ' ', $url );
00193                 }
00194 
00195                 $t->mDbkeyform = str_replace( ' ', '_', $url );
00196                 if ( $t->secureAndSplit() ) {
00197                         return $t;
00198                 } else {
00199                         return null;
00200                 }
00201         }
00202 
00209         protected static function getSelectFields() {
00210                 global $wgContentHandlerUseDB;
00211 
00212                 $fields = array(
00213                         'page_namespace', 'page_title', 'page_id',
00214                         'page_len', 'page_is_redirect', 'page_latest',
00215                 );
00216 
00217                 if ( $wgContentHandlerUseDB ) {
00218                         $fields[] = 'page_content_model';
00219                 }
00220 
00221                 return $fields;
00222         }
00223 
00231         public static function newFromID( $id, $flags = 0 ) {
00232                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00233                 $row = $db->selectRow(
00234                         'page',
00235                         self::getSelectFields(),
00236                         array( 'page_id' => $id ),
00237                         __METHOD__
00238                 );
00239                 if ( $row !== false ) {
00240                         $title = Title::newFromRow( $row );
00241                 } else {
00242                         $title = null;
00243                 }
00244                 return $title;
00245         }
00246 
00253         public static function newFromIDs( $ids ) {
00254                 if ( !count( $ids ) ) {
00255                         return array();
00256                 }
00257                 $dbr = wfGetDB( DB_SLAVE );
00258 
00259                 $res = $dbr->select(
00260                         'page',
00261                         self::getSelectFields(),
00262                         array( 'page_id' => $ids ),
00263                         __METHOD__
00264                 );
00265 
00266                 $titles = array();
00267                 foreach ( $res as $row ) {
00268                         $titles[] = Title::newFromRow( $row );
00269                 }
00270                 return $titles;
00271         }
00272 
00279         public static function newFromRow( $row ) {
00280                 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00281                 $t->loadFromRow( $row );
00282                 return $t;
00283         }
00284 
00291         public function loadFromRow( $row ) {
00292                 if ( $row ) { // page found
00293                         if ( isset( $row->page_id ) )
00294                                 $this->mArticleID = (int)$row->page_id;
00295                         if ( isset( $row->page_len ) )
00296                                 $this->mLength = (int)$row->page_len;
00297                         if ( isset( $row->page_is_redirect ) )
00298                                 $this->mRedirect = (bool)$row->page_is_redirect;
00299                         if ( isset( $row->page_latest ) )
00300                                 $this->mLatestID = (int)$row->page_latest;
00301                         if ( isset( $row->page_content_model ) )
00302                                 $this->mContentModel = strval( $row->page_content_model );
00303                         else
00304                                 $this->mContentModel = false; # initialized lazily in getContentModel()
00305                 } else { // page not found
00306                         $this->mArticleID = 0;
00307                         $this->mLength = 0;
00308                         $this->mRedirect = false;
00309                         $this->mLatestID = 0;
00310                         $this->mContentModel = false; # initialized lazily in getContentModel()
00311                 }
00312         }
00313 
00327         public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
00328                 $t = new Title();
00329                 $t->mInterwiki = $interwiki;
00330                 $t->mFragment = $fragment;
00331                 $t->mNamespace = $ns = intval( $ns );
00332                 $t->mDbkeyform = str_replace( ' ', '_', $title );
00333                 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00334                 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00335                 $t->mTextform = str_replace( '_', ' ', $title );
00336                 $t->mContentModel = false; # initialized lazily in getContentModel()
00337                 return $t;
00338         }
00339 
00351         public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
00352                 if ( !MWNamespace::exists( $ns ) ) {
00353                         return null;
00354                 }
00355 
00356                 $t = new Title();
00357                 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
00358                 if ( $t->secureAndSplit() ) {
00359                         return $t;
00360                 } else {
00361                         return null;
00362                 }
00363         }
00364 
00370         public static function newMainPage() {
00371                 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
00372                 // Don't give fatal errors if the message is broken
00373                 if ( !$title ) {
00374                         $title = Title::newFromText( 'Main Page' );
00375                 }
00376                 return $title;
00377         }
00378 
00389         public static function newFromRedirect( $text ) {
00390                 ContentHandler::deprecated( __METHOD__, '1.21' );
00391 
00392                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00393                 return $content->getRedirectTarget();
00394         }
00395 
00406         public static function newFromRedirectRecurse( $text ) {
00407                 ContentHandler::deprecated( __METHOD__, '1.21' );
00408 
00409                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00410                 return $content->getUltimateRedirectTarget();
00411         }
00412 
00423         public static function newFromRedirectArray( $text ) {
00424                 ContentHandler::deprecated( __METHOD__, '1.21' );
00425 
00426                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00427                 return $content->getRedirectChain();
00428         }
00429 
00436         public static function nameOf( $id ) {
00437                 $dbr = wfGetDB( DB_SLAVE );
00438 
00439                 $s = $dbr->selectRow(
00440                         'page',
00441                         array( 'page_namespace', 'page_title' ),
00442                         array( 'page_id' => $id ),
00443                         __METHOD__
00444                 );
00445                 if ( $s === false ) {
00446                         return null;
00447                 }
00448 
00449                 $n = self::makeName( $s->page_namespace, $s->page_title );
00450                 return $n;
00451         }
00452 
00458         public static function legalChars() {
00459                 global $wgLegalTitleChars;
00460                 return $wgLegalTitleChars;
00461         }
00462 
00470         static function getTitleInvalidRegex() {
00471                 static $rxTc = false;
00472                 if ( !$rxTc ) {
00473                         # Matching titles will be held as illegal.
00474                         $rxTc = '/' .
00475                                 # Any character not allowed is forbidden...
00476                                 '[^' . self::legalChars() . ']' .
00477                                 # URL percent encoding sequences interfere with the ability
00478                                 # to round-trip titles -- you can't link to them consistently.
00479                                 '|%[0-9A-Fa-f]{2}' .
00480                                 # XML/HTML character references produce similar issues.
00481                                 '|&[A-Za-z0-9\x80-\xff]+;' .
00482                                 '|&#[0-9]+;' .
00483                                 '|&#x[0-9A-Fa-f]+;' .
00484                                 '/S';
00485                 }
00486 
00487                 return $rxTc;
00488         }
00489 
00498         public static function indexTitle( $ns, $title ) {
00499                 global $wgContLang;
00500 
00501                 $lc = SearchEngine::legalSearchChars() . '&#;';
00502                 $t = $wgContLang->normalizeForSearch( $title );
00503                 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00504                 $t = $wgContLang->lc( $t );
00505 
00506                 # Handle 's, s'
00507                 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00508                 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00509 
00510                 $t = preg_replace( "/\\s+/", ' ', $t );
00511 
00512                 if ( $ns == NS_FILE ) {
00513                         $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00514                 }
00515                 return trim( $t );
00516         }
00517 
00527         public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
00528                 global $wgContLang;
00529 
00530                 $namespace = $wgContLang->getNsText( $ns );
00531                 $name = $namespace == '' ? $title : "$namespace:$title";
00532                 if ( strval( $interwiki ) != '' ) {
00533                         $name = "$interwiki:$name";
00534                 }
00535                 if ( strval( $fragment ) != '' ) {
00536                         $name .= '#' . $fragment;
00537                 }
00538                 return $name;
00539         }
00540 
00547         static function escapeFragmentForURL( $fragment ) {
00548                 # Note that we don't urlencode the fragment.  urlencoded Unicode
00549                 # fragments appear not to work in IE (at least up to 7) or in at least
00550                 # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
00551                 # to care if they aren't encoded.
00552                 return Sanitizer::escapeId( $fragment, 'noninitial' );
00553         }
00554 
00563         public static function compare( $a, $b ) {
00564                 if ( $a->getNamespace() == $b->getNamespace() ) {
00565                         return strcmp( $a->getText(), $b->getText() );
00566                 } else {
00567                         return $a->getNamespace() - $b->getNamespace();
00568                 }
00569         }
00570 
00577         public function isLocal() {
00578                 if ( $this->mInterwiki != '' ) {
00579                         return Interwiki::fetch( $this->mInterwiki )->isLocal();
00580                 } else {
00581                         return true;
00582                 }
00583         }
00584 
00590         public function isExternal() {
00591                 return ( $this->mInterwiki != '' );
00592         }
00593 
00599         public function getInterwiki() {
00600                 return $this->mInterwiki;
00601         }
00602 
00609         public function isTrans() {
00610                 if ( $this->mInterwiki == '' ) {
00611                         return false;
00612                 }
00613 
00614                 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00615         }
00616 
00622         public function getTransWikiID() {
00623                 if ( $this->mInterwiki == '' ) {
00624                         return false;
00625                 }
00626 
00627                 return Interwiki::fetch( $this->mInterwiki )->getWikiID();
00628         }
00629 
00635         public function getText() {
00636                 return $this->mTextform;
00637         }
00638 
00644         public function getPartialURL() {
00645                 return $this->mUrlform;
00646         }
00647 
00653         public function getDBkey() {
00654                 return $this->mDbkeyform;
00655         }
00656 
00662         function getUserCaseDBKey() {
00663                 return $this->mUserCaseDBKey;
00664         }
00665 
00671         public function getNamespace() {
00672                 return $this->mNamespace;
00673         }
00674 
00680         public function getContentModel() {
00681                 if ( !$this->mContentModel ) {
00682                         $linkCache = LinkCache::singleton();
00683                         $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
00684                 }
00685 
00686                 if ( !$this->mContentModel ) {
00687                         $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
00688                 }
00689 
00690                 if( !$this->mContentModel ) {
00691                         throw new MWException( "failed to determin content model!" );
00692                 }
00693 
00694                 return $this->mContentModel;
00695         }
00696 
00703         public function hasContentModel( $id ) {
00704                 return $this->getContentModel() == $id;
00705         }
00706 
00712         public function getNsText() {
00713                 global $wgContLang;
00714 
00715                 if ( $this->mInterwiki != '' ) {
00716                         // This probably shouldn't even happen. ohh man, oh yuck.
00717                         // But for interwiki transclusion it sometimes does.
00718                         // Shit. Shit shit shit.
00719                         //
00720                         // Use the canonical namespaces if possible to try to
00721                         // resolve a foreign namespace.
00722                         if ( MWNamespace::exists( $this->mNamespace ) ) {
00723                                 return MWNamespace::getCanonicalName( $this->mNamespace );
00724                         }
00725                 }
00726 
00727                 if ( $wgContLang->needsGenderDistinction() &&
00728                                 MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
00729                         $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
00730                         return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
00731                 }
00732 
00733                 return $wgContLang->getNsText( $this->mNamespace );
00734         }
00735 
00741         public function getSubjectNsText() {
00742                 global $wgContLang;
00743                 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00744         }
00745 
00751         public function getTalkNsText() {
00752                 global $wgContLang;
00753                 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00754         }
00755 
00761         public function canTalk() {
00762                 return( MWNamespace::canTalk( $this->mNamespace ) );
00763         }
00764 
00771         public function canExist() {
00772                 return $this->mNamespace >= NS_MAIN;
00773         }
00774 
00780         public function isWatchable() {
00781                 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
00782         }
00783 
00789         public function isSpecialPage() {
00790                 return $this->getNamespace() == NS_SPECIAL;
00791         }
00792 
00799         public function isSpecial( $name ) {
00800                 if ( $this->isSpecialPage() ) {
00801                         list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
00802                         if ( $name == $thisName ) {
00803                                 return true;
00804                         }
00805                 }
00806                 return false;
00807         }
00808 
00815         public function fixSpecialName() {
00816                 if ( $this->isSpecialPage() ) {
00817                         list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
00818                         if ( $canonicalName ) {
00819                                 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
00820                                 if ( $localName != $this->mDbkeyform ) {
00821                                         return Title::makeTitle( NS_SPECIAL, $localName );
00822                                 }
00823                         }
00824                 }
00825                 return $this;
00826         }
00827 
00838         public function inNamespace( $ns ) {
00839                 return MWNamespace::equals( $this->getNamespace(), $ns );
00840         }
00841 
00849         public function inNamespaces( /* ... */ ) {
00850                 $namespaces = func_get_args();
00851                 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
00852                         $namespaces = $namespaces[0];
00853                 }
00854 
00855                 foreach ( $namespaces as $ns ) {
00856                         if ( $this->inNamespace( $ns ) ) {
00857                                 return true;
00858                         }
00859                 }
00860 
00861                 return false;
00862         }
00863 
00877         public function hasSubjectNamespace( $ns ) {
00878                 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
00879         }
00880 
00888         public function isContentPage() {
00889                 return MWNamespace::isContent( $this->getNamespace() );
00890         }
00891 
00898         public function isMovable() {
00899                 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
00900                         // Interwiki title or immovable namespace. Hooks don't get to override here
00901                         return false;
00902                 }
00903 
00904                 $result = true;
00905                 wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
00906                 return $result;
00907         }
00908 
00919         public function isMainPage() {
00920                 return $this->equals( Title::newMainPage() );
00921         }
00922 
00928         public function isSubpage() {
00929                 return MWNamespace::hasSubpages( $this->mNamespace )
00930                         ? strpos( $this->getText(), '/' ) !== false
00931                         : false;
00932         }
00933 
00939         public function isConversionTable() {
00940                 //@todo: ConversionTable should become a separate content model.
00941 
00942                 return $this->getNamespace() == NS_MEDIAWIKI &&
00943                         strpos( $this->getText(), 'Conversiontable/' ) === 0;
00944         }
00945 
00951         public function isWikitextPage() {
00952                 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
00953         }
00954 
00966         public function isCssOrJsPage() {
00967                 $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
00968                         && ( $this->hasContentModel( CONTENT_MODEL_CSS )
00969                                 || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
00970 
00971                 #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
00972                 #      hook funktions can force this method to return true even outside the mediawiki namespace.
00973 
00974                 wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
00975 
00976                 return $isCssOrJsPage;
00977         }
00978 
00983         public function isCssJsSubpage() {
00984                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
00985                                 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
00986                                         || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
00987         }
00988 
00994         public function getSkinFromCssJsSubpage() {
00995                 $subpage = explode( '/', $this->mTextform );
00996                 $subpage = $subpage[ count( $subpage ) - 1 ];
00997                 $lastdot = strrpos( $subpage, '.' );
00998                 if ( $lastdot === false )
00999                         return $subpage; # Never happens: only called for names ending in '.css' or '.js'
01000                 return substr( $subpage, 0, $lastdot );
01001         }
01002 
01008         public function isCssSubpage() {
01009                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
01010                         && $this->hasContentModel( CONTENT_MODEL_CSS ) );
01011         }
01012 
01018         public function isJsSubpage() {
01019                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
01020                         && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
01021         }
01022 
01028         public function isTalkPage() {
01029                 return MWNamespace::isTalk( $this->getNamespace() );
01030         }
01031 
01037         public function getTalkPage() {
01038                 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
01039         }
01040 
01047         public function getSubjectPage() {
01048                 // Is this the same title?
01049                 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
01050                 if ( $this->getNamespace() == $subjectNS ) {
01051                         return $this;
01052                 }
01053                 return Title::makeTitle( $subjectNS, $this->getDBkey() );
01054         }
01055 
01061         public function getDefaultNamespace() {
01062                 return $this->mDefaultNamespace;
01063         }
01064 
01071         public function getIndexTitle() {
01072                 return Title::indexTitle( $this->mNamespace, $this->mTextform );
01073         }
01074 
01080         public function getFragment() {
01081                 return $this->mFragment;
01082         }
01083 
01088         public function getFragmentForURL() {
01089                 if ( $this->mFragment == '' ) {
01090                         return '';
01091                 } else {
01092                         return '#' . Title::escapeFragmentForURL( $this->mFragment );
01093                 }
01094         }
01095 
01106         public function setFragment( $fragment ) {
01107                 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
01108         }
01109 
01118         private function prefix( $name ) {
01119                 $p = '';
01120                 if ( $this->mInterwiki != '' ) {
01121                         $p = $this->mInterwiki . ':';
01122                 }
01123 
01124                 if ( 0 != $this->mNamespace ) {
01125                         $p .= $this->getNsText() . ':';
01126                 }
01127                 return $p . $name;
01128         }
01129 
01136         public function getPrefixedDBkey() {
01137                 $s = $this->prefix( $this->mDbkeyform );
01138                 $s = str_replace( ' ', '_', $s );
01139                 return $s;
01140         }
01141 
01148         public function getPrefixedText() {
01149                 // @todo FIXME: Bad usage of empty() ?
01150                 if ( empty( $this->mPrefixedText ) ) {
01151                         $s = $this->prefix( $this->mTextform );
01152                         $s = str_replace( '_', ' ', $s );
01153                         $this->mPrefixedText = $s;
01154                 }
01155                 return $this->mPrefixedText;
01156         }
01157 
01163         public function __toString() {
01164                 return $this->getPrefixedText();
01165         }
01166 
01173         public function getFullText() {
01174                 $text = $this->getPrefixedText();
01175                 if ( $this->mFragment != '' ) {
01176                         $text .= '#' . $this->mFragment;
01177                 }
01178                 return $text;
01179         }
01180 
01193         public function getRootText() {
01194                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01195                         return $this->getText();
01196                 }
01197 
01198                 return strtok( $this->getText(), '/' );
01199         }
01200 
01213         public function getRootTitle() {
01214                 return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
01215         }
01216 
01228         public function getBaseText() {
01229                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01230                         return $this->getText();
01231                 }
01232 
01233                 $parts = explode( '/', $this->getText() );
01234                 # Don't discard the real title if there's no subpage involved
01235                 if ( count( $parts ) > 1 ) {
01236                         unset( $parts[count( $parts ) - 1] );
01237                 }
01238                 return implode( '/', $parts );
01239         }
01240 
01253         public function getBaseTitle() {
01254                 return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
01255         }
01256 
01268         public function getSubpageText() {
01269                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01270                         return( $this->mTextform );
01271                 }
01272                 $parts = explode( '/', $this->mTextform );
01273                 return( $parts[count( $parts ) - 1] );
01274         }
01275 
01289         public function getSubpage( $text ) {
01290                 return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
01291         }
01292 
01299         public function getEscapedText() {
01300                 wfDeprecated( __METHOD__, '1.19' );
01301                 return htmlspecialchars( $this->getPrefixedText() );
01302         }
01303 
01309         public function getSubpageUrlForm() {
01310                 $text = $this->getSubpageText();
01311                 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
01312                 return( $text );
01313         }
01314 
01320         public function getPrefixedURL() {
01321                 $s = $this->prefix( $this->mDbkeyform );
01322                 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
01323                 return $s;
01324         }
01325 
01339         private static function fixUrlQueryArgs( $query, $query2 = false ) {
01340                 if( $query2 !== false ) {
01341                         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" );
01342                 }
01343                 if ( is_array( $query ) ) {
01344                         $query = wfArrayToCGI( $query );
01345                 }
01346                 if ( $query2 ) {
01347                         if ( is_string( $query2 ) ) {
01348                                 // $query2 is a string, we will consider this to be
01349                                 // a deprecated $variant argument and add it to the query
01350                                 $query2 = wfArrayToCGI( array( 'variant' => $query2 ) );
01351                         } else {
01352                                 $query2 = wfArrayToCGI( $query2 );
01353                         }
01354                         // If we have $query content add a & to it first
01355                         if ( $query ) {
01356                                 $query .= '&';
01357                         }
01358                         // Now append the queries together
01359                         $query .= $query2;
01360                 }
01361                 return $query;
01362         }
01363 
01377         public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01378                 $query = self::fixUrlQueryArgs( $query, $query2 );
01379 
01380                 # Hand off all the decisions on urls to getLocalURL
01381                 $url = $this->getLocalURL( $query );
01382 
01383                 # Expand the url to make it a full url. Note that getLocalURL has the
01384                 # potential to output full urls for a variety of reasons, so we use
01385                 # wfExpandUrl instead of simply prepending $wgServer
01386                 $url = wfExpandUrl( $url, $proto );
01387 
01388                 # Finally, add the fragment.
01389                 $url .= $this->getFragmentForURL();
01390 
01391                 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
01392                 return $url;
01393         }
01394 
01413         public function getLocalURL( $query = '', $query2 = false ) {
01414                 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
01415 
01416                 $query = self::fixUrlQueryArgs( $query, $query2 );
01417 
01418                 $interwiki = Interwiki::fetch( $this->mInterwiki );
01419                 if ( $interwiki ) {
01420                         $namespace = $this->getNsText();
01421                         if ( $namespace != '' ) {
01422                                 # Can this actually happen? Interwikis shouldn't be parsed.
01423                                 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
01424                                 $namespace .= ':';
01425                         }
01426                         $url = $interwiki->getURL( $namespace . $this->getDBkey() );
01427                         $url = wfAppendQuery( $url, $query );
01428                 } else {
01429                         $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
01430                         if ( $query == '' ) {
01431                                 $url = str_replace( '$1', $dbkey, $wgArticlePath );
01432                                 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
01433                         } else {
01434                                 global $wgVariantArticlePath, $wgActionPaths;
01435                                 $url = false;
01436                                 $matches = array();
01437 
01438                                 if ( !empty( $wgActionPaths ) &&
01439                                         preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
01440                                 {
01441                                         $action = urldecode( $matches[2] );
01442                                         if ( isset( $wgActionPaths[$action] ) ) {
01443                                                 $query = $matches[1];
01444                                                 if ( isset( $matches[4] ) ) {
01445                                                         $query .= $matches[4];
01446                                                 }
01447                                                 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
01448                                                 if ( $query != '' ) {
01449                                                         $url = wfAppendQuery( $url, $query );
01450                                                 }
01451                                         }
01452                                 }
01453 
01454                                 if ( $url === false &&
01455                                         $wgVariantArticlePath &&
01456                                         $this->getPageLanguage()->hasVariants() &&
01457                                         preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
01458                                 {
01459                                         $variant = urldecode( $matches[1] );
01460                                         if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
01461                                                 // Only do the variant replacement if the given variant is a valid
01462                                                 // variant for the page's language.
01463                                                 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
01464                                                 $url = str_replace( '$1', $dbkey, $url );
01465                                         }
01466                                 }
01467 
01468                                 if ( $url === false ) {
01469                                         if ( $query == '-' ) {
01470                                                 $query = '';
01471                                         }
01472                                         $url = "{$wgScript}?title={$dbkey}&{$query}";
01473                                 }
01474                         }
01475 
01476                         wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
01477 
01478                         // @todo FIXME: This causes breakage in various places when we
01479                         // actually expected a local URL and end up with dupe prefixes.
01480                         if ( $wgRequest->getVal( 'action' ) == 'render' ) {
01481                                 $url = $wgServer . $url;
01482                         }
01483                 }
01484                 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
01485                 return $url;
01486         }
01487 
01506         public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01507                 wfProfileIn( __METHOD__ );
01508                 if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
01509                         $ret = $this->getFullURL( $query, $query2, $proto );
01510                 } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
01511                         $ret = $this->getFragmentForURL();
01512                 } else {
01513                         $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
01514                 }
01515                 wfProfileOut( __METHOD__ );
01516                 return $ret;
01517         }
01518 
01530         public function escapeLocalURL( $query = '', $query2 = false ) {
01531                 wfDeprecated( __METHOD__, '1.19' );
01532                 return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
01533         }
01534 
01544         public function escapeFullURL( $query = '', $query2 = false ) {
01545                 wfDeprecated( __METHOD__, '1.19' );
01546                 return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
01547         }
01548 
01563         public function getInternalURL( $query = '', $query2 = false ) {
01564                 global $wgInternalServer, $wgServer;
01565                 $query = self::fixUrlQueryArgs( $query, $query2 );
01566                 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
01567                 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
01568                 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
01569                 return $url;
01570         }
01571 
01585         public function getCanonicalURL( $query = '', $query2 = false ) {
01586                 $query = self::fixUrlQueryArgs( $query, $query2 );
01587                 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
01588                 wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
01589                 return $url;
01590         }
01591 
01601         public function escapeCanonicalURL( $query = '', $query2 = false ) {
01602                 wfDeprecated( __METHOD__, '1.19' );
01603                 return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
01604         }
01605 
01612         public function getEditURL() {
01613                 if ( $this->mInterwiki != '' ) {
01614                         return '';
01615                 }
01616                 $s = $this->getLocalURL( 'action=edit' );
01617 
01618                 return $s;
01619         }
01620 
01627         public function userIsWatching() {
01628                 global $wgUser;
01629 
01630                 if ( is_null( $this->mWatched ) ) {
01631                         if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
01632                                 $this->mWatched = false;
01633                         } else {
01634                                 $this->mWatched = $wgUser->isWatched( $this );
01635                         }
01636                 }
01637                 return $this->mWatched;
01638         }
01639 
01647         public function userCanRead() {
01648                 wfDeprecated( __METHOD__, '1.19' );
01649                 return $this->userCan( 'read' );
01650         }
01651 
01667         public function quickUserCan( $action, $user = null ) {
01668                 return $this->userCan( $action, $user, false );
01669         }
01670 
01681         public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
01682                 if ( !$user instanceof User ) {
01683                         global $wgUser;
01684                         $user = $wgUser;
01685                 }
01686                 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
01687         }
01688 
01702         public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01703                 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01704 
01705                 // Remove the errors being ignored.
01706                 foreach ( $errors as $index => $error ) {
01707                         $error_key = is_array( $error ) ? $error[0] : $error;
01708 
01709                         if ( in_array( $error_key, $ignoreErrors ) ) {
01710                                 unset( $errors[$index] );
01711                         }
01712                 }
01713 
01714                 return $errors;
01715         }
01716 
01728         private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01729                 if ( $action == 'create' ) {
01730                         if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01731                                  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
01732                                 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
01733                         }
01734                 } elseif ( $action == 'move' ) {
01735                         if ( !$user->isAllowed( 'move-rootuserpages' )
01736                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01737                                 // Show user page-specific message only if the user can move other pages
01738                                 $errors[] = array( 'cant-move-user-page' );
01739                         }
01740 
01741                         // Check if user is allowed to move files if it's a file
01742                         if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01743                                 $errors[] = array( 'movenotallowedfile' );
01744                         }
01745 
01746                         if ( !$user->isAllowed( 'move' ) ) {
01747                                 // User can't move anything
01748                                 $userCanMove = User::groupHasPermission( 'user', 'move' );
01749                                 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
01750                                 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01751                                         // custom message if logged-in users without any special rights can move
01752                                         $errors[] = array( 'movenologintext' );
01753                                 } else {
01754                                         $errors[] = array( 'movenotallowed' );
01755                                 }
01756                         }
01757                 } elseif ( $action == 'move-target' ) {
01758                         if ( !$user->isAllowed( 'move' ) ) {
01759                                 // User can't move anything
01760                                 $errors[] = array( 'movenotallowed' );
01761                         } elseif ( !$user->isAllowed( 'move-rootuserpages' )
01762                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01763                                 // Show user page-specific message only if the user can move other pages
01764                                 $errors[] = array( 'cant-move-to-user-page' );
01765                         }
01766                 } elseif ( !$user->isAllowed( $action ) ) {
01767                         $errors[] = $this->missingPermissionError( $action, $short );
01768                 }
01769 
01770                 return $errors;
01771         }
01772 
01781         private function resultToError( $errors, $result ) {
01782                 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
01783                         // A single array representing an error
01784                         $errors[] = $result;
01785                 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
01786                         // A nested array representing multiple errors
01787                         $errors = array_merge( $errors, $result );
01788                 } elseif ( $result !== '' && is_string( $result ) ) {
01789                         // A string representing a message-id
01790                         $errors[] = array( $result );
01791                 } elseif ( $result === false ) {
01792                         // a generic "We don't want them to do that"
01793                         $errors[] = array( 'badaccess-group0' );
01794                 }
01795                 return $errors;
01796         }
01797 
01809         private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
01810                 // Use getUserPermissionsErrors instead
01811                 $result = '';
01812                 if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01813                         return $result ? array() : array( array( 'badaccess-group0' ) );
01814                 }
01815                 // Check getUserPermissionsErrors hook
01816                 if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
01817                         $errors = $this->resultToError( $errors, $result );
01818                 }
01819                 // Check getUserPermissionsErrorsExpensive hook
01820                 if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) &&
01821                          !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
01822                         $errors = $this->resultToError( $errors, $result );
01823                 }
01824 
01825                 return $errors;
01826         }
01827 
01839         private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01840                 # Only 'createaccount' and 'execute' can be performed on
01841                 # special pages, which don't actually exist in the DB.
01842                 $specialOKActions = array( 'createaccount', 'execute', 'read' );
01843                 if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
01844                         $errors[] = array( 'ns-specialprotected' );
01845                 }
01846 
01847                 # Check $wgNamespaceProtection for restricted namespaces
01848                 if ( $this->isNamespaceProtected( $user ) ) {
01849                         $ns = $this->mNamespace == NS_MAIN ?
01850                                 wfMessage( 'nstab-main' )->text() : $this->getNsText();
01851                         $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
01852                                 array( 'protectedinterface' ) : array( 'namespaceprotected',  $ns );
01853                 }
01854 
01855                 return $errors;
01856         }
01857 
01869         private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01870                 # Protect css/js subpages of user pages
01871                 # XXX: this might be better using restrictions
01872                 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
01873                 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
01874                                 && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
01875                         if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
01876                                 $errors[] = array( 'customcssprotected' );
01877                         } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
01878                                 $errors[] = array( 'customjsprotected' );
01879                         }
01880                 }
01881 
01882                 return $errors;
01883         }
01884 
01898         private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01899                 foreach ( $this->getRestrictions( $action ) as $right ) {
01900                         // Backwards compatibility, rewrite sysop -> protect
01901                         if ( $right == 'sysop' ) {
01902                                 $right = 'protect';
01903                         }
01904                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01905                                 // Users with 'editprotected' permission can edit protected pages
01906                                 // without cascading option turned on.
01907                                 if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
01908                                         || $this->mCascadeRestriction )
01909                                 {
01910                                         $errors[] = array( 'protectedpagetext', $right );
01911                                 }
01912                         }
01913                 }
01914 
01915                 return $errors;
01916         }
01917 
01929         private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01930                 if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01931                         # We /could/ use the protection level on the source page, but it's
01932                         # fairly ugly as we have to establish a precedence hierarchy for pages
01933                         # included by multiple cascade-protected pages. So just restrict
01934                         # it to people with 'protect' permission, as they could remove the
01935                         # protection anyway.
01936                         list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01937                         # Cascading protection depends on more than this page...
01938                         # Several cascading protected pages may include this page...
01939                         # Check each cascading level
01940                         # This is only for protection restrictions, not for all actions
01941                         if ( isset( $restrictions[$action] ) ) {
01942                                 foreach ( $restrictions[$action] as $right ) {
01943                                         $right = ( $right == 'sysop' ) ? 'protect' : $right;
01944                                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01945                                                 $pages = '';
01946                                                 foreach ( $cascadingSources as $page )
01947                                                         $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01948                                                 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01949                                         }
01950                                 }
01951                         }
01952                 }
01953 
01954                 return $errors;
01955         }
01956 
01968         private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01969                 global $wgDeleteRevisionsLimit, $wgLang;
01970 
01971                 if ( $action == 'protect' ) {
01972                         if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
01973                                 // If they can't edit, they shouldn't protect.
01974                                 $errors[] = array( 'protect-cantedit' );
01975                         }
01976                 } elseif ( $action == 'create' ) {
01977                         $title_protection = $this->getTitleProtection();
01978                         if( $title_protection ) {
01979                                 if( $title_protection['pt_create_perm'] == 'sysop' ) {
01980                                         $title_protection['pt_create_perm'] = 'protect'; // B/C
01981                                 }
01982                                 if( $title_protection['pt_create_perm'] == '' ||
01983                                         !$user->isAllowed( $title_protection['pt_create_perm'] ) )
01984                                 {
01985                                         $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
01986                                 }
01987                         }
01988                 } elseif ( $action == 'move' ) {
01989                         // Check for immobile pages
01990                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01991                                 // Specific message for this case
01992                                 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
01993                         } elseif ( !$this->isMovable() ) {
01994                                 // Less specific message for rarer cases
01995                                 $errors[] = array( 'immobile-source-page' );
01996                         }
01997                 } elseif ( $action == 'move-target' ) {
01998                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01999                                 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
02000                         } elseif ( !$this->isMovable() ) {
02001                                 $errors[] = array( 'immobile-target-page' );
02002                         }
02003                 } elseif ( $action == 'delete' ) {
02004                         if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
02005                                 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
02006                         {
02007                                 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
02008                         }
02009                 }
02010                 return $errors;
02011         }
02012 
02024         private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
02025                 // Account creation blocks handled at userlogin.
02026                 // Unblocking handled in SpecialUnblock
02027                 if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
02028                         return $errors;
02029                 }
02030 
02031                 global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
02032 
02033                 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
02034                         $errors[] = array( 'confirmedittext' );
02035                 }
02036 
02037                 if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
02038                         // Don't block the user from editing their own talk page unless they've been
02039                         // explicitly blocked from that too.
02040                 } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
02041                         $block = $user->getBlock();
02042 
02043                         // This is from OutputPage::blockedPage
02044                         // Copied at r23888 by werdna
02045 
02046                         $id = $user->blockedBy();
02047                         $reason = $user->blockedFor();
02048                         if ( $reason == '' ) {
02049                                 $reason = wfMessage( 'blockednoreason' )->text();
02050                         }
02051                         $ip = $user->getRequest()->getIP();
02052 
02053                         if ( is_numeric( $id ) ) {
02054                                 $name = User::whoIs( $id );
02055                         } else {
02056                                 $name = $id;
02057                         }
02058 
02059                         $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
02060                         $blockid = $block->getId();
02061                         $blockExpiry = $block->getExpiry();
02062                         $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
02063                         if ( $blockExpiry == 'infinity' ) {
02064                                 $blockExpiry = wfMessage( 'infiniteblock' )->text();
02065                         } else {
02066                                 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
02067                         }
02068 
02069                         $intended = strval( $block->getTarget() );
02070 
02071                         $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
02072                                 $blockid, $blockExpiry, $intended, $blockTimestamp );
02073                 }
02074 
02075                 return $errors;
02076         }
02077 
02089         private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
02090                 global $wgWhitelistRead, $wgRevokePermissions;
02091                 static $useShortcut = null;
02092 
02093                 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
02094                 if ( is_null( $useShortcut ) ) {
02095                         $useShortcut = true;
02096                         if ( !User::groupHasPermission( '*', 'read' ) ) {
02097                                 # Not a public wiki, so no shortcut
02098                                 $useShortcut = false;
02099                         } elseif ( !empty( $wgRevokePermissions ) ) {
02106                                 foreach ( $wgRevokePermissions as $perms ) {
02107                                         if ( !empty( $perms['read'] ) ) {
02108                                                 # We might be removing the read right from the user, so no shortcut
02109                                                 $useShortcut = false;
02110                                                 break;
02111                                         }
02112                                 }
02113                         }
02114                 }
02115 
02116                 $whitelisted = false;
02117                 if ( $useShortcut ) {
02118                         # Shortcut for public wikis, allows skipping quite a bit of code
02119                         $whitelisted = true;
02120                 } elseif ( $user->isAllowed( 'read' ) ) {
02121                         # If the user is allowed to read pages, he is allowed to read all pages
02122                         $whitelisted = true;
02123                 } elseif ( $this->isSpecial( 'Userlogin' )
02124                         || $this->isSpecial( 'ChangePassword' )
02125                         || $this->isSpecial( 'PasswordReset' )
02126                 ) {
02127                         # Always grant access to the login page.
02128                         # Even anons need to be able to log in.
02129                         $whitelisted = true;
02130                 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
02131                         # Time to check the whitelist
02132                         # Only do these checks is there's something to check against
02133                         $name = $this->getPrefixedText();
02134                         $dbName = $this->getPrefixedDBKey();
02135 
02136                         // Check for explicit whitelisting with and without underscores
02137                         if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
02138                                 $whitelisted = true;
02139                         } elseif ( $this->getNamespace() == NS_MAIN ) {
02140                                 # Old settings might have the title prefixed with
02141                                 # a colon for main-namespace pages
02142                                 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
02143                                         $whitelisted = true;
02144                                 }
02145                         } elseif ( $this->isSpecialPage() ) {
02146                                 # If it's a special page, ditch the subpage bit and check again
02147                                 $name = $this->getDBkey();
02148                                 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
02149                                 if ( $name !== false ) {
02150                                         $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
02151                                         if ( in_array( $pure, $wgWhitelistRead, true ) ) {
02152                                                 $whitelisted = true;
02153                                         }
02154                                 }
02155                         }
02156                 }
02157 
02158                 if ( !$whitelisted ) {
02159                         # If the title is not whitelisted, give extensions a chance to do so...
02160                         wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
02161                         if ( !$whitelisted ) {
02162                                 $errors[] = $this->missingPermissionError( $action, $short );
02163                         }
02164                 }
02165 
02166                 return $errors;
02167         }
02168 
02177         private function missingPermissionError( $action, $short ) {
02178                 // We avoid expensive display logic for quickUserCan's and such
02179                 if ( $short ) {
02180                         return array( 'badaccess-group0' );
02181                 }
02182 
02183                 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
02184                         User::getGroupsWithPermission( $action ) );
02185 
02186                 if ( count( $groups ) ) {
02187                         global $wgLang;
02188                         return array(
02189                                 'badaccess-groups',
02190                                 $wgLang->commaList( $groups ),
02191                                 count( $groups )
02192                         );
02193                 } else {
02194                         return array( 'badaccess-group0' );
02195                 }
02196         }
02197 
02209         protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
02210                 wfProfileIn( __METHOD__ );
02211 
02212                 # Read has special handling
02213                 if ( $action == 'read' ) {
02214                         $checks = array(
02215                                 'checkPermissionHooks',
02216                                 'checkReadPermissions',
02217                         );
02218                 } else {
02219                         $checks = array(
02220                                 'checkQuickPermissions',
02221                                 'checkPermissionHooks',
02222                                 'checkSpecialsAndNSPermissions',
02223                                 'checkCSSandJSPermissions',
02224                                 'checkPageRestrictions',
02225                                 'checkCascadingSourcesRestrictions',
02226                                 'checkActionPermissions',
02227                                 'checkUserBlock'
02228                         );
02229                 }
02230 
02231                 $errors = array();
02232                 while( count( $checks ) > 0 &&
02233                                 !( $short && count( $errors ) > 0 ) ) {
02234                         $method = array_shift( $checks );
02235                         $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
02236                 }
02237 
02238                 wfProfileOut( __METHOD__ );
02239                 return $errors;
02240         }
02241 
02249         public function userCanEditCssSubpage() {
02250                 global $wgUser;
02251                 wfDeprecated( __METHOD__, '1.19' );
02252                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
02253                         || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02254         }
02255 
02263         public function userCanEditJsSubpage() {
02264                 global $wgUser;
02265                 wfDeprecated( __METHOD__, '1.19' );
02266                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
02267                            || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02268         }
02269 
02277         public static function getFilteredRestrictionTypes( $exists = true ) {
02278                 global $wgRestrictionTypes;
02279                 $types = $wgRestrictionTypes;
02280                 if ( $exists ) {
02281                         # Remove the create restriction for existing titles
02282                         $types = array_diff( $types, array( 'create' ) );
02283                 } else {
02284                         # Only the create and upload restrictions apply to non-existing titles
02285                         $types = array_intersect( $types, array( 'create', 'upload' ) );
02286                 }
02287                 return $types;
02288         }
02289 
02295         public function getRestrictionTypes() {
02296                 if ( $this->isSpecialPage() ) {
02297                         return array();
02298                 }
02299 
02300                 $types = self::getFilteredRestrictionTypes( $this->exists() );
02301 
02302                 if ( $this->getNamespace() != NS_FILE ) {
02303                         # Remove the upload restriction for non-file titles
02304                         $types = array_diff( $types, array( 'upload' ) );
02305                 }
02306 
02307                 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
02308 
02309                 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
02310                         $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
02311 
02312                 return $types;
02313         }
02314 
02322         private function getTitleProtection() {
02323                 // Can't protect pages in special namespaces
02324                 if ( $this->getNamespace() < 0 ) {
02325                         return false;
02326                 }
02327 
02328                 // Can't protect pages that exist.
02329                 if ( $this->exists() ) {
02330                         return false;
02331                 }
02332 
02333                 if ( !isset( $this->mTitleProtection ) ) {
02334                         $dbr = wfGetDB( DB_SLAVE );
02335                         $res = $dbr->select( 'protected_titles', '*',
02336                                 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02337                                 __METHOD__ );
02338 
02339                         // fetchRow returns false if there are no rows.
02340                         $this->mTitleProtection = $dbr->fetchRow( $res );
02341                 }
02342                 return $this->mTitleProtection;
02343         }
02344 
02354         public function updateTitleProtection( $create_perm, $reason, $expiry ) {
02355                 wfDeprecated( __METHOD__, '1.19' );
02356 
02357                 global $wgUser;
02358 
02359                 $limit = array( 'create' => $create_perm );
02360                 $expiry = array( 'create' => $expiry );
02361 
02362                 $page = WikiPage::factory( $this );
02363                 $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser );
02364 
02365                 return $status->isOK();
02366         }
02367 
02371         public function deleteTitleProtection() {
02372                 $dbw = wfGetDB( DB_MASTER );
02373 
02374                 $dbw->delete(
02375                         'protected_titles',
02376                         array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02377                         __METHOD__
02378                 );
02379                 $this->mTitleProtection = false;
02380         }
02381 
02388         public function isSemiProtected( $action = 'edit' ) {
02389                 if ( $this->exists() ) {
02390                         $restrictions = $this->getRestrictions( $action );
02391                         if ( count( $restrictions ) > 0 ) {
02392                                 foreach ( $restrictions as $restriction ) {
02393                                         if ( strtolower( $restriction ) != 'autoconfirmed' ) {
02394                                                 return false;
02395                                         }
02396                                 }
02397                         } else {
02398                                 # Not protected
02399                                 return false;
02400                         }
02401                         return true;
02402                 } else {
02403                         # If it doesn't exist, it can't be protected
02404                         return false;
02405                 }
02406         }
02407 
02415         public function isProtected( $action = '' ) {
02416                 global $wgRestrictionLevels;
02417 
02418                 $restrictionTypes = $this->getRestrictionTypes();
02419 
02420                 # Special pages have inherent protection
02421                 if( $this->isSpecialPage() ) {
02422                         return true;
02423                 }
02424 
02425                 # Check regular protection levels
02426                 foreach ( $restrictionTypes as $type ) {
02427                         if ( $action == $type || $action == '' ) {
02428                                 $r = $this->getRestrictions( $type );
02429                                 foreach ( $wgRestrictionLevels as $level ) {
02430                                         if ( in_array( $level, $r ) && $level != '' ) {
02431                                                 return true;
02432                                         }
02433                                 }
02434                         }
02435                 }
02436 
02437                 return false;
02438         }
02439 
02447         public function isNamespaceProtected( User $user ) {
02448                 global $wgNamespaceProtection;
02449 
02450                 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
02451                         foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
02452                                 if ( $right != '' && !$user->isAllowed( $right ) ) {
02453                                         return true;
02454                                 }
02455                         }
02456                 }
02457                 return false;
02458         }
02459 
02465         public function isCascadeProtected() {
02466                 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
02467                 return ( $sources > 0 );
02468         }
02469 
02480         public function getCascadeProtectionSources( $getPages = true ) {
02481                 global $wgContLang;
02482                 $pagerestrictions = array();
02483 
02484                 if ( isset( $this->mCascadeSources ) && $getPages ) {
02485                         return array( $this->mCascadeSources, $this->mCascadingRestrictions );
02486                 } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
02487                         return array( $this->mHasCascadingRestrictions, $pagerestrictions );
02488                 }
02489 
02490                 wfProfileIn( __METHOD__ );
02491 
02492                 $dbr = wfGetDB( DB_SLAVE );
02493 
02494                 if ( $this->getNamespace() == NS_FILE ) {
02495                         $tables = array( 'imagelinks', 'page_restrictions' );
02496                         $where_clauses = array(
02497                                 'il_to' => $this->getDBkey(),
02498                                 'il_from=pr_page',
02499                                 'pr_cascade' => 1
02500                         );
02501                 } else {
02502                         $tables = array( 'templatelinks', 'page_restrictions' );
02503                         $where_clauses = array(
02504                                 'tl_namespace' => $this->getNamespace(),
02505                                 'tl_title' => $this->getDBkey(),
02506                                 'tl_from=pr_page',
02507                                 'pr_cascade' => 1
02508                         );
02509                 }
02510 
02511                 if ( $getPages ) {
02512                         $cols = array( 'pr_page', 'page_namespace', 'page_title',
02513                                                    'pr_expiry', 'pr_type', 'pr_level' );
02514                         $where_clauses[] = 'page_id=pr_page';
02515                         $tables[] = 'page';
02516                 } else {
02517                         $cols = array( 'pr_expiry' );
02518                 }
02519 
02520                 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
02521 
02522                 $sources = $getPages ? array() : false;
02523                 $now = wfTimestampNow();
02524                 $purgeExpired = false;
02525 
02526                 foreach ( $res as $row ) {
02527                         $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02528                         if ( $expiry > $now ) {
02529                                 if ( $getPages ) {
02530                                         $page_id = $row->pr_page;
02531                                         $page_ns = $row->page_namespace;
02532                                         $page_title = $row->page_title;
02533                                         $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
02534                                         # Add groups needed for each restriction type if its not already there
02535                                         # Make sure this restriction type still exists
02536 
02537                                         if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
02538                                                 $pagerestrictions[$row->pr_type] = array();
02539                                         }
02540 
02541                                         if ( isset( $pagerestrictions[$row->pr_type] ) &&
02542                                                  !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) {
02543                                                 $pagerestrictions[$row->pr_type][] = $row->pr_level;
02544                                         }
02545                                 } else {
02546                                         $sources = true;
02547                                 }
02548                         } else {
02549                                 // Trigger lazy purge of expired restrictions from the db
02550                                 $purgeExpired = true;
02551                         }
02552                 }
02553                 if ( $purgeExpired ) {
02554                         Title::purgeExpiredRestrictions();
02555                 }
02556 
02557                 if ( $getPages ) {
02558                         $this->mCascadeSources = $sources;
02559                         $this->mCascadingRestrictions = $pagerestrictions;
02560                 } else {
02561                         $this->mHasCascadingRestrictions = $sources;
02562                 }
02563 
02564                 wfProfileOut( __METHOD__ );
02565                 return array( $sources, $pagerestrictions );
02566         }
02567 
02574         public function getRestrictions( $action ) {
02575                 if ( !$this->mRestrictionsLoaded ) {
02576                         $this->loadRestrictions();
02577                 }
02578                 return isset( $this->mRestrictions[$action] )
02579                                 ? $this->mRestrictions[$action]
02580                                 : array();
02581         }
02582 
02590         public function getRestrictionExpiry( $action ) {
02591                 if ( !$this->mRestrictionsLoaded ) {
02592                         $this->loadRestrictions();
02593                 }
02594                 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02595         }
02596 
02602         function areRestrictionsCascading() {
02603                 if ( !$this->mRestrictionsLoaded ) {
02604                         $this->loadRestrictions();
02605                 }
02606 
02607                 return $this->mCascadeRestriction;
02608         }
02609 
02617         private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
02618                 $rows = array();
02619 
02620                 foreach ( $res as $row ) {
02621                         $rows[] = $row;
02622                 }
02623 
02624                 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
02625         }
02626 
02636         public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
02637                 global $wgContLang;
02638                 $dbr = wfGetDB( DB_SLAVE );
02639 
02640                 $restrictionTypes = $this->getRestrictionTypes();
02641 
02642                 foreach ( $restrictionTypes as $type ) {
02643                         $this->mRestrictions[$type] = array();
02644                         $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
02645                 }
02646 
02647                 $this->mCascadeRestriction = false;
02648 
02649                 # Backwards-compatibility: also load the restrictions from the page record (old format).
02650 
02651                 if ( $oldFashionedRestrictions === null ) {
02652                         $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
02653                                 array( 'page_id' => $this->getArticleID() ), __METHOD__ );
02654                 }
02655 
02656                 if ( $oldFashionedRestrictions != '' ) {
02657 
02658                         foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
02659                                 $temp = explode( '=', trim( $restrict ) );
02660                                 if ( count( $temp ) == 1 ) {
02661                                         // old old format should be treated as edit/move restriction
02662                                         $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
02663                                         $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
02664                                 } else {
02665                                         $restriction = trim( $temp[1] );
02666                                         if( $restriction != '' ) { //some old entries are empty
02667                                                 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
02668                                         }
02669                                 }
02670                         }
02671 
02672                         $this->mOldRestrictions = true;
02673 
02674                 }
02675 
02676                 if ( count( $rows ) ) {
02677                         # Current system - load second to make them override.
02678                         $now = wfTimestampNow();
02679                         $purgeExpired = false;
02680 
02681                         # Cycle through all the restrictions.
02682                         foreach ( $rows as $row ) {
02683 
02684                                 // Don't take care of restrictions types that aren't allowed
02685                                 if ( !in_array( $row->pr_type, $restrictionTypes ) )
02686                                         continue;
02687 
02688                                 // This code should be refactored, now that it's being used more generally,
02689                                 // But I don't really see any harm in leaving it in Block for now -werdna
02690                                 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02691 
02692                                 // Only apply the restrictions if they haven't expired!
02693                                 if ( !$expiry || $expiry > $now ) {
02694                                         $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
02695                                         $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
02696 
02697                                         $this->mCascadeRestriction |= $row->pr_cascade;
02698                                 } else {
02699                                         // Trigger a lazy purge of expired restrictions
02700                                         $purgeExpired = true;
02701                                 }
02702                         }
02703 
02704                         if ( $purgeExpired ) {
02705                                 Title::purgeExpiredRestrictions();
02706                         }
02707                 }
02708 
02709                 $this->mRestrictionsLoaded = true;
02710         }
02711 
02718         public function loadRestrictions( $oldFashionedRestrictions = null ) {
02719                 global $wgContLang;
02720                 if ( !$this->mRestrictionsLoaded ) {
02721                         if ( $this->exists() ) {
02722                                 $dbr = wfGetDB( DB_SLAVE );
02723 
02724                                 $res = $dbr->select(
02725                                         'page_restrictions',
02726                                         '*',
02727                                         array( 'pr_page' => $this->getArticleID() ),
02728                                         __METHOD__
02729                                 );
02730 
02731                                 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
02732                         } else {
02733                                 $title_protection = $this->getTitleProtection();
02734 
02735                                 if ( $title_protection ) {
02736                                         $now = wfTimestampNow();
02737                                         $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
02738 
02739                                         if ( !$expiry || $expiry > $now ) {
02740                                                 // Apply the restrictions
02741                                                 $this->mRestrictionsExpiry['create'] = $expiry;
02742                                                 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
02743                                         } else { // Get rid of the old restrictions
02744                                                 Title::purgeExpiredRestrictions();
02745                                                 $this->mTitleProtection = false;
02746                                         }
02747                                 } else {
02748                                         $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
02749                                 }
02750                                 $this->mRestrictionsLoaded = true;
02751                         }
02752                 }
02753         }
02754 
02759         public function flushRestrictions() {
02760                 $this->mRestrictionsLoaded = false;
02761                 $this->mTitleProtection = null;
02762         }
02763 
02767         static function purgeExpiredRestrictions() {
02768                 $dbw = wfGetDB( DB_MASTER );
02769                 $dbw->delete(
02770                         'page_restrictions',
02771                         array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02772                         __METHOD__
02773                 );
02774 
02775                 $dbw->delete(
02776                         'protected_titles',
02777                         array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02778                         __METHOD__
02779                 );
02780         }
02781 
02787         public function hasSubpages() {
02788                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
02789                         # Duh
02790                         return false;
02791                 }
02792 
02793                 # We dynamically add a member variable for the purpose of this method
02794                 # alone to cache the result.  There's no point in having it hanging
02795                 # around uninitialized in every Title object; therefore we only add it
02796                 # if needed and don't declare it statically.
02797                 if ( isset( $this->mHasSubpages ) ) {
02798                         return $this->mHasSubpages;
02799                 }
02800 
02801                 $subpages = $this->getSubpages( 1 );
02802                 if ( $subpages instanceof TitleArray ) {
02803                         return $this->mHasSubpages = (bool)$subpages->count();
02804                 }
02805                 return $this->mHasSubpages = false;
02806         }
02807 
02815         public function getSubpages( $limit = -1 ) {
02816                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
02817                         return array();
02818                 }
02819 
02820                 $dbr = wfGetDB( DB_SLAVE );
02821                 $conds['page_namespace'] = $this->getNamespace();
02822                 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
02823                 $options = array();
02824                 if ( $limit > -1 ) {
02825                         $options['LIMIT'] = $limit;
02826                 }
02827                 return $this->mSubpages = TitleArray::newFromResult(
02828                         $dbr->select( 'page',
02829                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
02830                                 $conds,
02831                                 __METHOD__,
02832                                 $options
02833                         )
02834                 );
02835         }
02836 
02842         public function isDeleted() {
02843                 if ( $this->getNamespace() < 0 ) {
02844                         $n = 0;
02845                 } else {
02846                         $dbr = wfGetDB( DB_SLAVE );
02847 
02848                         $n = $dbr->selectField( 'archive', 'COUNT(*)',
02849                                 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02850                                 __METHOD__
02851                         );
02852                         if ( $this->getNamespace() == NS_FILE ) {
02853                                 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
02854                                         array( 'fa_name' => $this->getDBkey() ),
02855                                         __METHOD__
02856                                 );
02857                         }
02858                 }
02859                 return (int)$n;
02860         }
02861 
02867         public function isDeletedQuick() {
02868                 if ( $this->getNamespace() < 0 ) {
02869                         return false;
02870                 }
02871                 $dbr = wfGetDB( DB_SLAVE );
02872                 $deleted = (bool)$dbr->selectField( 'archive', '1',
02873                         array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02874                         __METHOD__
02875                 );
02876                 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
02877                         $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02878                                 array( 'fa_name' => $this->getDBkey() ),
02879                                 __METHOD__
02880                         );
02881                 }
02882                 return $deleted;
02883         }
02884 
02893         public function getArticleID( $flags = 0 ) {
02894                 if ( $this->getNamespace() < 0 ) {
02895                         return $this->mArticleID = 0;
02896                 }
02897                 $linkCache = LinkCache::singleton();
02898                 if ( $flags & self::GAID_FOR_UPDATE ) {
02899                         $oldUpdate = $linkCache->forUpdate( true );
02900                         $linkCache->clearLink( $this );
02901                         $this->mArticleID = $linkCache->addLinkObj( $this );
02902                         $linkCache->forUpdate( $oldUpdate );
02903                 } else {
02904                         if ( -1 == $this->mArticleID ) {
02905                                 $this->mArticleID = $linkCache->addLinkObj( $this );
02906                         }
02907                 }
02908                 return $this->mArticleID;
02909         }
02910 
02918         public function isRedirect( $flags = 0 ) {
02919                 if ( !is_null( $this->mRedirect ) ) {
02920                         return $this->mRedirect;
02921                 }
02922                 # Calling getArticleID() loads the field from cache as needed
02923                 if ( !$this->getArticleID( $flags ) ) {
02924                         return $this->mRedirect = false;
02925                 }
02926 
02927                 $linkCache = LinkCache::singleton();
02928                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02929                 if ( $cached === null ) {
02930                         // TODO: check the assumption that the cache actually knows about this title
02931                         // and handle this, such as get the title from the database.
02932                         // See https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
02933                         wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
02934                         wfDebug( wfBacktrace() );
02935                 }
02936 
02937                 $this->mRedirect = (bool)$cached;
02938 
02939                 return $this->mRedirect;
02940         }
02941 
02949         public function getLength( $flags = 0 ) {
02950                 if ( $this->mLength != -1 ) {
02951                         return $this->mLength;
02952                 }
02953                 # Calling getArticleID() loads the field from cache as needed
02954                 if ( !$this->getArticleID( $flags ) ) {
02955                         return $this->mLength = 0;
02956                 }
02957                 $linkCache = LinkCache::singleton();
02958                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
02959                 if ( $cached === null ) { # check the assumption that the cache actually knows about this title
02960                         # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
02961                         #      as a stop gap, perhaps log this, but don't throw an exception?
02962                         wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
02963                         wfDebug( wfBacktrace() );
02964                 }
02965 
02966                 $this->mLength = intval( $cached );
02967 
02968                 return $this->mLength;
02969         }
02970 
02977         public function getLatestRevID( $flags = 0 ) {
02978                 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
02979                         return intval( $this->mLatestID );
02980                 }
02981                 # Calling getArticleID() loads the field from cache as needed
02982                 if ( !$this->getArticleID( $flags ) ) {
02983                         return $this->mLatestID = 0;
02984                 }
02985                 $linkCache = LinkCache::singleton();
02986                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
02987                 if ( $cached === null ) { # check the assumption that the cache actually knows about this title
02988                         # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
02989                         #      as a stop gap, perhaps log this, but don't throw an exception?
02990                         throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
02991                 }
02992 
02993                 $this->mLatestID = intval( $cached );
02994 
02995                 return $this->mLatestID;
02996         }
02997 
03008         public function resetArticleID( $newid ) {
03009                 $linkCache = LinkCache::singleton();
03010                 $linkCache->clearLink( $this );
03011 
03012                 if ( $newid === false ) {
03013                         $this->mArticleID = -1;
03014                 } else {
03015                         $this->mArticleID = intval( $newid );
03016                 }
03017                 $this->mRestrictionsLoaded = false;
03018                 $this->mRestrictions = array();
03019                 $this->mRedirect = null;
03020                 $this->mLength = -1;
03021                 $this->mLatestID = false;
03022                 $this->mContentModel = false;
03023                 $this->mEstimateRevisions = null;
03024         }
03025 
03033         public static function capitalize( $text, $ns = NS_MAIN ) {
03034                 global $wgContLang;
03035 
03036                 if ( MWNamespace::isCapitalized( $ns ) ) {
03037                         return $wgContLang->ucfirst( $text );
03038                 } else {
03039                         return $text;
03040                 }
03041         }
03042 
03054         private function secureAndSplit() {
03055                 global $wgContLang, $wgLocalInterwiki;
03056 
03057                 # Initialisation
03058                 $this->mInterwiki = $this->mFragment = '';
03059                 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
03060 
03061                 $dbkey = $this->mDbkeyform;
03062 
03063                 # Strip Unicode bidi override characters.
03064                 # Sometimes they slip into cut-n-pasted page titles, where the
03065                 # override chars get included in list displays.
03066                 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
03067 
03068                 # Clean up whitespace
03069                 # Note: use of the /u option on preg_replace here will cause
03070                 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
03071                 # conveniently disabling them.
03072                 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
03073                 $dbkey = trim( $dbkey, '_' );
03074 
03075                 if ( $dbkey == '' ) {
03076                         return false;
03077                 }
03078 
03079                 if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
03080                         # Contained illegal UTF-8 sequences or forbidden Unicode chars.
03081                         return false;
03082                 }
03083 
03084                 $this->mDbkeyform = $dbkey;
03085 
03086                 # Initial colon indicates main namespace rather than specified default
03087                 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
03088                 if ( ':' == $dbkey[0] ) {
03089                         $this->mNamespace = NS_MAIN;
03090                         $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
03091                         $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
03092                 }
03093 
03094                 # Namespace or interwiki prefix
03095                 $firstPass = true;
03096                 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
03097                 do {
03098                         $m = array();
03099                         if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
03100                                 $p = $m[1];
03101                                 if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
03102                                         # Ordinary namespace
03103                                         $dbkey = $m[2];
03104                                         $this->mNamespace = $ns;
03105                                         # For Talk:X pages, check if X has a "namespace" prefix
03106                                         if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
03107                                                 if ( $wgContLang->getNsIndex( $x[1] ) ) {
03108                                                         # Disallow Talk:File:x type titles...
03109                                                         return false;
03110                                                 } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
03111                                                         # Disallow Talk:Interwiki:x type titles...
03112                                                         return false;
03113                                                 }
03114                                         }
03115                                 } elseif ( Interwiki::isValidInterwiki( $p ) ) {
03116                                         if ( !$firstPass ) {
03117                                                 # Can't make a local interwiki link to an interwiki link.
03118                                                 # That's just crazy!
03119                                                 return false;
03120                                         }
03121 
03122                                         # Interwiki link
03123                                         $dbkey = $m[2];
03124                                         $this->mInterwiki = $wgContLang->lc( $p );
03125 
03126                                         # Redundant interwiki prefix to the local wiki
03127                                         if ( $wgLocalInterwiki !== false
03128                                                 && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
03129                                         {
03130                                                 if ( $dbkey == '' ) {
03131                                                         # Can't have an empty self-link
03132                                                         return false;
03133                                                 }
03134                                                 $this->mInterwiki = '';
03135                                                 $firstPass = false;
03136                                                 # Do another namespace split...
03137                                                 continue;
03138                                         }
03139 
03140                                         # If there's an initial colon after the interwiki, that also
03141                                         # resets the default namespace
03142                                         if ( $dbkey !== '' && $dbkey[0] == ':' ) {
03143                                                 $this->mNamespace = NS_MAIN;
03144                                                 $dbkey = substr( $dbkey, 1 );
03145                                         }
03146                                 }
03147                                 # If there's no recognized interwiki or namespace,
03148                                 # then let the colon expression be part of the title.
03149                         }
03150                         break;
03151                 } while ( true );
03152 
03153                 # We already know that some pages won't be in the database!
03154                 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
03155                         $this->mArticleID = 0;
03156                 }
03157                 $fragment = strstr( $dbkey, '#' );
03158                 if ( false !== $fragment ) {
03159                         $this->setFragment( $fragment );
03160                         $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
03161                         # remove whitespace again: prevents "Foo_bar_#"
03162                         # becoming "Foo_bar_"
03163                         $dbkey = preg_replace( '/_*$/', '', $dbkey );
03164                 }
03165 
03166                 # Reject illegal characters.
03167                 $rxTc = self::getTitleInvalidRegex();
03168                 if ( preg_match( $rxTc, $dbkey ) ) {
03169                         return false;
03170                 }
03171 
03172                 # Pages with "/./" or "/../" appearing in the URLs will often be un-
03173                 # reachable due to the way web browsers deal with 'relative' URLs.
03174                 # Also, they conflict with subpage syntax.  Forbid them explicitly.
03175                 if ( strpos( $dbkey, '.' ) !== false &&
03176                          ( $dbkey === '.' || $dbkey === '..' ||
03177                            strpos( $dbkey, './' ) === 0  ||
03178                            strpos( $dbkey, '../' ) === 0 ||
03179                            strpos( $dbkey, '/./' ) !== false ||
03180                            strpos( $dbkey, '/../' ) !== false  ||
03181                            substr( $dbkey, -2 ) == '/.' ||
03182                            substr( $dbkey, -3 ) == '/..' ) )
03183                 {
03184                         return false;
03185                 }
03186 
03187                 # Magic tilde sequences? Nu-uh!
03188                 if ( strpos( $dbkey, '~~~' ) !== false ) {
03189                         return false;
03190                 }
03191 
03192                 # Limit the size of titles to 255 bytes. This is typically the size of the
03193                 # underlying database field. We make an exception for special pages, which
03194                 # don't need to be stored in the database, and may edge over 255 bytes due
03195                 # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
03196                 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
03197                   strlen( $dbkey ) > 512 )
03198                 {
03199                         return false;
03200                 }
03201 
03202                 # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
03203                 # and [[Foo]] point to the same place.  Don't force it for interwikis, since the
03204                 # other site might be case-sensitive.
03205                 $this->mUserCaseDBKey = $dbkey;
03206                 if ( $this->mInterwiki == '' ) {
03207                         $dbkey = self::capitalize( $dbkey, $this->mNamespace );
03208                 }
03209 
03210                 # Can't make a link to a namespace alone... "empty" local links can only be
03211                 # self-links with a fragment identifier.
03212                 if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
03213                         return false;
03214                 }
03215 
03216                 // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
03217                 // IP names are not allowed for accounts, and can only be referring to
03218                 // edits from the IP. Given '::' abbreviations and caps/lowercaps,
03219                 // there are numerous ways to present the same IP. Having sp:contribs scan
03220                 // them all is silly and having some show the edits and others not is
03221                 // inconsistent. Same for talk/userpages. Keep them normalized instead.
03222                 $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
03223                         ? IP::sanitizeIP( $dbkey )
03224                         : $dbkey;
03225 
03226                 // Any remaining initial :s are illegal.
03227                 if ( $dbkey !== '' && ':' == $dbkey[0] ) {
03228                         return false;
03229                 }
03230 
03231                 # Fill fields
03232                 $this->mDbkeyform = $dbkey;
03233                 $this->mUrlform = wfUrlencode( $dbkey );
03234 
03235                 $this->mTextform = str_replace( '_', ' ', $dbkey );
03236 
03237                 return true;
03238         }
03239 
03252         public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03253                 if ( count( $options ) > 0 ) {
03254                         $db = wfGetDB( DB_MASTER );
03255                 } else {
03256                         $db = wfGetDB( DB_SLAVE );
03257                 }
03258 
03259                 $res = $db->select(
03260                         array( 'page', $table ),
03261                         self::getSelectFields(),
03262                         array(
03263                                 "{$prefix}_from=page_id",
03264                                 "{$prefix}_namespace" => $this->getNamespace(),
03265                                 "{$prefix}_title"     => $this->getDBkey() ),
03266                         __METHOD__,
03267                         $options
03268                 );
03269 
03270                 $retVal = array();
03271                 if ( $res->numRows() ) {
03272                         $linkCache = LinkCache::singleton();
03273                         foreach ( $res as $row ) {
03274                                 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
03275                                 if ( $titleObj ) {
03276                                         $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03277                                         $retVal[] = $titleObj;
03278                                 }
03279                         }
03280                 }
03281                 return $retVal;
03282         }
03283 
03294         public function getTemplateLinksTo( $options = array() ) {
03295                 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
03296         }
03297 
03310         public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03311                 global $wgContentHandlerUseDB;
03312 
03313                 $id = $this->getArticleID();
03314 
03315                 # If the page doesn't exist; there can't be any link from this page
03316                 if ( !$id ) {
03317                         return array();
03318                 }
03319 
03320                 if ( count( $options ) > 0 ) {
03321                         $db = wfGetDB( DB_MASTER );
03322                 } else {
03323                         $db = wfGetDB( DB_SLAVE );
03324                 }
03325 
03326                 $namespaceFiled = "{$prefix}_namespace";
03327                 $titleField = "{$prefix}_title";
03328 
03329                 $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
03330                 if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
03331 
03332                 $res = $db->select(
03333                         array( $table, 'page' ),
03334                         $fields,
03335                         array( "{$prefix}_from" => $id ),
03336                         __METHOD__,
03337                         $options,
03338                         array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
03339                 );
03340 
03341                 $retVal = array();
03342                 if ( $res->numRows() ) {
03343                         $linkCache = LinkCache::singleton();
03344                         foreach ( $res as $row ) {
03345                                 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
03346                                 if ( $titleObj ) {
03347                                         if ( $row->page_id ) {
03348                                                 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03349                                         } else {
03350                                                 $linkCache->addBadLinkObj( $titleObj );
03351                                         }
03352                                         $retVal[] = $titleObj;
03353                                 }
03354                         }
03355                 }
03356                 return $retVal;
03357         }
03358 
03369         public function getTemplateLinksFrom( $options = array() ) {
03370                 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
03371         }
03372 
03379         public function getBrokenLinksFrom() {
03380                 if ( $this->getArticleID() == 0 ) {
03381                         # All links from article ID 0 are false positives
03382                         return array();
03383                 }
03384 
03385                 $dbr = wfGetDB( DB_SLAVE );
03386                 $res = $dbr->select(
03387                         array( 'page', 'pagelinks' ),
03388                         array( 'pl_namespace', 'pl_title' ),
03389                         array(
03390                                 'pl_from' => $this->getArticleID(),
03391                                 'page_namespace IS NULL'
03392                         ),
03393                         __METHOD__, array(),
03394                         array(
03395                                 'page' => array(
03396                                         'LEFT JOIN',
03397                                         array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
03398                                 )
03399                         )
03400                 );
03401 
03402                 $retVal = array();
03403                 foreach ( $res as $row ) {
03404                         $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
03405                 }
03406                 return $retVal;
03407         }
03408 
03409 
03416         public function getSquidURLs() {
03417                 $urls = array(
03418                         $this->getInternalURL(),
03419                         $this->getInternalURL( 'action=history' )
03420                 );
03421 
03422                 $pageLang = $this->getPageLanguage();
03423                 if ( $pageLang->hasVariants() ) {
03424                         $variants = $pageLang->getVariants();
03425                         foreach ( $variants as $vCode ) {
03426                                 $urls[] = $this->getInternalURL( '', $vCode );
03427                         }
03428                 }
03429 
03430                 return $urls;
03431         }
03432 
03436         public function purgeSquid() {
03437                 global $wgUseSquid;
03438                 if ( $wgUseSquid ) {
03439                         $urls = $this->getSquidURLs();
03440                         $u = new SquidUpdate( $urls );
03441                         $u->doUpdate();
03442                 }
03443         }
03444 
03451         public function moveNoAuth( &$nt ) {
03452                 return $this->moveTo( $nt, false );
03453         }
03454 
03465         public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
03466                 global $wgUser, $wgContentHandlerUseDB;
03467 
03468                 $errors = array();
03469                 if ( !$nt ) {
03470                         // Normally we'd add this to $errors, but we'll get
03471                         // lots of syntax errors if $nt is not an object
03472                         return array( array( 'badtitletext' ) );
03473                 }
03474                 if ( $this->equals( $nt ) ) {
03475                         $errors[] = array( 'selfmove' );
03476                 }
03477                 if ( !$this->isMovable() ) {
03478                         $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
03479                 }
03480                 if ( $nt->getInterwiki() != '' ) {
03481                         $errors[] = array( 'immobile-target-namespace-iw' );
03482                 }
03483                 if ( !$nt->isMovable() ) {
03484                         $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
03485                 }
03486 
03487                 $oldid = $this->getArticleID();
03488                 $newid = $nt->getArticleID();
03489 
03490                 if ( strlen( $nt->getDBkey() ) < 1 ) {
03491                         $errors[] = array( 'articleexists' );
03492                 }
03493                 if ( ( $this->getDBkey() == '' ) ||
03494                          ( !$oldid ) ||
03495                          ( $nt->getDBkey() == '' ) ) {
03496                         $errors[] = array( 'badarticleerror' );
03497                 }
03498 
03499                 // Content model checks
03500                 if ( !$wgContentHandlerUseDB &&
03501                                 $this->getContentModel() !== $nt->getContentModel() ) {
03502                         // can't move a page if that would change the page's content model
03503                         $errors[] = array( 'bad-target-model',
03504                                                         ContentHandler::getLocalizedName( $this->getContentModel() ),
03505                                                         ContentHandler::getLocalizedName( $nt->getContentModel() ) );
03506                 }
03507 
03508                 // Image-specific checks
03509                 if ( $this->getNamespace() == NS_FILE ) {
03510                         $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
03511                 }
03512 
03513                 if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
03514                         $errors[] = array( 'nonfile-cannot-move-to-file' );
03515                 }
03516 
03517                 if ( $auth ) {
03518                         $errors = wfMergeErrorArrays( $errors,
03519                                 $this->getUserPermissionsErrors( 'move', $wgUser ),
03520                                 $this->getUserPermissionsErrors( 'edit', $wgUser ),
03521                                 $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
03522                                 $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
03523                 }
03524 
03525                 $match = EditPage::matchSummarySpamRegex( $reason );
03526                 if ( $match !== false ) {
03527                         // This is kind of lame, won't display nice
03528                         $errors[] = array( 'spamprotectiontext' );
03529                 }
03530 
03531                 $err = null;
03532                 if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
03533                         $errors[] = array( 'hookaborted', $err );
03534                 }
03535 
03536                 # The move is allowed only if (1) the target doesn't exist, or
03537                 # (2) the target is a redirect to the source, and has no history
03538                 # (so we can undo bad moves right after they're done).
03539 
03540                 if ( 0 != $newid ) { # Target exists; check for validity
03541                         if ( !$this->isValidMoveTarget( $nt ) ) {
03542                                 $errors[] = array( 'articleexists' );
03543                         }
03544                 } else {
03545                         $tp = $nt->getTitleProtection();
03546                         $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
03547                         if ( $tp and !$wgUser->isAllowed( $right ) ) {
03548                                 $errors[] = array( 'cantmove-titleprotected' );
03549                         }
03550                 }
03551                 if ( empty( $errors ) ) {
03552                         return true;
03553                 }
03554                 return $errors;
03555         }
03556 
03562         protected function validateFileMoveOperation( $nt ) {
03563                 global $wgUser;
03564 
03565                 $errors = array();
03566 
03567                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
03568 
03569                 $file = wfLocalFile( $this );
03570                 if ( $file->exists() ) {
03571                         if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
03572                                 $errors[] = array( 'imageinvalidfilename' );
03573                         }
03574                         if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
03575                                 $errors[] = array( 'imagetypemismatch' );
03576                         }
03577                 }
03578 
03579                 if ( $nt->getNamespace() != NS_FILE ) {
03580                         $errors[] = array( 'imagenocrossnamespace' );
03581                         // From here we want to do checks on a file object, so if we can't
03582                         // create one, we must return.
03583                         return $errors;
03584                 }
03585 
03586                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
03587 
03588                 $destFile = wfLocalFile( $nt );
03589                 if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
03590                         $errors[] = array( 'file-exists-sharedrepo' );
03591                 }
03592 
03593                 return $errors;
03594         }
03595 
03607         public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
03608                 global $wgUser;
03609                 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
03610                 if ( is_array( $err ) ) {
03611                         // Auto-block user's IP if the account was "hard" blocked
03612                         $wgUser->spreadAnyEditBlock();
03613                         return $err;
03614                 }
03615                 // Check suppressredirect permission
03616                 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
03617                         $createRedirect = true;
03618                 }
03619 
03620                 // If it is a file, move it first.
03621                 // It is done before all other moving stuff is done because it's hard to revert.
03622                 $dbw = wfGetDB( DB_MASTER );
03623                 if ( $this->getNamespace() == NS_FILE ) {
03624                         $file = wfLocalFile( $this );
03625                         if ( $file->exists() ) {
03626                                 $status = $file->move( $nt );
03627                                 if ( !$status->isOk() ) {
03628                                         return $status->getErrorsArray();
03629                                 }
03630                         }
03631                         // Clear RepoGroup process cache
03632                         RepoGroup::singleton()->clearCache( $this );
03633                         RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
03634                 }
03635 
03636                 $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
03637                 $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
03638                 $protected = $this->isProtected();
03639 
03640                 // Do the actual move
03641                 $this->moveToInternal( $nt, $reason, $createRedirect );
03642 
03643                 // Refresh the sortkey for this row.  Be careful to avoid resetting
03644                 // cl_timestamp, which may disturb time-based lists on some sites.
03645                 $prefixes = $dbw->select(
03646                         'categorylinks',
03647                         array( 'cl_sortkey_prefix', 'cl_to' ),
03648                         array( 'cl_from' => $pageid ),
03649                         __METHOD__
03650                 );
03651                 foreach ( $prefixes as $prefixRow ) {
03652                         $prefix = $prefixRow->cl_sortkey_prefix;
03653                         $catTo = $prefixRow->cl_to;
03654                         $dbw->update( 'categorylinks',
03655                                 array(
03656                                         'cl_sortkey' => Collation::singleton()->getSortKey(
03657                                                 $nt->getCategorySortkey( $prefix ) ),
03658                                         'cl_timestamp=cl_timestamp' ),
03659                                 array(
03660                                         'cl_from' => $pageid,
03661                                         'cl_to' => $catTo ),
03662                                 __METHOD__
03663                         );
03664                 }
03665 
03666                 $redirid = $this->getArticleID();
03667 
03668                 if ( $protected ) {
03669                         # Protect the redirect title as the title used to be...
03670                         $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
03671                                 array(
03672                                         'pr_page'    => $redirid,
03673                                         'pr_type'    => 'pr_type',
03674                                         'pr_level'   => 'pr_level',
03675                                         'pr_cascade' => 'pr_cascade',
03676                                         'pr_user'    => 'pr_user',
03677                                         'pr_expiry'  => 'pr_expiry'
03678                                 ),
03679                                 array( 'pr_page' => $pageid ),
03680                                 __METHOD__,
03681                                 array( 'IGNORE' )
03682                         );
03683                         # Update the protection log
03684                         $log = new LogPage( 'protect' );
03685                         $comment = wfMessage(
03686                                 'prot_1movedto2',
03687                                 $this->getPrefixedText(),
03688                                 $nt->getPrefixedText()
03689                         )->inContentLanguage()->text();
03690                         if ( $reason ) {
03691                                 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03692                         }
03693                         // @todo FIXME: $params?
03694                         $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
03695                 }
03696 
03697                 # Update watchlists
03698                 $oldnamespace = $this->getNamespace() & ~1;
03699                 $newnamespace = $nt->getNamespace() & ~1;
03700                 $oldtitle = $this->getDBkey();
03701                 $newtitle = $nt->getDBkey();
03702 
03703                 if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
03704                         WatchedItem::duplicateEntries( $this, $nt );
03705                 }
03706 
03707                 $dbw->commit( __METHOD__ );
03708 
03709                 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
03710                 return true;
03711         }
03712 
03723         private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
03724                 global $wgUser, $wgContLang;
03725 
03726                 if ( $nt->exists() ) {
03727                         $moveOverRedirect = true;
03728                         $logType = 'move_redir';
03729                 } else {
03730                         $moveOverRedirect = false;
03731                         $logType = 'move';
03732                 }
03733 
03734                 if ( $createRedirect ) {
03735                         $contentHandler = ContentHandler::getForTitle( $this );
03736                         $redirectContent = $contentHandler->makeRedirectContent( $nt );
03737 
03738                         // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
03739                 } else {
03740                         $redirectContent = null;
03741                 }
03742 
03743                 $logEntry = new ManualLogEntry( 'move', $logType );
03744                 $logEntry->setPerformer( $wgUser );
03745                 $logEntry->setTarget( $this );
03746                 $logEntry->setComment( $reason );
03747                 $logEntry->setParameters( array(
03748                         '4::target' => $nt->getPrefixedText(),
03749                         '5::noredir' => $redirectContent ? '0': '1',
03750                 ) );
03751 
03752                 $formatter = LogFormatter::newFromEntry( $logEntry );
03753                 $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
03754                 $comment = $formatter->getPlainActionText();
03755                 if ( $reason ) {
03756                         $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03757                 }
03758                 # Truncate for whole multibyte characters.
03759                 $comment = $wgContLang->truncate( $comment, 255 );
03760 
03761                 $oldid = $this->getArticleID();
03762 
03763                 $dbw = wfGetDB( DB_MASTER );
03764 
03765                 $newpage = WikiPage::factory( $nt );
03766 
03767                 if ( $moveOverRedirect ) {
03768                         $newid = $nt->getArticleID();
03769 
03770                         # Delete the old redirect. We don't save it to history since
03771                         # by definition if we've got here it's rather uninteresting.
03772                         # We have to remove it so that the next step doesn't trigger
03773                         # a conflict on the unique namespace+title index...
03774                         $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
03775 
03776                         $newpage->doDeleteUpdates( $newid );
03777                 }
03778 
03779                 # Save a null revision in the page's history notifying of the move
03780                 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
03781                 if ( !is_object( $nullRevision ) ) {
03782                         throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
03783                 }
03784 
03785                 $nullRevision->insertOn( $dbw );
03786 
03787                 # Change the name of the target page:
03788                 $dbw->update( 'page',
03789                         /* SET */ array(
03790                                 'page_namespace' => $nt->getNamespace(),
03791                                 'page_title'     => $nt->getDBkey(),
03792                         ),
03793                         /* WHERE */ array( 'page_id' => $oldid ),
03794                         __METHOD__
03795                 );
03796 
03797                 $this->resetArticleID( 0 );
03798                 $nt->resetArticleID( $oldid );
03799 
03800                 $newpage->updateRevisionOn( $dbw, $nullRevision );
03801 
03802                 wfRunHooks( 'NewRevisionFromEditComplete',
03803                         array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
03804 
03805                 $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
03806 
03807                 if ( !$moveOverRedirect ) {
03808                         WikiPage::onArticleCreate( $nt );
03809                 }
03810 
03811                 # Recreate the redirect, this time in the other direction.
03812                 if ( !$redirectContent ) {
03813                         WikiPage::onArticleDelete( $this );
03814                 } else {
03815                         $redirectArticle = WikiPage::factory( $this );
03816                         $newid = $redirectArticle->insertOn( $dbw );
03817                         if ( $newid ) { // sanity
03818                                 $redirectRevision = new Revision( array(
03819                                         'title'   => $this, // for determining the default content model
03820                                         'page'    => $newid,
03821                                         'comment' => $comment,
03822                                         'content'    => $redirectContent ) );
03823                                 $redirectRevision->insertOn( $dbw );
03824                                 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
03825 
03826                                 wfRunHooks( 'NewRevisionFromEditComplete',
03827                                         array( $redirectArticle, $redirectRevision, false, $wgUser ) );
03828 
03829                                 $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
03830                         }
03831                 }
03832 
03833                 # Log the move
03834                 $logid = $logEntry->insert();
03835                 $logEntry->publish( $logid );
03836         }
03837 
03850         public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03851                 global $wgMaximumMovedPages;
03852                 // Check permissions
03853                 if ( !$this->userCan( 'move-subpages' ) ) {
03854                         return array( 'cant-move-subpages' );
03855                 }
03856                 // Do the source and target namespaces support subpages?
03857                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03858                         return array( 'namespace-nosubpages',
03859                                 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03860                 }
03861                 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
03862                         return array( 'namespace-nosubpages',
03863                                 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03864                 }
03865 
03866                 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
03867                 $retval = array();
03868                 $count = 0;
03869                 foreach ( $subpages as $oldSubpage ) {
03870                         $count++;
03871                         if ( $count > $wgMaximumMovedPages ) {
03872                                 $retval[$oldSubpage->getPrefixedTitle()] =
03873                                                 array( 'movepage-max-pages',
03874                                                         $wgMaximumMovedPages );
03875                                 break;
03876                         }
03877 
03878                         // We don't know whether this function was called before
03879                         // or after moving the root page, so check both
03880                         // $this and $nt
03881                         if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
03882                                         $oldSubpage->getArticleID() == $nt->getArticleID() )
03883                         {
03884                                 // When moving a page to a subpage of itself,
03885                                 // don't move it twice
03886                                 continue;
03887                         }
03888                         $newPageName = preg_replace(
03889                                         '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
03890                                         StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03891                                         $oldSubpage->getDBkey() );
03892                         if ( $oldSubpage->isTalkPage() ) {
03893                                 $newNs = $nt->getTalkPage()->getNamespace();
03894                         } else {
03895                                 $newNs = $nt->getSubjectPage()->getNamespace();
03896                         }
03897                         # Bug 14385: we need makeTitleSafe because the new page names may
03898                         # be longer than 255 characters.
03899                         $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03900 
03901                         $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03902                         if ( $success === true ) {
03903                                 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03904                         } else {
03905                                 $retval[$oldSubpage->getPrefixedText()] = $success;
03906                         }
03907                 }
03908                 return $retval;
03909         }
03910 
03917         public function isSingleRevRedirect() {
03918                 global $wgContentHandlerUseDB;
03919 
03920                 $dbw = wfGetDB( DB_MASTER );
03921 
03922                 # Is it a redirect?
03923                 $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
03924                 if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
03925 
03926                 $row = $dbw->selectRow( 'page',
03927                         $fields,
03928                         $this->pageCond(),
03929                         __METHOD__,
03930                         array( 'FOR UPDATE' )
03931                 );
03932                 # Cache some fields we may want
03933                 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
03934                 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03935                 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
03936                 $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
03937                 if ( !$this->mRedirect ) {
03938                         return false;
03939                 }
03940                 # Does the article have a history?
03941                 $row = $dbw->selectField( array( 'page', 'revision' ),
03942                         'rev_id',
03943                         array( 'page_namespace' => $this->getNamespace(),
03944                                 'page_title' => $this->getDBkey(),
03945                                 'page_id=rev_page',
03946                                 'page_latest != rev_id'
03947                         ),
03948                         __METHOD__,
03949                         array( 'FOR UPDATE' )
03950                 );
03951                 # Return true if there was no history
03952                 return ( $row === false );
03953         }
03954 
03962         public function isValidMoveTarget( $nt ) {
03963                 # Is it an existing file?
03964                 if ( $nt->getNamespace() == NS_FILE ) {
03965                         $file = wfLocalFile( $nt );
03966                         if ( $file->exists() ) {
03967                                 wfDebug( __METHOD__ . ": file exists\n" );
03968                                 return false;
03969                         }
03970                 }
03971                 # Is it a redirect with no history?
03972                 if ( !$nt->isSingleRevRedirect() ) {
03973                         wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03974                         return false;
03975                 }
03976                 # Get the article text
03977                 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
03978                 if( !is_object( $rev ) ){
03979                         return false;
03980                 }
03981                 $content = $rev->getContent();
03982                 # Does the redirect point to the source?
03983                 # Or is it a broken self-redirect, usually caused by namespace collisions?
03984                 $redirTitle = $content ? $content->getRedirectTarget() : null;
03985 
03986                 if ( $redirTitle ) {
03987                         if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03988                                 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
03989                                 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03990                                 return false;
03991                         } else {
03992                                 return true;
03993                         }
03994                 } else {
03995                         # Fail safe (not a redirect after all. strange.)
03996                         wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
03997                                                 " is a redirect, but it doesn't contain a valid redirect.\n" );
03998                         return false;
03999                 }
04000         }
04001 
04009         public function getParentCategories() {
04010                 global $wgContLang;
04011 
04012                 $data = array();
04013 
04014                 $titleKey = $this->getArticleID();
04015 
04016                 if ( $titleKey === 0 ) {
04017                         return $data;
04018                 }
04019 
04020                 $dbr = wfGetDB( DB_SLAVE );
04021 
04022                 $res = $dbr->select( 'categorylinks', '*',
04023                         array(
04024                                 'cl_from' => $titleKey,
04025                         ),
04026                         __METHOD__,
04027                         array()
04028                 );
04029 
04030                 if ( $dbr->numRows( $res ) > 0 ) {
04031                         foreach ( $res as $row ) {
04032                                 // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
04033                                 $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
04034                         }
04035                 }
04036                 return $data;
04037         }
04038 
04045         public function getParentCategoryTree( $children = array() ) {
04046                 $stack = array();
04047                 $parents = $this->getParentCategories();
04048 
04049                 if ( $parents ) {
04050                         foreach ( $parents as $parent => $current ) {
04051                                 if ( array_key_exists( $parent, $children ) ) {
04052                                         # Circular reference
04053                                         $stack[$parent] = array();
04054                                 } else {
04055                                         $nt = Title::newFromText( $parent );
04056                                         if ( $nt ) {
04057                                                 $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
04058                                         }
04059                                 }
04060                         }
04061                 }
04062 
04063                 return $stack;
04064         }
04065 
04072         public function pageCond() {
04073                 if ( $this->mArticleID > 0 ) {
04074                         // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
04075                         return array( 'page_id' => $this->mArticleID );
04076                 } else {
04077                         return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
04078                 }
04079         }
04080 
04088         public function getPreviousRevisionID( $revId, $flags = 0 ) {
04089                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04090                 $revId = $db->selectField( 'revision', 'rev_id',
04091                         array(
04092                                 'rev_page' => $this->getArticleID( $flags ),
04093                                 'rev_id < ' . intval( $revId )
04094                         ),
04095                         __METHOD__,
04096                         array( 'ORDER BY' => 'rev_id DESC' )
04097                 );
04098 
04099                 if ( $revId === false ) {
04100                         return false;
04101                 } else {
04102                         return intval( $revId );
04103                 }
04104         }
04105 
04113         public function getNextRevisionID( $revId, $flags = 0 ) {
04114                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04115                 $revId = $db->selectField( 'revision', 'rev_id',
04116                         array(
04117                                 'rev_page' => $this->getArticleID( $flags ),
04118                                 'rev_id > ' . intval( $revId )
04119                         ),
04120                         __METHOD__,
04121                         array( 'ORDER BY' => 'rev_id' )
04122                 );
04123 
04124                 if ( $revId === false ) {
04125                         return false;
04126                 } else {
04127                         return intval( $revId );
04128                 }
04129         }
04130 
04137         public function getFirstRevision( $flags = 0 ) {
04138                 $pageId = $this->getArticleID( $flags );
04139                 if ( $pageId ) {
04140                         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04141                         $row = $db->selectRow( 'revision', Revision::selectFields(),
04142                                 array( 'rev_page' => $pageId ),
04143                                 __METHOD__,
04144                                 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
04145                         );
04146                         if ( $row ) {
04147                                 return new Revision( $row );
04148                         }
04149                 }
04150                 return null;
04151         }
04152 
04159         public function getEarliestRevTime( $flags = 0 ) {
04160                 $rev = $this->getFirstRevision( $flags );
04161                 return $rev ? $rev->getTimestamp() : null;
04162         }
04163 
04169         public function isNewPage() {
04170                 $dbr = wfGetDB( DB_SLAVE );
04171                 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
04172         }
04173 
04179         public function isBigDeletion() {
04180                 global $wgDeleteRevisionsLimit;
04181 
04182                 if ( !$wgDeleteRevisionsLimit ) {
04183                         return false;
04184                 }
04185 
04186                 $revCount = $this->estimateRevisionCount();
04187                 return $revCount > $wgDeleteRevisionsLimit;
04188         }
04189 
04195         public function estimateRevisionCount() {
04196                 if ( !$this->exists() ) {
04197                         return 0;
04198                 }
04199 
04200                 if ( $this->mEstimateRevisions === null ) {
04201                         $dbr = wfGetDB( DB_SLAVE );
04202                         $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
04203                                 array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
04204                 }
04205 
04206                 return $this->mEstimateRevisions;
04207         }
04208 
04217         public function countRevisionsBetween( $old, $new ) {
04218                 if ( !( $old instanceof Revision ) ) {
04219                         $old = Revision::newFromTitle( $this, (int)$old );
04220                 }
04221                 if ( !( $new instanceof Revision ) ) {
04222                         $new = Revision::newFromTitle( $this, (int)$new );
04223                 }
04224                 if ( !$old || !$new ) {
04225                         return 0; // nothing to compare
04226                 }
04227                 $dbr = wfGetDB( DB_SLAVE );
04228                 return (int)$dbr->selectField( 'revision', 'count(*)',
04229                         array(
04230                                 'rev_page' => $this->getArticleID(),
04231                                 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04232                                 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04233                         ),
04234                         __METHOD__
04235                 );
04236         }
04237 
04252         public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
04253                 if ( !( $old instanceof Revision ) ) {
04254                         $old = Revision::newFromTitle( $this, (int)$old );
04255                 }
04256                 if ( !( $new instanceof Revision ) ) {
04257                         $new = Revision::newFromTitle( $this, (int)$new );
04258                 }
04259                 // XXX: what if Revision objects are passed in, but they don't refer to this title?
04260                 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
04261                 // in the sanity check below?
04262                 if ( !$old || !$new ) {
04263                         return 0; // nothing to compare
04264                 }
04265                 $old_cmp = '>';
04266                 $new_cmp = '<';
04267                 $options = (array) $options;
04268                 if ( in_array( 'include_old', $options ) ) {
04269                         $old_cmp = '>=';
04270                 }
04271                 if ( in_array( 'include_new', $options ) ) {
04272                         $new_cmp = '<=';
04273                 }
04274                 if ( in_array( 'include_both', $options ) ) {
04275                         $old_cmp = '>=';
04276                         $new_cmp = '<=';
04277                 }
04278                 // No DB query needed if $old and $new are the same or successive revisions:
04279                 if ( $old->getId() === $new->getId() ) {
04280                         return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04281                 } else if ( $old->getId() === $new->getParentId() ) {
04282                         if ( $old_cmp === '>' || $new_cmp === '<' ) {
04283                                 return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04284                         }
04285                         return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
04286                 }
04287                 $dbr = wfGetDB( DB_SLAVE );
04288                 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
04289                         array(
04290                                 'rev_page' => $this->getArticleID(),
04291                                 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04292                                 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04293                         ), __METHOD__,
04294                         array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
04295                 );
04296                 return (int)$dbr->numRows( $res );
04297         }
04298 
04305         public function equals( Title $title ) {
04306                 // Note: === is necessary for proper matching of number-like titles.
04307                 return $this->getInterwiki() === $title->getInterwiki()
04308                         && $this->getNamespace() == $title->getNamespace()
04309                         && $this->getDBkey() === $title->getDBkey();
04310         }
04311 
04318         public function isSubpageOf( Title $title ) {
04319                 return $this->getInterwiki() === $title->getInterwiki()
04320                         && $this->getNamespace() == $title->getNamespace()
04321                         && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
04322         }
04323 
04333         public function exists() {
04334                 return $this->getArticleID() != 0;
04335         }
04336 
04353         public function isAlwaysKnown() {
04354                 $isKnown = null;
04355 
04366                 wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
04367 
04368                 if ( !is_null( $isKnown ) ) {
04369                         return $isKnown;
04370                 }
04371 
04372                 if ( $this->mInterwiki != '' ) {
04373                         return true;  // any interwiki link might be viewable, for all we know
04374                 }
04375 
04376                 switch( $this->mNamespace ) {
04377                         case NS_MEDIA:
04378                         case NS_FILE:
04379                                 // file exists, possibly in a foreign repo
04380                                 return (bool)wfFindFile( $this );
04381                         case NS_SPECIAL:
04382                                 // valid special page
04383                                 return SpecialPageFactory::exists( $this->getDBkey() );
04384                         case NS_MAIN:
04385                                 // selflink, possibly with fragment
04386                                 return $this->mDbkeyform == '';
04387                         case NS_MEDIAWIKI:
04388                                 // known system message
04389                                 return $this->hasSourceText() !== false;
04390                         default:
04391                                 return false;
04392                 }
04393         }
04394 
04406         public function isKnown() {
04407                 return $this->isAlwaysKnown() || $this->exists();
04408         }
04409 
04415         public function hasSourceText() {
04416                 if ( $this->exists() ) {
04417                         return true;
04418                 }
04419 
04420                 if ( $this->mNamespace == NS_MEDIAWIKI ) {
04421                         // If the page doesn't exist but is a known system message, default
04422                         // message content will be displayed, same for language subpages-
04423                         // Use always content language to avoid loading hundreds of languages
04424                         // to get the link color.
04425                         global $wgContLang;
04426                         list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04427                         $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
04428                         return $message->exists();
04429                 }
04430 
04431                 return false;
04432         }
04433 
04439         public function getDefaultMessageText() {
04440                 global $wgContLang;
04441 
04442                 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
04443                         return false;
04444                 }
04445 
04446                 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04447                 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
04448 
04449                 if ( $message->exists() ) {
04450                         return $message->plain();
04451                 } else {
04452                         return false;
04453                 }
04454         }
04455 
04461         public function invalidateCache() {
04462                 if ( wfReadOnly() ) {
04463                         return false;
04464                 }
04465                 $dbw = wfGetDB( DB_MASTER );
04466                 $success = $dbw->update(
04467                         'page',
04468                         array( 'page_touched' => $dbw->timestamp() ),
04469                         $this->pageCond(),
04470                         __METHOD__
04471                 );
04472                 HTMLFileCache::clearFileCache( $this );
04473                 return $success;
04474         }
04475 
04481         public function touchLinks() {
04482                 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
04483                 $u->doUpdate();
04484 
04485                 if ( $this->getNamespace() == NS_CATEGORY ) {
04486                         $u = new HTMLCacheUpdate( $this, 'categorylinks' );
04487                         $u->doUpdate();
04488                 }
04489         }
04490 
04497         public function getTouched( $db = null ) {
04498                 $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
04499                 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
04500                 return $touched;
04501         }
04502 
04509         public function getNotificationTimestamp( $user = null ) {
04510                 global $wgUser, $wgShowUpdatedMarker;
04511                 // Assume current user if none given
04512                 if ( !$user ) {
04513                         $user = $wgUser;
04514                 }
04515                 // Check cache first
04516                 $uid = $user->getId();
04517                 // avoid isset here, as it'll return false for null entries
04518                 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
04519                         return $this->mNotificationTimestamp[$uid];
04520                 }
04521                 if ( !$uid || !$wgShowUpdatedMarker ) {
04522                         return $this->mNotificationTimestamp[$uid] = false;
04523                 }
04524                 // Don't cache too much!
04525                 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
04526                         $this->mNotificationTimestamp = array();
04527                 }
04528                 $dbr = wfGetDB( DB_SLAVE );
04529                 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
04530                         'wl_notificationtimestamp',
04531                         array(
04532                                 'wl_user' => $user->getId(),
04533                                 'wl_namespace' => $this->getNamespace(),
04534                                 'wl_title' => $this->getDBkey(),
04535                         ),
04536                         __METHOD__
04537                 );
04538                 return $this->mNotificationTimestamp[$uid];
04539         }
04540 
04547         public function getNamespaceKey( $prepend = 'nstab-' ) {
04548                 global $wgContLang;
04549                 // Gets the subject namespace if this title
04550                 $namespace = MWNamespace::getSubject( $this->getNamespace() );
04551                 // Checks if cononical namespace name exists for namespace
04552                 if ( MWNamespace::exists( $this->getNamespace() ) ) {
04553                         // Uses canonical namespace name
04554                         $namespaceKey = MWNamespace::getCanonicalName( $namespace );
04555                 } else {
04556                         // Uses text of namespace
04557                         $namespaceKey = $this->getSubjectNsText();
04558                 }
04559                 // Makes namespace key lowercase
04560                 $namespaceKey = $wgContLang->lc( $namespaceKey );
04561                 // Uses main
04562                 if ( $namespaceKey == '' ) {
04563                         $namespaceKey = 'main';
04564                 }
04565                 // Changes file to image for backwards compatibility
04566                 if ( $namespaceKey == 'file' ) {
04567                         $namespaceKey = 'image';
04568                 }
04569                 return $prepend . $namespaceKey;
04570         }
04571 
04578         public function getRedirectsHere( $ns = null ) {
04579                 $redirs = array();
04580 
04581                 $dbr = wfGetDB( DB_SLAVE );
04582                 $where = array(
04583                         'rd_namespace' => $this->getNamespace(),
04584                         'rd_title' => $this->getDBkey(),
04585                         'rd_from = page_id'
04586                 );
04587                 if ( $this->isExternal() ) {
04588                         $where['rd_interwiki'] = $this->getInterwiki();
04589                 } else {
04590                         $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
04591                 }
04592                 if ( !is_null( $ns ) ) {
04593                         $where['page_namespace'] = $ns;
04594                 }
04595 
04596                 $res = $dbr->select(
04597                         array( 'redirect', 'page' ),
04598                         array( 'page_namespace', 'page_title' ),
04599                         $where,
04600                         __METHOD__
04601                 );
04602 
04603                 foreach ( $res as $row ) {
04604                         $redirs[] = self::newFromRow( $row );
04605                 }
04606                 return $redirs;
04607         }
04608 
04614         public function isValidRedirectTarget() {
04615                 global $wgInvalidRedirectTargets;
04616 
04617                 // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
04618                 if ( $this->isSpecial( 'Userlogout' ) ) {
04619                         return false;
04620                 }
04621 
04622                 foreach ( $wgInvalidRedirectTargets as $target ) {
04623                         if ( $this->isSpecial( $target ) ) {
04624                                 return false;
04625                         }
04626                 }
04627 
04628                 return true;
04629         }
04630 
04636         public function getBacklinkCache() {
04637                 return BacklinkCache::get( $this );
04638         }
04639 
04645         public function canUseNoindex() {
04646                 global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
04647 
04648                 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
04649                         ? $wgContentNamespaces
04650                         : $wgExemptFromUserRobotsControl;
04651 
04652                 return !in_array( $this->mNamespace, $bannedNamespaces );
04653 
04654         }
04655 
04666         public function getCategorySortkey( $prefix = '' ) {
04667                 $unprefixed = $this->getText();
04668 
04669                 // Anything that uses this hook should only depend
04670                 // on the Title object passed in, and should probably
04671                 // tell the users to run updateCollations.php --force
04672                 // in order to re-sort existing category relations.
04673                 wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
04674                 if ( $prefix !== '' ) {
04675                         # Separate with a line feed, so the unprefixed part is only used as
04676                         # a tiebreaker when two pages have the exact same prefix.
04677                         # In UCA, tab is the only character that can sort above LF
04678                         # so we strip both of them from the original prefix.
04679                         $prefix = strtr( $prefix, "\n\t", '  ' );
04680                         return "$prefix\n$unprefixed";
04681                 }
04682                 return $unprefixed;
04683         }
04684 
04693         public function getPageLanguage() {
04694                 global $wgLang;
04695                 if ( $this->isSpecialPage() ) {
04696                         // special pages are in the user language
04697                         return $wgLang;
04698                 }
04699 
04700                 //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
04701                 //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
04702                 $contentHandler = ContentHandler::getForTitle( $this );
04703                 $pageLang = $contentHandler->getPageLanguage( $this );
04704 
04705                 return wfGetLangObj( $pageLang );
04706         }
04707 
04716         public function getPageViewLanguage() {
04717                 global $wgLang;
04718 
04719                 if ( $this->isSpecialPage() ) {
04720                         // If the user chooses a variant, the content is actually
04721                         // in a language whose code is the variant code.
04722                         $variant = $wgLang->getPreferredVariant();
04723                         if ( $wgLang->getCode() !== $variant ) {
04724                                 return Language::factory( $variant );
04725                         }
04726 
04727                         return $wgLang;
04728                 }
04729 
04730                 //NOTE: can't be cached persistently, depends on user settings
04731                 //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
04732                 $contentHandler = ContentHandler::getForTitle( $this );
04733                 $pageLang = $contentHandler->getPageViewLanguage( $this );
04734                 return $pageLang;
04735         }
04736 }