MediaWiki  REL1_21
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 
00094         /*protected*/ function __construct() { }
00095 
00104         public static function newFromDBkey( $key ) {
00105                 $t = new Title();
00106                 $t->mDbkeyform = $key;
00107                 if ( $t->secureAndSplit() ) {
00108                         return $t;
00109                 } else {
00110                         return null;
00111                 }
00112         }
00113 
00127         public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00128                 if ( is_object( $text ) ) {
00129                         throw new MWException( 'Title::newFromText given an object' );
00130                 }
00131 
00140                 if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00141                         return Title::$titleCache[$text];
00142                 }
00143 
00144                 # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
00145                 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
00146 
00147                 $t = new Title();
00148                 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00149                 $t->mDefaultNamespace = $defaultNamespace;
00150 
00151                 static $cachedcount = 0;
00152                 if ( $t->secureAndSplit() ) {
00153                         if ( $defaultNamespace == NS_MAIN ) {
00154                                 if ( $cachedcount >= self::CACHE_MAX ) {
00155                                         # Avoid memory leaks on mass operations...
00156                                         Title::$titleCache = array();
00157                                         $cachedcount = 0;
00158                                 }
00159                                 $cachedcount++;
00160                                 Title::$titleCache[$text] =& $t;
00161                         }
00162                         return $t;
00163                 } else {
00164                         $ret = null;
00165                         return $ret;
00166                 }
00167         }
00168 
00184         public static function newFromURL( $url ) {
00185                 $t = new Title();
00186 
00187                 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00188                 # but some URLs used it as a space replacement and they still come
00189                 # from some external search tools.
00190                 if ( strpos( self::legalChars(), '+' ) === false ) {
00191                         $url = str_replace( '+', ' ', $url );
00192                 }
00193 
00194                 $t->mDbkeyform = str_replace( ' ', '_', $url );
00195                 if ( $t->secureAndSplit() ) {
00196                         return $t;
00197                 } else {
00198                         return null;
00199                 }
00200         }
00201 
00208         protected static function getSelectFields() {
00209                 global $wgContentHandlerUseDB;
00210 
00211                 $fields = array(
00212                         'page_namespace', 'page_title', 'page_id',
00213                         'page_len', 'page_is_redirect', 'page_latest',
00214                 );
00215 
00216                 if ( $wgContentHandlerUseDB ) {
00217                         $fields[] = 'page_content_model';
00218                 }
00219 
00220                 return $fields;
00221         }
00222 
00230         public static function newFromID( $id, $flags = 0 ) {
00231                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00232                 $row = $db->selectRow(
00233                         'page',
00234                         self::getSelectFields(),
00235                         array( 'page_id' => $id ),
00236                         __METHOD__
00237                 );
00238                 if ( $row !== false ) {
00239                         $title = Title::newFromRow( $row );
00240                 } else {
00241                         $title = null;
00242                 }
00243                 return $title;
00244         }
00245 
00252         public static function newFromIDs( $ids ) {
00253                 if ( !count( $ids ) ) {
00254                         return array();
00255                 }
00256                 $dbr = wfGetDB( DB_SLAVE );
00257 
00258                 $res = $dbr->select(
00259                         'page',
00260                         self::getSelectFields(),
00261                         array( 'page_id' => $ids ),
00262                         __METHOD__
00263                 );
00264 
00265                 $titles = array();
00266                 foreach ( $res as $row ) {
00267                         $titles[] = Title::newFromRow( $row );
00268                 }
00269                 return $titles;
00270         }
00271 
00278         public static function newFromRow( $row ) {
00279                 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00280                 $t->loadFromRow( $row );
00281                 return $t;
00282         }
00283 
00290         public function loadFromRow( $row ) {
00291                 if ( $row ) { // page found
00292                         if ( isset( $row->page_id ) )
00293                                 $this->mArticleID = (int)$row->page_id;
00294                         if ( isset( $row->page_len ) )
00295                                 $this->mLength = (int)$row->page_len;
00296                         if ( isset( $row->page_is_redirect ) )
00297                                 $this->mRedirect = (bool)$row->page_is_redirect;
00298                         if ( isset( $row->page_latest ) )
00299                                 $this->mLatestID = (int)$row->page_latest;
00300                         if ( isset( $row->page_content_model ) )
00301                                 $this->mContentModel = strval( $row->page_content_model );
00302                         else
00303                                 $this->mContentModel = false; # initialized lazily in getContentModel()
00304                 } else { // page not found
00305                         $this->mArticleID = 0;
00306                         $this->mLength = 0;
00307                         $this->mRedirect = false;
00308                         $this->mLatestID = 0;
00309                         $this->mContentModel = false; # initialized lazily in getContentModel()
00310                 }
00311         }
00312 
00326         public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
00327                 $t = new Title();
00328                 $t->mInterwiki = $interwiki;
00329                 $t->mFragment = $fragment;
00330                 $t->mNamespace = $ns = intval( $ns );
00331                 $t->mDbkeyform = str_replace( ' ', '_', $title );
00332                 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00333                 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00334                 $t->mTextform = str_replace( '_', ' ', $title );
00335                 $t->mContentModel = false; # initialized lazily in getContentModel()
00336                 return $t;
00337         }
00338 
00350         public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
00351                 if ( !MWNamespace::exists( $ns ) ) {
00352                         return null;
00353                 }
00354 
00355                 $t = new Title();
00356                 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
00357                 if ( $t->secureAndSplit() ) {
00358                         return $t;
00359                 } else {
00360                         return null;
00361                 }
00362         }
00363 
00369         public static function newMainPage() {
00370                 $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
00371                 // Don't give fatal errors if the message is broken
00372                 if ( !$title ) {
00373                         $title = Title::newFromText( 'Main Page' );
00374                 }
00375                 return $title;
00376         }
00377 
00388         public static function newFromRedirect( $text ) {
00389                 ContentHandler::deprecated( __METHOD__, '1.21' );
00390 
00391                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00392                 return $content->getRedirectTarget();
00393         }
00394 
00405         public static function newFromRedirectRecurse( $text ) {
00406                 ContentHandler::deprecated( __METHOD__, '1.21' );
00407 
00408                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00409                 return $content->getUltimateRedirectTarget();
00410         }
00411 
00422         public static function newFromRedirectArray( $text ) {
00423                 ContentHandler::deprecated( __METHOD__, '1.21' );
00424 
00425                 $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00426                 return $content->getRedirectChain();
00427         }
00428 
00435         public static function nameOf( $id ) {
00436                 $dbr = wfGetDB( DB_SLAVE );
00437 
00438                 $s = $dbr->selectRow(
00439                         'page',
00440                         array( 'page_namespace', 'page_title' ),
00441                         array( 'page_id' => $id ),
00442                         __METHOD__
00443                 );
00444                 if ( $s === false ) {
00445                         return null;
00446                 }
00447 
00448                 $n = self::makeName( $s->page_namespace, $s->page_title );
00449                 return $n;
00450         }
00451 
00457         public static function legalChars() {
00458                 global $wgLegalTitleChars;
00459                 return $wgLegalTitleChars;
00460         }
00461 
00469         static function getTitleInvalidRegex() {
00470                 static $rxTc = false;
00471                 if ( !$rxTc ) {
00472                         # Matching titles will be held as illegal.
00473                         $rxTc = '/' .
00474                                 # Any character not allowed is forbidden...
00475                                 '[^' . self::legalChars() . ']' .
00476                                 # URL percent encoding sequences interfere with the ability
00477                                 # to round-trip titles -- you can't link to them consistently.
00478                                 '|%[0-9A-Fa-f]{2}' .
00479                                 # XML/HTML character references produce similar issues.
00480                                 '|&[A-Za-z0-9\x80-\xff]+;' .
00481                                 '|&#[0-9]+;' .
00482                                 '|&#x[0-9A-Fa-f]+;' .
00483                                 '/S';
00484                 }
00485 
00486                 return $rxTc;
00487         }
00488 
00497         public static function indexTitle( $ns, $title ) {
00498                 global $wgContLang;
00499 
00500                 $lc = SearchEngine::legalSearchChars() . '&#;';
00501                 $t = $wgContLang->normalizeForSearch( $title );
00502                 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00503                 $t = $wgContLang->lc( $t );
00504 
00505                 # Handle 's, s'
00506                 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00507                 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00508 
00509                 $t = preg_replace( "/\\s+/", ' ', $t );
00510 
00511                 if ( $ns == NS_FILE ) {
00512                         $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00513                 }
00514                 return trim( $t );
00515         }
00516 
00526         public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
00527                 global $wgContLang;
00528 
00529                 $namespace = $wgContLang->getNsText( $ns );
00530                 $name = $namespace == '' ? $title : "$namespace:$title";
00531                 if ( strval( $interwiki ) != '' ) {
00532                         $name = "$interwiki:$name";
00533                 }
00534                 if ( strval( $fragment ) != '' ) {
00535                         $name .= '#' . $fragment;
00536                 }
00537                 return $name;
00538         }
00539 
00546         static function escapeFragmentForURL( $fragment ) {
00547                 # Note that we don't urlencode the fragment.  urlencoded Unicode
00548                 # fragments appear not to work in IE (at least up to 7) or in at least
00549                 # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
00550                 # to care if they aren't encoded.
00551                 return Sanitizer::escapeId( $fragment, 'noninitial' );
00552         }
00553 
00562         public static function compare( $a, $b ) {
00563                 if ( $a->getNamespace() == $b->getNamespace() ) {
00564                         return strcmp( $a->getText(), $b->getText() );
00565                 } else {
00566                         return $a->getNamespace() - $b->getNamespace();
00567                 }
00568         }
00569 
00576         public function isLocal() {
00577                 if ( $this->mInterwiki != '' ) {
00578                         $iw = Interwiki::fetch( $this->mInterwiki );
00579                         if ( $iw ) {
00580                                 return $iw->isLocal();
00581                         }
00582                 }
00583                 return true;
00584         }
00585 
00591         public function isExternal() {
00592                 return ( $this->mInterwiki != '' );
00593         }
00594 
00600         public function getInterwiki() {
00601                 return $this->mInterwiki;
00602         }
00603 
00610         public function isTrans() {
00611                 if ( $this->mInterwiki == '' ) {
00612                         return false;
00613                 }
00614 
00615                 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00616         }
00617 
00623         public function getTransWikiID() {
00624                 if ( $this->mInterwiki == '' ) {
00625                         return false;
00626                 }
00627 
00628                 return Interwiki::fetch( $this->mInterwiki )->getWikiID();
00629         }
00630 
00636         public function getText() {
00637                 return $this->mTextform;
00638         }
00639 
00645         public function getPartialURL() {
00646                 return $this->mUrlform;
00647         }
00648 
00654         public function getDBkey() {
00655                 return $this->mDbkeyform;
00656         }
00657 
00663         function getUserCaseDBKey() {
00664                 return $this->mUserCaseDBKey;
00665         }
00666 
00672         public function getNamespace() {
00673                 return $this->mNamespace;
00674         }
00675 
00682         public function getContentModel() {
00683                 if ( !$this->mContentModel ) {
00684                         $linkCache = LinkCache::singleton();
00685                         $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
00686                 }
00687 
00688                 if ( !$this->mContentModel ) {
00689                         $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
00690                 }
00691 
00692                 if( !$this->mContentModel ) {
00693                         throw new MWException( 'Failed to determine content model!' );
00694                 }
00695 
00696                 return $this->mContentModel;
00697         }
00698 
00705         public function hasContentModel( $id ) {
00706                 return $this->getContentModel() == $id;
00707         }
00708 
00714         public function getNsText() {
00715                 global $wgContLang;
00716 
00717                 if ( $this->mInterwiki != '' ) {
00718                         // This probably shouldn't even happen. ohh man, oh yuck.
00719                         // But for interwiki transclusion it sometimes does.
00720                         // Shit. Shit shit shit.
00721                         //
00722                         // Use the canonical namespaces if possible to try to
00723                         // resolve a foreign namespace.
00724                         if ( MWNamespace::exists( $this->mNamespace ) ) {
00725                                 return MWNamespace::getCanonicalName( $this->mNamespace );
00726                         }
00727                 }
00728 
00729                 if ( $wgContLang->needsGenderDistinction() &&
00730                                 MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
00731                         $gender = GenderCache::singleton()->getGenderOf( $this->getText(), __METHOD__ );
00732                         return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
00733                 }
00734 
00735                 return $wgContLang->getNsText( $this->mNamespace );
00736         }
00737 
00743         public function getSubjectNsText() {
00744                 global $wgContLang;
00745                 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00746         }
00747 
00753         public function getTalkNsText() {
00754                 global $wgContLang;
00755                 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00756         }
00757 
00763         public function canTalk() {
00764                 return( MWNamespace::canTalk( $this->mNamespace ) );
00765         }
00766 
00773         public function canExist() {
00774                 return $this->mNamespace >= NS_MAIN;
00775         }
00776 
00782         public function isWatchable() {
00783                 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
00784         }
00785 
00791         public function isSpecialPage() {
00792                 return $this->getNamespace() == NS_SPECIAL;
00793         }
00794 
00801         public function isSpecial( $name ) {
00802                 if ( $this->isSpecialPage() ) {
00803                         list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
00804                         if ( $name == $thisName ) {
00805                                 return true;
00806                         }
00807                 }
00808                 return false;
00809         }
00810 
00817         public function fixSpecialName() {
00818                 if ( $this->isSpecialPage() ) {
00819                         list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
00820                         if ( $canonicalName ) {
00821                                 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
00822                                 if ( $localName != $this->mDbkeyform ) {
00823                                         return Title::makeTitle( NS_SPECIAL, $localName );
00824                                 }
00825                         }
00826                 }
00827                 return $this;
00828         }
00829 
00840         public function inNamespace( $ns ) {
00841                 return MWNamespace::equals( $this->getNamespace(), $ns );
00842         }
00843 
00851         public function inNamespaces( /* ... */ ) {
00852                 $namespaces = func_get_args();
00853                 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
00854                         $namespaces = $namespaces[0];
00855                 }
00856 
00857                 foreach ( $namespaces as $ns ) {
00858                         if ( $this->inNamespace( $ns ) ) {
00859                                 return true;
00860                         }
00861                 }
00862 
00863                 return false;
00864         }
00865 
00879         public function hasSubjectNamespace( $ns ) {
00880                 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
00881         }
00882 
00890         public function isContentPage() {
00891                 return MWNamespace::isContent( $this->getNamespace() );
00892         }
00893 
00900         public function isMovable() {
00901                 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
00902                         // Interwiki title or immovable namespace. Hooks don't get to override here
00903                         return false;
00904                 }
00905 
00906                 $result = true;
00907                 wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
00908                 return $result;
00909         }
00910 
00921         public function isMainPage() {
00922                 return $this->equals( Title::newMainPage() );
00923         }
00924 
00930         public function isSubpage() {
00931                 return MWNamespace::hasSubpages( $this->mNamespace )
00932                         ? strpos( $this->getText(), '/' ) !== false
00933                         : false;
00934         }
00935 
00941         public function isConversionTable() {
00942                 //@todo: ConversionTable should become a separate content model.
00943 
00944                 return $this->getNamespace() == NS_MEDIAWIKI &&
00945                         strpos( $this->getText(), 'Conversiontable/' ) === 0;
00946         }
00947 
00953         public function isWikitextPage() {
00954                 return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
00955         }
00956 
00968         public function isCssOrJsPage() {
00969                 $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
00970                         && ( $this->hasContentModel( CONTENT_MODEL_CSS )
00971                                 || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
00972 
00973                 #NOTE: this hook is also called in ContentHandler::getDefaultModel. It's called here again to make sure
00974                 #      hook functions can force this method to return true even outside the mediawiki namespace.
00975 
00976                 wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
00977 
00978                 return $isCssOrJsPage;
00979         }
00980 
00985         public function isCssJsSubpage() {
00986                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
00987                                 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
00988                                         || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
00989         }
00990 
00996         public function getSkinFromCssJsSubpage() {
00997                 $subpage = explode( '/', $this->mTextform );
00998                 $subpage = $subpage[ count( $subpage ) - 1 ];
00999                 $lastdot = strrpos( $subpage, '.' );
01000                 if ( $lastdot === false )
01001                         return $subpage; # Never happens: only called for names ending in '.css' or '.js'
01002                 return substr( $subpage, 0, $lastdot );
01003         }
01004 
01010         public function isCssSubpage() {
01011                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
01012                         && $this->hasContentModel( CONTENT_MODEL_CSS ) );
01013         }
01014 
01020         public function isJsSubpage() {
01021                 return ( NS_USER == $this->mNamespace && $this->isSubpage()
01022                         && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
01023         }
01024 
01030         public function isTalkPage() {
01031                 return MWNamespace::isTalk( $this->getNamespace() );
01032         }
01033 
01039         public function getTalkPage() {
01040                 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
01041         }
01042 
01049         public function getSubjectPage() {
01050                 // Is this the same title?
01051                 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
01052                 if ( $this->getNamespace() == $subjectNS ) {
01053                         return $this;
01054                 }
01055                 return Title::makeTitle( $subjectNS, $this->getDBkey() );
01056         }
01057 
01063         public function getDefaultNamespace() {
01064                 return $this->mDefaultNamespace;
01065         }
01066 
01073         public function getIndexTitle() {
01074                 return Title::indexTitle( $this->mNamespace, $this->mTextform );
01075         }
01076 
01082         public function getFragment() {
01083                 return $this->mFragment;
01084         }
01085 
01090         public function getFragmentForURL() {
01091                 if ( $this->mFragment == '' ) {
01092                         return '';
01093                 } else {
01094                         return '#' . Title::escapeFragmentForURL( $this->mFragment );
01095                 }
01096         }
01097 
01108         public function setFragment( $fragment ) {
01109                 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
01110         }
01111 
01120         private function prefix( $name ) {
01121                 $p = '';
01122                 if ( $this->mInterwiki != '' ) {
01123                         $p = $this->mInterwiki . ':';
01124                 }
01125 
01126                 if ( 0 != $this->mNamespace ) {
01127                         $p .= $this->getNsText() . ':';
01128                 }
01129                 return $p . $name;
01130         }
01131 
01138         public function getPrefixedDBkey() {
01139                 $s = $this->prefix( $this->mDbkeyform );
01140                 $s = str_replace( ' ', '_', $s );
01141                 return $s;
01142         }
01143 
01150         public function getPrefixedText() {
01151                 // @todo FIXME: Bad usage of empty() ?
01152                 if ( empty( $this->mPrefixedText ) ) {
01153                         $s = $this->prefix( $this->mTextform );
01154                         $s = str_replace( '_', ' ', $s );
01155                         $this->mPrefixedText = $s;
01156                 }
01157                 return $this->mPrefixedText;
01158         }
01159 
01165         public function __toString() {
01166                 return $this->getPrefixedText();
01167         }
01168 
01175         public function getFullText() {
01176                 $text = $this->getPrefixedText();
01177                 if ( $this->mFragment != '' ) {
01178                         $text .= '#' . $this->mFragment;
01179                 }
01180                 return $text;
01181         }
01182 
01195         public function getRootText() {
01196                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01197                         return $this->getText();
01198                 }
01199 
01200                 return strtok( $this->getText(), '/' );
01201         }
01202 
01215         public function getRootTitle() {
01216                 return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
01217         }
01218 
01230         public function getBaseText() {
01231                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01232                         return $this->getText();
01233                 }
01234 
01235                 $parts = explode( '/', $this->getText() );
01236                 # Don't discard the real title if there's no subpage involved
01237                 if ( count( $parts ) > 1 ) {
01238                         unset( $parts[count( $parts ) - 1] );
01239                 }
01240                 return implode( '/', $parts );
01241         }
01242 
01255         public function getBaseTitle() {
01256                 return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
01257         }
01258 
01270         public function getSubpageText() {
01271                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01272                         return( $this->mTextform );
01273                 }
01274                 $parts = explode( '/', $this->mTextform );
01275                 return( $parts[count( $parts ) - 1] );
01276         }
01277 
01291         public function getSubpage( $text ) {
01292                 return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
01293         }
01294 
01302         public function getEscapedText() {
01303                 wfDeprecated( __METHOD__, '1.19' );
01304                 return htmlspecialchars( $this->getPrefixedText() );
01305         }
01306 
01312         public function getSubpageUrlForm() {
01313                 $text = $this->getSubpageText();
01314                 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
01315                 return( $text );
01316         }
01317 
01323         public function getPrefixedURL() {
01324                 $s = $this->prefix( $this->mDbkeyform );
01325                 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
01326                 return $s;
01327         }
01328 
01342         private static function fixUrlQueryArgs( $query, $query2 = false ) {
01343                 if( $query2 !== false ) {
01344                         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" );
01345                 }
01346                 if ( is_array( $query ) ) {
01347                         $query = wfArrayToCgi( $query );
01348                 }
01349                 if ( $query2 ) {
01350                         if ( is_string( $query2 ) ) {
01351                                 // $query2 is a string, we will consider this to be
01352                                 // a deprecated $variant argument and add it to the query
01353                                 $query2 = wfArrayToCgi( array( 'variant' => $query2 ) );
01354                         } else {
01355                                 $query2 = wfArrayToCgi( $query2 );
01356                         }
01357                         // If we have $query content add a & to it first
01358                         if ( $query ) {
01359                                 $query .= '&';
01360                         }
01361                         // Now append the queries together
01362                         $query .= $query2;
01363                 }
01364                 return $query;
01365         }
01366 
01380         public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01381                 $query = self::fixUrlQueryArgs( $query, $query2 );
01382 
01383                 # Hand off all the decisions on urls to getLocalURL
01384                 $url = $this->getLocalURL( $query );
01385 
01386                 # Expand the url to make it a full url. Note that getLocalURL has the
01387                 # potential to output full urls for a variety of reasons, so we use
01388                 # wfExpandUrl instead of simply prepending $wgServer
01389                 $url = wfExpandUrl( $url, $proto );
01390 
01391                 # Finally, add the fragment.
01392                 $url .= $this->getFragmentForURL();
01393 
01394                 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
01395                 return $url;
01396         }
01397 
01416         public function getLocalURL( $query = '', $query2 = false ) {
01417                 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
01418 
01419                 $query = self::fixUrlQueryArgs( $query, $query2 );
01420 
01421                 $interwiki = Interwiki::fetch( $this->mInterwiki );
01422                 if ( $interwiki ) {
01423                         $namespace = $this->getNsText();
01424                         if ( $namespace != '' ) {
01425                                 # Can this actually happen? Interwikis shouldn't be parsed.
01426                                 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
01427                                 $namespace .= ':';
01428                         }
01429                         $url = $interwiki->getURL( $namespace . $this->getDBkey() );
01430                         $url = wfAppendQuery( $url, $query );
01431                 } else {
01432                         $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
01433                         if ( $query == '' ) {
01434                                 $url = str_replace( '$1', $dbkey, $wgArticlePath );
01435                                 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
01436                         } else {
01437                                 global $wgVariantArticlePath, $wgActionPaths;
01438                                 $url = false;
01439                                 $matches = array();
01440 
01441                                 if ( !empty( $wgActionPaths ) &&
01442                                         preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
01443                                 {
01444                                         $action = urldecode( $matches[2] );
01445                                         if ( isset( $wgActionPaths[$action] ) ) {
01446                                                 $query = $matches[1];
01447                                                 if ( isset( $matches[4] ) ) {
01448                                                         $query .= $matches[4];
01449                                                 }
01450                                                 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
01451                                                 if ( $query != '' ) {
01452                                                         $url = wfAppendQuery( $url, $query );
01453                                                 }
01454                                         }
01455                                 }
01456 
01457                                 if ( $url === false &&
01458                                         $wgVariantArticlePath &&
01459                                         $this->getPageLanguage()->hasVariants() &&
01460                                         preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
01461                                 {
01462                                         $variant = urldecode( $matches[1] );
01463                                         if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
01464                                                 // Only do the variant replacement if the given variant is a valid
01465                                                 // variant for the page's language.
01466                                                 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
01467                                                 $url = str_replace( '$1', $dbkey, $url );
01468                                         }
01469                                 }
01470 
01471                                 if ( $url === false ) {
01472                                         if ( $query == '-' ) {
01473                                                 $query = '';
01474                                         }
01475                                         $url = "{$wgScript}?title={$dbkey}&{$query}";
01476                                 }
01477                         }
01478 
01479                         wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
01480 
01481                         // @todo FIXME: This causes breakage in various places when we
01482                         // actually expected a local URL and end up with dupe prefixes.
01483                         if ( $wgRequest->getVal( 'action' ) == 'render' ) {
01484                                 $url = $wgServer . $url;
01485                         }
01486                 }
01487                 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
01488                 return $url;
01489         }
01490 
01509         public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01510                 wfProfileIn( __METHOD__ );
01511                 if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
01512                         $ret = $this->getFullURL( $query, $query2, $proto );
01513                 } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
01514                         $ret = $this->getFragmentForURL();
01515                 } else {
01516                         $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
01517                 }
01518                 wfProfileOut( __METHOD__ );
01519                 return $ret;
01520         }
01521 
01534         public function escapeLocalURL( $query = '', $query2 = false ) {
01535                 wfDeprecated( __METHOD__, '1.19' );
01536                 return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
01537         }
01538 
01549         public function escapeFullURL( $query = '', $query2 = false ) {
01550                 wfDeprecated( __METHOD__, '1.19' );
01551                 return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
01552         }
01553 
01568         public function getInternalURL( $query = '', $query2 = false ) {
01569                 global $wgInternalServer, $wgServer;
01570                 $query = self::fixUrlQueryArgs( $query, $query2 );
01571                 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
01572                 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
01573                 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
01574                 return $url;
01575         }
01576 
01590         public function getCanonicalURL( $query = '', $query2 = false ) {
01591                 $query = self::fixUrlQueryArgs( $query, $query2 );
01592                 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
01593                 wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
01594                 return $url;
01595         }
01596 
01607         public function escapeCanonicalURL( $query = '', $query2 = false ) {
01608                 wfDeprecated( __METHOD__, '1.19' );
01609                 return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
01610         }
01611 
01618         public function getEditURL() {
01619                 if ( $this->mInterwiki != '' ) {
01620                         return '';
01621                 }
01622                 $s = $this->getLocalURL( 'action=edit' );
01623 
01624                 return $s;
01625         }
01626 
01633         public function userIsWatching() {
01634                 global $wgUser;
01635 
01636                 if ( is_null( $this->mWatched ) ) {
01637                         if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
01638                                 $this->mWatched = false;
01639                         } else {
01640                                 $this->mWatched = $wgUser->isWatched( $this );
01641                         }
01642                 }
01643                 return $this->mWatched;
01644         }
01645 
01653         public function userCanRead() {
01654                 wfDeprecated( __METHOD__, '1.19' );
01655                 return $this->userCan( 'read' );
01656         }
01657 
01673         public function quickUserCan( $action, $user = null ) {
01674                 return $this->userCan( $action, $user, false );
01675         }
01676 
01687         public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
01688                 if ( !$user instanceof User ) {
01689                         global $wgUser;
01690                         $user = $wgUser;
01691                 }
01692                 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
01693         }
01694 
01708         public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01709                 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01710 
01711                 // Remove the errors being ignored.
01712                 foreach ( $errors as $index => $error ) {
01713                         $error_key = is_array( $error ) ? $error[0] : $error;
01714 
01715                         if ( in_array( $error_key, $ignoreErrors ) ) {
01716                                 unset( $errors[$index] );
01717                         }
01718                 }
01719 
01720                 return $errors;
01721         }
01722 
01734         private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01735                 if ( $action == 'create' ) {
01736                         if (
01737                                 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01738                                 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
01739                         ) {
01740                                 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
01741                         }
01742                 } elseif ( $action == 'move' ) {
01743                         if ( !$user->isAllowed( 'move-rootuserpages' )
01744                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01745                                 // Show user page-specific message only if the user can move other pages
01746                                 $errors[] = array( 'cant-move-user-page' );
01747                         }
01748 
01749                         // Check if user is allowed to move files if it's a file
01750                         if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01751                                 $errors[] = array( 'movenotallowedfile' );
01752                         }
01753 
01754                         if ( !$user->isAllowed( 'move' ) ) {
01755                                 // User can't move anything
01756                                 $userCanMove = User::groupHasPermission( 'user', 'move' );
01757                                 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
01758                                 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01759                                         // custom message if logged-in users without any special rights can move
01760                                         $errors[] = array( 'movenologintext' );
01761                                 } else {
01762                                         $errors[] = array( 'movenotallowed' );
01763                                 }
01764                         }
01765                 } elseif ( $action == 'move-target' ) {
01766                         if ( !$user->isAllowed( 'move' ) ) {
01767                                 // User can't move anything
01768                                 $errors[] = array( 'movenotallowed' );
01769                         } elseif ( !$user->isAllowed( 'move-rootuserpages' )
01770                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01771                                 // Show user page-specific message only if the user can move other pages
01772                                 $errors[] = array( 'cant-move-to-user-page' );
01773                         }
01774                 } elseif ( !$user->isAllowed( $action ) ) {
01775                         $errors[] = $this->missingPermissionError( $action, $short );
01776                 }
01777 
01778                 return $errors;
01779         }
01780 
01789         private function resultToError( $errors, $result ) {
01790                 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
01791                         // A single array representing an error
01792                         $errors[] = $result;
01793                 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
01794                         // A nested array representing multiple errors
01795                         $errors = array_merge( $errors, $result );
01796                 } elseif ( $result !== '' && is_string( $result ) ) {
01797                         // A string representing a message-id
01798                         $errors[] = array( $result );
01799                 } elseif ( $result === false ) {
01800                         // a generic "We don't want them to do that"
01801                         $errors[] = array( 'badaccess-group0' );
01802                 }
01803                 return $errors;
01804         }
01805 
01817         private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
01818                 // Use getUserPermissionsErrors instead
01819                 $result = '';
01820                 if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01821                         return $result ? array() : array( array( 'badaccess-group0' ) );
01822                 }
01823                 // Check getUserPermissionsErrors hook
01824                 if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
01825                         $errors = $this->resultToError( $errors, $result );
01826                 }
01827                 // Check getUserPermissionsErrorsExpensive hook
01828                 if (
01829                         $doExpensiveQueries
01830                         && !( $short && count( $errors ) > 0 )
01831                         && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
01832                 ) {
01833                         $errors = $this->resultToError( $errors, $result );
01834                 }
01835 
01836                 return $errors;
01837         }
01838 
01850         private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01851                 # Only 'createaccount' can be performed on special pages,
01852                 # which don't actually exist in the DB.
01853                 if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
01854                         $errors[] = array( 'ns-specialprotected' );
01855                 }
01856 
01857                 # Check $wgNamespaceProtection for restricted namespaces
01858                 if ( $this->isNamespaceProtected( $user ) ) {
01859                         $ns = $this->mNamespace == NS_MAIN ?
01860                                 wfMessage( 'nstab-main' )->text() : $this->getNsText();
01861                         $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
01862                                 array( 'protectedinterface' ) : array( 'namespaceprotected', $ns );
01863                 }
01864 
01865                 return $errors;
01866         }
01867 
01879         private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01880                 # Protect css/js subpages of user pages
01881                 # XXX: this might be better using restrictions
01882                 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
01883                 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
01884                                 && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
01885                         if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
01886                                 $errors[] = array( 'customcssprotected' );
01887                         } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
01888                                 $errors[] = array( 'customjsprotected' );
01889                         }
01890                 }
01891 
01892                 return $errors;
01893         }
01894 
01908         private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01909                 foreach ( $this->getRestrictions( $action ) as $right ) {
01910                         // Backwards compatibility, rewrite sysop -> protect
01911                         if ( $right == 'sysop' ) {
01912                                 $right = 'protect';
01913                         }
01914                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01915                                 // Users with 'editprotected' permission can edit protected pages
01916                                 // without cascading option turned on.
01917                                 if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
01918                                         || $this->mCascadeRestriction )
01919                                 {
01920                                         $errors[] = array( 'protectedpagetext', $right );
01921                                 }
01922                         }
01923                 }
01924 
01925                 return $errors;
01926         }
01927 
01939         private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01940                 if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01941                         # We /could/ use the protection level on the source page, but it's
01942                         # fairly ugly as we have to establish a precedence hierarchy for pages
01943                         # included by multiple cascade-protected pages. So just restrict
01944                         # it to people with 'protect' permission, as they could remove the
01945                         # protection anyway.
01946                         list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01947                         # Cascading protection depends on more than this page...
01948                         # Several cascading protected pages may include this page...
01949                         # Check each cascading level
01950                         # This is only for protection restrictions, not for all actions
01951                         if ( isset( $restrictions[$action] ) ) {
01952                                 foreach ( $restrictions[$action] as $right ) {
01953                                         $right = ( $right == 'sysop' ) ? 'protect' : $right;
01954                                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01955                                                 $pages = '';
01956                                                 foreach ( $cascadingSources as $page )
01957                                                         $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01958                                                 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01959                                         }
01960                                 }
01961                         }
01962                 }
01963 
01964                 return $errors;
01965         }
01966 
01978         private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01979                 global $wgDeleteRevisionsLimit, $wgLang;
01980 
01981                 if ( $action == 'protect' ) {
01982                         if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
01983                                 // If they can't edit, they shouldn't protect.
01984                                 $errors[] = array( 'protect-cantedit' );
01985                         }
01986                 } elseif ( $action == 'create' ) {
01987                         $title_protection = $this->getTitleProtection();
01988                         if( $title_protection ) {
01989                                 if( $title_protection['pt_create_perm'] == 'sysop' ) {
01990                                         $title_protection['pt_create_perm'] = 'protect'; // B/C
01991                                 }
01992                                 if( $title_protection['pt_create_perm'] == '' ||
01993                                         !$user->isAllowed( $title_protection['pt_create_perm'] ) )
01994                                 {
01995                                         $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
01996                                 }
01997                         }
01998                 } elseif ( $action == 'move' ) {
01999                         // Check for immobile pages
02000                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
02001                                 // Specific message for this case
02002                                 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
02003                         } elseif ( !$this->isMovable() ) {
02004                                 // Less specific message for rarer cases
02005                                 $errors[] = array( 'immobile-source-page' );
02006                         }
02007                 } elseif ( $action == 'move-target' ) {
02008                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
02009                                 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
02010                         } elseif ( !$this->isMovable() ) {
02011                                 $errors[] = array( 'immobile-target-page' );
02012                         }
02013                 } elseif ( $action == 'delete' ) {
02014                         if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
02015                                 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
02016                         {
02017                                 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
02018                         }
02019                 }
02020                 return $errors;
02021         }
02022 
02034         private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
02035                 // Account creation blocks handled at userlogin.
02036                 // Unblocking handled in SpecialUnblock
02037                 if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
02038                         return $errors;
02039                 }
02040 
02041                 global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
02042 
02043                 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
02044                         $errors[] = array( 'confirmedittext' );
02045                 }
02046 
02047                 if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
02048                         // Don't block the user from editing their own talk page unless they've been
02049                         // explicitly blocked from that too.
02050                 } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
02051                         $block = $user->getBlock();
02052 
02053                         // This is from OutputPage::blockedPage
02054                         // Copied at r23888 by werdna
02055 
02056                         $id = $user->blockedBy();
02057                         $reason = $user->blockedFor();
02058                         if ( $reason == '' ) {
02059                                 $reason = wfMessage( 'blockednoreason' )->text();
02060                         }
02061                         $ip = $user->getRequest()->getIP();
02062 
02063                         if ( is_numeric( $id ) ) {
02064                                 $name = User::whoIs( $id );
02065                         } else {
02066                                 $name = $id;
02067                         }
02068 
02069                         $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
02070                         $blockid = $block->getId();
02071                         $blockExpiry = $block->getExpiry();
02072                         $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
02073                         if ( $blockExpiry == 'infinity' ) {
02074                                 $blockExpiry = wfMessage( 'infiniteblock' )->text();
02075                         } else {
02076                                 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
02077                         }
02078 
02079                         $intended = strval( $block->getTarget() );
02080 
02081                         $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
02082                                 $blockid, $blockExpiry, $intended, $blockTimestamp );
02083                 }
02084 
02085                 return $errors;
02086         }
02087 
02099         private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
02100                 global $wgWhitelistRead, $wgWhitelistReadRegexp, $wgRevokePermissions;
02101                 static $useShortcut = null;
02102 
02103                 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
02104                 if ( is_null( $useShortcut ) ) {
02105                         $useShortcut = true;
02106                         if ( !User::groupHasPermission( '*', 'read' ) ) {
02107                                 # Not a public wiki, so no shortcut
02108                                 $useShortcut = false;
02109                         } elseif ( !empty( $wgRevokePermissions ) ) {
02116                                 foreach ( $wgRevokePermissions as $perms ) {
02117                                         if ( !empty( $perms['read'] ) ) {
02118                                                 # We might be removing the read right from the user, so no shortcut
02119                                                 $useShortcut = false;
02120                                                 break;
02121                                         }
02122                                 }
02123                         }
02124                 }
02125 
02126                 $whitelisted = false;
02127                 if ( $useShortcut ) {
02128                         # Shortcut for public wikis, allows skipping quite a bit of code
02129                         $whitelisted = true;
02130                 } elseif ( $user->isAllowed( 'read' ) ) {
02131                         # If the user is allowed to read pages, he is allowed to read all pages
02132                         $whitelisted = true;
02133                 } elseif ( $this->isSpecial( 'Userlogin' )
02134                         || $this->isSpecial( 'ChangePassword' )
02135                         || $this->isSpecial( 'PasswordReset' )
02136                 ) {
02137                         # Always grant access to the login page.
02138                         # Even anons need to be able to log in.
02139                         $whitelisted = true;
02140                 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
02141                         # Time to check the whitelist
02142                         # Only do these checks is there's something to check against
02143                         $name = $this->getPrefixedText();
02144                         $dbName = $this->getPrefixedDBkey();
02145 
02146                         // Check for explicit whitelisting with and without underscores
02147                         if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
02148                                 $whitelisted = true;
02149                         } elseif ( $this->getNamespace() == NS_MAIN ) {
02150                                 # Old settings might have the title prefixed with
02151                                 # a colon for main-namespace pages
02152                                 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
02153                                         $whitelisted = true;
02154                                 }
02155                         } elseif ( $this->isSpecialPage() ) {
02156                                 # If it's a special page, ditch the subpage bit and check again
02157                                 $name = $this->getDBkey();
02158                                 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
02159                                 if ( $name ) {
02160                                         $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
02161                                         if ( in_array( $pure, $wgWhitelistRead, true ) ) {
02162                                                 $whitelisted = true;
02163                                         }
02164                                 }
02165                         }
02166                 }
02167 
02168                 if( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
02169                         $name = $this->getPrefixedText();
02170                         // Check for regex whitelisting
02171                         foreach ( $wgWhitelistReadRegexp as $listItem ) {
02172                                 if ( preg_match( $listItem, $name ) ) {
02173                                         $whitelisted = true;
02174                                         break;
02175                                 }
02176                         }
02177                 }
02178 
02179                 if ( !$whitelisted ) {
02180                         # If the title is not whitelisted, give extensions a chance to do so...
02181                         wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
02182                         if ( !$whitelisted ) {
02183                                 $errors[] = $this->missingPermissionError( $action, $short );
02184                         }
02185                 }
02186 
02187                 return $errors;
02188         }
02189 
02198         private function missingPermissionError( $action, $short ) {
02199                 // We avoid expensive display logic for quickUserCan's and such
02200                 if ( $short ) {
02201                         return array( 'badaccess-group0' );
02202                 }
02203 
02204                 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
02205                         User::getGroupsWithPermission( $action ) );
02206 
02207                 if ( count( $groups ) ) {
02208                         global $wgLang;
02209                         return array(
02210                                 'badaccess-groups',
02211                                 $wgLang->commaList( $groups ),
02212                                 count( $groups )
02213                         );
02214                 } else {
02215                         return array( 'badaccess-group0' );
02216                 }
02217         }
02218 
02230         protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
02231                 wfProfileIn( __METHOD__ );
02232 
02233                 # Read has special handling
02234                 if ( $action == 'read' ) {
02235                         $checks = array(
02236                                 'checkPermissionHooks',
02237                                 'checkReadPermissions',
02238                         );
02239                 } else {
02240                         $checks = array(
02241                                 'checkQuickPermissions',
02242                                 'checkPermissionHooks',
02243                                 'checkSpecialsAndNSPermissions',
02244                                 'checkCSSandJSPermissions',
02245                                 'checkPageRestrictions',
02246                                 'checkCascadingSourcesRestrictions',
02247                                 'checkActionPermissions',
02248                                 'checkUserBlock'
02249                         );
02250                 }
02251 
02252                 $errors = array();
02253                 while( count( $checks ) > 0 &&
02254                                 !( $short && count( $errors ) > 0 ) ) {
02255                         $method = array_shift( $checks );
02256                         $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
02257                 }
02258 
02259                 wfProfileOut( __METHOD__ );
02260                 return $errors;
02261         }
02262 
02270         public function userCanEditCssSubpage() {
02271                 global $wgUser;
02272                 wfDeprecated( __METHOD__, '1.19' );
02273                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
02274                         || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02275         }
02276 
02284         public function userCanEditJsSubpage() {
02285                 global $wgUser;
02286                 wfDeprecated( __METHOD__, '1.19' );
02287                 return (
02288                         ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
02289                         || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform )
02290                 );
02291         }
02292 
02300         public static function getFilteredRestrictionTypes( $exists = true ) {
02301                 global $wgRestrictionTypes;
02302                 $types = $wgRestrictionTypes;
02303                 if ( $exists ) {
02304                         # Remove the create restriction for existing titles
02305                         $types = array_diff( $types, array( 'create' ) );
02306                 } else {
02307                         # Only the create and upload restrictions apply to non-existing titles
02308                         $types = array_intersect( $types, array( 'create', 'upload' ) );
02309                 }
02310                 return $types;
02311         }
02312 
02318         public function getRestrictionTypes() {
02319                 if ( $this->isSpecialPage() ) {
02320                         return array();
02321                 }
02322 
02323                 $types = self::getFilteredRestrictionTypes( $this->exists() );
02324 
02325                 if ( $this->getNamespace() != NS_FILE ) {
02326                         # Remove the upload restriction for non-file titles
02327                         $types = array_diff( $types, array( 'upload' ) );
02328                 }
02329 
02330                 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
02331 
02332                 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
02333                         $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
02334 
02335                 return $types;
02336         }
02337 
02345         private function getTitleProtection() {
02346                 // Can't protect pages in special namespaces
02347                 if ( $this->getNamespace() < 0 ) {
02348                         return false;
02349                 }
02350 
02351                 // Can't protect pages that exist.
02352                 if ( $this->exists() ) {
02353                         return false;
02354                 }
02355 
02356                 if ( !isset( $this->mTitleProtection ) ) {
02357                         $dbr = wfGetDB( DB_SLAVE );
02358                         $res = $dbr->select(
02359                                 'protected_titles',
02360                                 array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
02361                                 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02362                                 __METHOD__
02363                         );
02364 
02365                         // fetchRow returns false if there are no rows.
02366                         $this->mTitleProtection = $dbr->fetchRow( $res );
02367                 }
02368                 return $this->mTitleProtection;
02369         }
02370 
02380         public function updateTitleProtection( $create_perm, $reason, $expiry ) {
02381                 wfDeprecated( __METHOD__, '1.19' );
02382 
02383                 global $wgUser;
02384 
02385                 $limit = array( 'create' => $create_perm );
02386                 $expiry = array( 'create' => $expiry );
02387 
02388                 $page = WikiPage::factory( $this );
02389                 $cascade = false;
02390                 $status = $page->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $wgUser );
02391 
02392                 return $status->isOK();
02393         }
02394 
02398         public function deleteTitleProtection() {
02399                 $dbw = wfGetDB( DB_MASTER );
02400 
02401                 $dbw->delete(
02402                         'protected_titles',
02403                         array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02404                         __METHOD__
02405                 );
02406                 $this->mTitleProtection = false;
02407         }
02408 
02415         public function isSemiProtected( $action = 'edit' ) {
02416                 if ( $this->exists() ) {
02417                         $restrictions = $this->getRestrictions( $action );
02418                         if ( count( $restrictions ) > 0 ) {
02419                                 foreach ( $restrictions as $restriction ) {
02420                                         if ( strtolower( $restriction ) != 'autoconfirmed' ) {
02421                                                 return false;
02422                                         }
02423                                 }
02424                         } else {
02425                                 # Not protected
02426                                 return false;
02427                         }
02428                         return true;
02429                 } else {
02430                         # If it doesn't exist, it can't be protected
02431                         return false;
02432                 }
02433         }
02434 
02442         public function isProtected( $action = '' ) {
02443                 global $wgRestrictionLevels;
02444 
02445                 $restrictionTypes = $this->getRestrictionTypes();
02446 
02447                 # Special pages have inherent protection
02448                 if( $this->isSpecialPage() ) {
02449                         return true;
02450                 }
02451 
02452                 # Check regular protection levels
02453                 foreach ( $restrictionTypes as $type ) {
02454                         if ( $action == $type || $action == '' ) {
02455                                 $r = $this->getRestrictions( $type );
02456                                 foreach ( $wgRestrictionLevels as $level ) {
02457                                         if ( in_array( $level, $r ) && $level != '' ) {
02458                                                 return true;
02459                                         }
02460                                 }
02461                         }
02462                 }
02463 
02464                 return false;
02465         }
02466 
02474         public function isNamespaceProtected( User $user ) {
02475                 global $wgNamespaceProtection;
02476 
02477                 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
02478                         foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
02479                                 if ( $right != '' && !$user->isAllowed( $right ) ) {
02480                                         return true;
02481                                 }
02482                         }
02483                 }
02484                 return false;
02485         }
02486 
02492         public function isCascadeProtected() {
02493                 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
02494                 return ( $sources > 0 );
02495         }
02496 
02507         public function getCascadeProtectionSources( $getPages = true ) {
02508                 global $wgContLang;
02509                 $pagerestrictions = array();
02510 
02511                 if ( isset( $this->mCascadeSources ) && $getPages ) {
02512                         return array( $this->mCascadeSources, $this->mCascadingRestrictions );
02513                 } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
02514                         return array( $this->mHasCascadingRestrictions, $pagerestrictions );
02515                 }
02516 
02517                 wfProfileIn( __METHOD__ );
02518 
02519                 $dbr = wfGetDB( DB_SLAVE );
02520 
02521                 if ( $this->getNamespace() == NS_FILE ) {
02522                         $tables = array( 'imagelinks', 'page_restrictions' );
02523                         $where_clauses = array(
02524                                 'il_to' => $this->getDBkey(),
02525                                 'il_from=pr_page',
02526                                 'pr_cascade' => 1
02527                         );
02528                 } else {
02529                         $tables = array( 'templatelinks', 'page_restrictions' );
02530                         $where_clauses = array(
02531                                 'tl_namespace' => $this->getNamespace(),
02532                                 'tl_title' => $this->getDBkey(),
02533                                 'tl_from=pr_page',
02534                                 'pr_cascade' => 1
02535                         );
02536                 }
02537 
02538                 if ( $getPages ) {
02539                         $cols = array( 'pr_page', 'page_namespace', 'page_title',
02540                                 'pr_expiry', 'pr_type', 'pr_level' );
02541                         $where_clauses[] = 'page_id=pr_page';
02542                         $tables[] = 'page';
02543                 } else {
02544                         $cols = array( 'pr_expiry' );
02545                 }
02546 
02547                 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
02548 
02549                 $sources = $getPages ? array() : false;
02550                 $now = wfTimestampNow();
02551                 $purgeExpired = false;
02552 
02553                 foreach ( $res as $row ) {
02554                         $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02555                         if ( $expiry > $now ) {
02556                                 if ( $getPages ) {
02557                                         $page_id = $row->pr_page;
02558                                         $page_ns = $row->page_namespace;
02559                                         $page_title = $row->page_title;
02560                                         $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
02561                                         # Add groups needed for each restriction type if its not already there
02562                                         # Make sure this restriction type still exists
02563 
02564                                         if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
02565                                                 $pagerestrictions[$row->pr_type] = array();
02566                                         }
02567 
02568                                         if (
02569                                                 isset( $pagerestrictions[$row->pr_type] )
02570                                                 && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
02571                                         ) {
02572                                                 $pagerestrictions[$row->pr_type][] = $row->pr_level;
02573                                         }
02574                                 } else {
02575                                         $sources = true;
02576                                 }
02577                         } else {
02578                                 // Trigger lazy purge of expired restrictions from the db
02579                                 $purgeExpired = true;
02580                         }
02581                 }
02582                 if ( $purgeExpired ) {
02583                         Title::purgeExpiredRestrictions();
02584                 }
02585 
02586                 if ( $getPages ) {
02587                         $this->mCascadeSources = $sources;
02588                         $this->mCascadingRestrictions = $pagerestrictions;
02589                 } else {
02590                         $this->mHasCascadingRestrictions = $sources;
02591                 }
02592 
02593                 wfProfileOut( __METHOD__ );
02594                 return array( $sources, $pagerestrictions );
02595         }
02596 
02603         public function getRestrictions( $action ) {
02604                 if ( !$this->mRestrictionsLoaded ) {
02605                         $this->loadRestrictions();
02606                 }
02607                 return isset( $this->mRestrictions[$action] )
02608                                 ? $this->mRestrictions[$action]
02609                                 : array();
02610         }
02611 
02619         public function getRestrictionExpiry( $action ) {
02620                 if ( !$this->mRestrictionsLoaded ) {
02621                         $this->loadRestrictions();
02622                 }
02623                 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02624         }
02625 
02631         function areRestrictionsCascading() {
02632                 if ( !$this->mRestrictionsLoaded ) {
02633                         $this->loadRestrictions();
02634                 }
02635 
02636                 return $this->mCascadeRestriction;
02637         }
02638 
02646         private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
02647                 $rows = array();
02648 
02649                 foreach ( $res as $row ) {
02650                         $rows[] = $row;
02651                 }
02652 
02653                 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
02654         }
02655 
02665         public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
02666                 global $wgContLang;
02667                 $dbr = wfGetDB( DB_SLAVE );
02668 
02669                 $restrictionTypes = $this->getRestrictionTypes();
02670 
02671                 foreach ( $restrictionTypes as $type ) {
02672                         $this->mRestrictions[$type] = array();
02673                         $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
02674                 }
02675 
02676                 $this->mCascadeRestriction = false;
02677 
02678                 # Backwards-compatibility: also load the restrictions from the page record (old format).
02679 
02680                 if ( $oldFashionedRestrictions === null ) {
02681                         $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
02682                                 array( 'page_id' => $this->getArticleID() ), __METHOD__ );
02683                 }
02684 
02685                 if ( $oldFashionedRestrictions != '' ) {
02686 
02687                         foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
02688                                 $temp = explode( '=', trim( $restrict ) );
02689                                 if ( count( $temp ) == 1 ) {
02690                                         // old old format should be treated as edit/move restriction
02691                                         $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
02692                                         $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
02693                                 } else {
02694                                         $restriction = trim( $temp[1] );
02695                                         if( $restriction != '' ) { //some old entries are empty
02696                                                 $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
02697                                         }
02698                                 }
02699                         }
02700 
02701                         $this->mOldRestrictions = true;
02702 
02703                 }
02704 
02705                 if ( count( $rows ) ) {
02706                         # Current system - load second to make them override.
02707                         $now = wfTimestampNow();
02708                         $purgeExpired = false;
02709 
02710                         # Cycle through all the restrictions.
02711                         foreach ( $rows as $row ) {
02712 
02713                                 // Don't take care of restrictions types that aren't allowed
02714                                 if ( !in_array( $row->pr_type, $restrictionTypes ) )
02715                                         continue;
02716 
02717                                 // This code should be refactored, now that it's being used more generally,
02718                                 // But I don't really see any harm in leaving it in Block for now -werdna
02719                                 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02720 
02721                                 // Only apply the restrictions if they haven't expired!
02722                                 if ( !$expiry || $expiry > $now ) {
02723                                         $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
02724                                         $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
02725 
02726                                         $this->mCascadeRestriction |= $row->pr_cascade;
02727                                 } else {
02728                                         // Trigger a lazy purge of expired restrictions
02729                                         $purgeExpired = true;
02730                                 }
02731                         }
02732 
02733                         if ( $purgeExpired ) {
02734                                 Title::purgeExpiredRestrictions();
02735                         }
02736                 }
02737 
02738                 $this->mRestrictionsLoaded = true;
02739         }
02740 
02747         public function loadRestrictions( $oldFashionedRestrictions = null ) {
02748                 global $wgContLang;
02749                 if ( !$this->mRestrictionsLoaded ) {
02750                         if ( $this->exists() ) {
02751                                 $dbr = wfGetDB( DB_SLAVE );
02752 
02753                                 $res = $dbr->select(
02754                                         'page_restrictions',
02755                                         array( 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ),
02756                                         array( 'pr_page' => $this->getArticleID() ),
02757                                         __METHOD__
02758                                 );
02759 
02760                                 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
02761                         } else {
02762                                 $title_protection = $this->getTitleProtection();
02763 
02764                                 if ( $title_protection ) {
02765                                         $now = wfTimestampNow();
02766                                         $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
02767 
02768                                         if ( !$expiry || $expiry > $now ) {
02769                                                 // Apply the restrictions
02770                                                 $this->mRestrictionsExpiry['create'] = $expiry;
02771                                                 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
02772                                         } else { // Get rid of the old restrictions
02773                                                 Title::purgeExpiredRestrictions();
02774                                                 $this->mTitleProtection = false;
02775                                         }
02776                                 } else {
02777                                         $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
02778                                 }
02779                                 $this->mRestrictionsLoaded = true;
02780                         }
02781                 }
02782         }
02783 
02788         public function flushRestrictions() {
02789                 $this->mRestrictionsLoaded = false;
02790                 $this->mTitleProtection = null;
02791         }
02792 
02796         static function purgeExpiredRestrictions() {
02797                 if ( wfReadOnly() ) {
02798                         return;
02799                 }
02800 
02801                 $dbw = wfGetDB( DB_MASTER );
02802                 $dbw->delete(
02803                         'page_restrictions',
02804                         array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02805                         __METHOD__
02806                 );
02807 
02808                 $dbw->delete(
02809                         'protected_titles',
02810                         array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02811                         __METHOD__
02812                 );
02813         }
02814 
02820         public function hasSubpages() {
02821                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
02822                         # Duh
02823                         return false;
02824                 }
02825 
02826                 # We dynamically add a member variable for the purpose of this method
02827                 # alone to cache the result.  There's no point in having it hanging
02828                 # around uninitialized in every Title object; therefore we only add it
02829                 # if needed and don't declare it statically.
02830                 if ( isset( $this->mHasSubpages ) ) {
02831                         return $this->mHasSubpages;
02832                 }
02833 
02834                 $subpages = $this->getSubpages( 1 );
02835                 if ( $subpages instanceof TitleArray ) {
02836                         return $this->mHasSubpages = (bool)$subpages->count();
02837                 }
02838                 return $this->mHasSubpages = false;
02839         }
02840 
02848         public function getSubpages( $limit = -1 ) {
02849                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
02850                         return array();
02851                 }
02852 
02853                 $dbr = wfGetDB( DB_SLAVE );
02854                 $conds['page_namespace'] = $this->getNamespace();
02855                 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
02856                 $options = array();
02857                 if ( $limit > -1 ) {
02858                         $options['LIMIT'] = $limit;
02859                 }
02860                 return $this->mSubpages = TitleArray::newFromResult(
02861                         $dbr->select( 'page',
02862                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
02863                                 $conds,
02864                                 __METHOD__,
02865                                 $options
02866                         )
02867                 );
02868         }
02869 
02875         public function isDeleted() {
02876                 if ( $this->getNamespace() < 0 ) {
02877                         $n = 0;
02878                 } else {
02879                         $dbr = wfGetDB( DB_SLAVE );
02880 
02881                         $n = $dbr->selectField( 'archive', 'COUNT(*)',
02882                                 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02883                                 __METHOD__
02884                         );
02885                         if ( $this->getNamespace() == NS_FILE ) {
02886                                 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
02887                                         array( 'fa_name' => $this->getDBkey() ),
02888                                         __METHOD__
02889                                 );
02890                         }
02891                 }
02892                 return (int)$n;
02893         }
02894 
02900         public function isDeletedQuick() {
02901                 if ( $this->getNamespace() < 0 ) {
02902                         return false;
02903                 }
02904                 $dbr = wfGetDB( DB_SLAVE );
02905                 $deleted = (bool)$dbr->selectField( 'archive', '1',
02906                         array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02907                         __METHOD__
02908                 );
02909                 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
02910                         $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02911                                 array( 'fa_name' => $this->getDBkey() ),
02912                                 __METHOD__
02913                         );
02914                 }
02915                 return $deleted;
02916         }
02917 
02926         public function getArticleID( $flags = 0 ) {
02927                 if ( $this->getNamespace() < 0 ) {
02928                         return $this->mArticleID = 0;
02929                 }
02930                 $linkCache = LinkCache::singleton();
02931                 if ( $flags & self::GAID_FOR_UPDATE ) {
02932                         $oldUpdate = $linkCache->forUpdate( true );
02933                         $linkCache->clearLink( $this );
02934                         $this->mArticleID = $linkCache->addLinkObj( $this );
02935                         $linkCache->forUpdate( $oldUpdate );
02936                 } else {
02937                         if ( -1 == $this->mArticleID ) {
02938                                 $this->mArticleID = $linkCache->addLinkObj( $this );
02939                         }
02940                 }
02941                 return $this->mArticleID;
02942         }
02943 
02951         public function isRedirect( $flags = 0 ) {
02952                 if ( !is_null( $this->mRedirect ) ) {
02953                         return $this->mRedirect;
02954                 }
02955                 # Calling getArticleID() loads the field from cache as needed
02956                 if ( !$this->getArticleID( $flags ) ) {
02957                         return $this->mRedirect = false;
02958                 }
02959 
02960                 $linkCache = LinkCache::singleton();
02961                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02962                 if ( $cached === null ) {
02963                         // TODO: check the assumption that the cache actually knows about this title
02964                         // and handle this, such as get the title from the database.
02965                         // See https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
02966                         wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
02967                         wfDebug( wfBacktrace() );
02968                 }
02969 
02970                 $this->mRedirect = (bool)$cached;
02971 
02972                 return $this->mRedirect;
02973         }
02974 
02982         public function getLength( $flags = 0 ) {
02983                 if ( $this->mLength != -1 ) {
02984                         return $this->mLength;
02985                 }
02986                 # Calling getArticleID() loads the field from cache as needed
02987                 if ( !$this->getArticleID( $flags ) ) {
02988                         return $this->mLength = 0;
02989                 }
02990                 $linkCache = LinkCache::singleton();
02991                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
02992                 if ( $cached === null ) { # check the assumption that the cache actually knows about this title
02993                         # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
02994                         #      as a stop gap, perhaps log this, but don't throw an exception?
02995                         wfDebug( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
02996                         wfDebug( wfBacktrace() );
02997                 }
02998 
02999                 $this->mLength = intval( $cached );
03000 
03001                 return $this->mLength;
03002         }
03003 
03011         public function getLatestRevID( $flags = 0 ) {
03012                 if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
03013                         return intval( $this->mLatestID );
03014                 }
03015                 # Calling getArticleID() loads the field from cache as needed
03016                 if ( !$this->getArticleID( $flags ) ) {
03017                         return $this->mLatestID = 0;
03018                 }
03019                 $linkCache = LinkCache::singleton();
03020                 $linkCache->addLinkObj( $this );
03021                 $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
03022                 if ( $cached === null ) { # check the assumption that the cache actually knows about this title
03023                         # XXX: this does apparently happen, see https://bugzilla.wikimedia.org/show_bug.cgi?id=37209
03024                         #      as a stop gap, perhaps log this, but don't throw an exception?
03025                         throw new MWException( "LinkCache doesn't currently know about this title: " . $this->getPrefixedDBkey() );
03026                 }
03027 
03028                 $this->mLatestID = intval( $cached );
03029 
03030                 return $this->mLatestID;
03031         }
03032 
03043         public function resetArticleID( $newid ) {
03044                 $linkCache = LinkCache::singleton();
03045                 $linkCache->clearLink( $this );
03046 
03047                 if ( $newid === false ) {
03048                         $this->mArticleID = -1;
03049                 } else {
03050                         $this->mArticleID = intval( $newid );
03051                 }
03052                 $this->mRestrictionsLoaded = false;
03053                 $this->mRestrictions = array();
03054                 $this->mRedirect = null;
03055                 $this->mLength = -1;
03056                 $this->mLatestID = false;
03057                 $this->mContentModel = false;
03058                 $this->mEstimateRevisions = null;
03059         }
03060 
03068         public static function capitalize( $text, $ns = NS_MAIN ) {
03069                 global $wgContLang;
03070 
03071                 if ( MWNamespace::isCapitalized( $ns ) ) {
03072                         return $wgContLang->ucfirst( $text );
03073                 } else {
03074                         return $text;
03075                 }
03076         }
03077 
03089         private function secureAndSplit() {
03090                 global $wgContLang, $wgLocalInterwiki;
03091 
03092                 # Initialisation
03093                 $this->mInterwiki = $this->mFragment = '';
03094                 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
03095 
03096                 $dbkey = $this->mDbkeyform;
03097 
03098                 # Strip Unicode bidi override characters.
03099                 # Sometimes they slip into cut-n-pasted page titles, where the
03100                 # override chars get included in list displays.
03101                 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
03102 
03103                 # Clean up whitespace
03104                 # Note: use of the /u option on preg_replace here will cause
03105                 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
03106                 # conveniently disabling them.
03107                 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
03108                 $dbkey = trim( $dbkey, '_' );
03109 
03110                 if ( $dbkey == '' ) {
03111                         return false;
03112                 }
03113 
03114                 if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
03115                         # Contained illegal UTF-8 sequences or forbidden Unicode chars.
03116                         return false;
03117                 }
03118 
03119                 $this->mDbkeyform = $dbkey;
03120 
03121                 # Initial colon indicates main namespace rather than specified default
03122                 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
03123                 if ( ':' == $dbkey[0] ) {
03124                         $this->mNamespace = NS_MAIN;
03125                         $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
03126                         $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
03127                 }
03128 
03129                 # Namespace or interwiki prefix
03130                 $firstPass = true;
03131                 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
03132                 do {
03133                         $m = array();
03134                         if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
03135                                 $p = $m[1];
03136                                 if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
03137                                         # Ordinary namespace
03138                                         $dbkey = $m[2];
03139                                         $this->mNamespace = $ns;
03140                                         # For Talk:X pages, check if X has a "namespace" prefix
03141                                         if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
03142                                                 if ( $wgContLang->getNsIndex( $x[1] ) ) {
03143                                                         # Disallow Talk:File:x type titles...
03144                                                         return false;
03145                                                 } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
03146                                                         # Disallow Talk:Interwiki:x type titles...
03147                                                         return false;
03148                                                 }
03149                                         }
03150                                 } elseif ( Interwiki::isValidInterwiki( $p ) ) {
03151                                         if ( !$firstPass ) {
03152                                                 # Can't make a local interwiki link to an interwiki link.
03153                                                 # That's just crazy!
03154                                                 return false;
03155                                         }
03156 
03157                                         # Interwiki link
03158                                         $dbkey = $m[2];
03159                                         $this->mInterwiki = $wgContLang->lc( $p );
03160 
03161                                         # Redundant interwiki prefix to the local wiki
03162                                         if ( $wgLocalInterwiki !== false
03163                                                 && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
03164                                         {
03165                                                 if ( $dbkey == '' ) {
03166                                                         # Can't have an empty self-link
03167                                                         return false;
03168                                                 }
03169                                                 $this->mInterwiki = '';
03170                                                 $firstPass = false;
03171                                                 # Do another namespace split...
03172                                                 continue;
03173                                         }
03174 
03175                                         # If there's an initial colon after the interwiki, that also
03176                                         # resets the default namespace
03177                                         if ( $dbkey !== '' && $dbkey[0] == ':' ) {
03178                                                 $this->mNamespace = NS_MAIN;
03179                                                 $dbkey = substr( $dbkey, 1 );
03180                                         }
03181                                 }
03182                                 # If there's no recognized interwiki or namespace,
03183                                 # then let the colon expression be part of the title.
03184                         }
03185                         break;
03186                 } while ( true );
03187 
03188                 # We already know that some pages won't be in the database!
03189                 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
03190                         $this->mArticleID = 0;
03191                 }
03192                 $fragment = strstr( $dbkey, '#' );
03193                 if ( false !== $fragment ) {
03194                         $this->setFragment( $fragment );
03195                         $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
03196                         # remove whitespace again: prevents "Foo_bar_#"
03197                         # becoming "Foo_bar_"
03198                         $dbkey = preg_replace( '/_*$/', '', $dbkey );
03199                 }
03200 
03201                 # Reject illegal characters.
03202                 $rxTc = self::getTitleInvalidRegex();
03203                 if ( preg_match( $rxTc, $dbkey ) ) {
03204                         return false;
03205                 }
03206 
03207                 # Pages with "/./" or "/../" appearing in the URLs will often be un-
03208                 # reachable due to the way web browsers deal with 'relative' URLs.
03209                 # Also, they conflict with subpage syntax.  Forbid them explicitly.
03210                 if (
03211                         strpos( $dbkey, '.' ) !== false &&
03212                         (
03213                                 $dbkey === '.' || $dbkey === '..' ||
03214                                 strpos( $dbkey, './' ) === 0 ||
03215                                 strpos( $dbkey, '../' ) === 0 ||
03216                                 strpos( $dbkey, '/./' ) !== false ||
03217                                 strpos( $dbkey, '/../' ) !== false ||
03218                                 substr( $dbkey, -2 ) == '/.' ||
03219                                 substr( $dbkey, -3 ) == '/..'
03220                         )
03221                 ) {
03222                         return false;
03223                 }
03224 
03225                 # Magic tilde sequences? Nu-uh!
03226                 if ( strpos( $dbkey, '~~~' ) !== false ) {
03227                         return false;
03228                 }
03229 
03230                 # Limit the size of titles to 255 bytes. This is typically the size of the
03231                 # underlying database field. We make an exception for special pages, which
03232                 # don't need to be stored in the database, and may edge over 255 bytes due
03233                 # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
03234                 if (
03235                         ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 )
03236                         || strlen( $dbkey ) > 512
03237                 ) {
03238                         return false;
03239                 }
03240 
03241                 # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
03242                 # and [[Foo]] point to the same place.  Don't force it for interwikis, since the
03243                 # other site might be case-sensitive.
03244                 $this->mUserCaseDBKey = $dbkey;
03245                 if ( $this->mInterwiki == '' ) {
03246                         $dbkey = self::capitalize( $dbkey, $this->mNamespace );
03247                 }
03248 
03249                 # Can't make a link to a namespace alone... "empty" local links can only be
03250                 # self-links with a fragment identifier.
03251                 if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
03252                         return false;
03253                 }
03254 
03255                 // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
03256                 // IP names are not allowed for accounts, and can only be referring to
03257                 // edits from the IP. Given '::' abbreviations and caps/lowercaps,
03258                 // there are numerous ways to present the same IP. Having sp:contribs scan
03259                 // them all is silly and having some show the edits and others not is
03260                 // inconsistent. Same for talk/userpages. Keep them normalized instead.
03261                 $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
03262                         ? IP::sanitizeIP( $dbkey )
03263                         : $dbkey;
03264 
03265                 // Any remaining initial :s are illegal.
03266                 if ( $dbkey !== '' && ':' == $dbkey[0] ) {
03267                         return false;
03268                 }
03269 
03270                 # Fill fields
03271                 $this->mDbkeyform = $dbkey;
03272                 $this->mUrlform = wfUrlencode( $dbkey );
03273 
03274                 $this->mTextform = str_replace( '_', ' ', $dbkey );
03275 
03276                 return true;
03277         }
03278 
03291         public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03292                 if ( count( $options ) > 0 ) {
03293                         $db = wfGetDB( DB_MASTER );
03294                 } else {
03295                         $db = wfGetDB( DB_SLAVE );
03296                 }
03297 
03298                 $res = $db->select(
03299                         array( 'page', $table ),
03300                         self::getSelectFields(),
03301                         array(
03302                                 "{$prefix}_from=page_id",
03303                                 "{$prefix}_namespace" => $this->getNamespace(),
03304                                 "{$prefix}_title"     => $this->getDBkey() ),
03305                         __METHOD__,
03306                         $options
03307                 );
03308 
03309                 $retVal = array();
03310                 if ( $res->numRows() ) {
03311                         $linkCache = LinkCache::singleton();
03312                         foreach ( $res as $row ) {
03313                                 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
03314                                 if ( $titleObj ) {
03315                                         $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03316                                         $retVal[] = $titleObj;
03317                                 }
03318                         }
03319                 }
03320                 return $retVal;
03321         }
03322 
03333         public function getTemplateLinksTo( $options = array() ) {
03334                 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
03335         }
03336 
03349         public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03350                 global $wgContentHandlerUseDB;
03351 
03352                 $id = $this->getArticleID();
03353 
03354                 # If the page doesn't exist; there can't be any link from this page
03355                 if ( !$id ) {
03356                         return array();
03357                 }
03358 
03359                 if ( count( $options ) > 0 ) {
03360                         $db = wfGetDB( DB_MASTER );
03361                 } else {
03362                         $db = wfGetDB( DB_SLAVE );
03363                 }
03364 
03365                 $namespaceFiled = "{$prefix}_namespace";
03366                 $titleField = "{$prefix}_title";
03367 
03368                 $fields = array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' );
03369                 if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
03370 
03371                 $res = $db->select(
03372                         array( $table, 'page' ),
03373                         $fields,
03374                         array( "{$prefix}_from" => $id ),
03375                         __METHOD__,
03376                         $options,
03377                         array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
03378                 );
03379 
03380                 $retVal = array();
03381                 if ( $res->numRows() ) {
03382                         $linkCache = LinkCache::singleton();
03383                         foreach ( $res as $row ) {
03384                                 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
03385                                 if ( $titleObj ) {
03386                                         if ( $row->page_id ) {
03387                                                 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03388                                         } else {
03389                                                 $linkCache->addBadLinkObj( $titleObj );
03390                                         }
03391                                         $retVal[] = $titleObj;
03392                                 }
03393                         }
03394                 }
03395                 return $retVal;
03396         }
03397 
03408         public function getTemplateLinksFrom( $options = array() ) {
03409                 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
03410         }
03411 
03418         public function getBrokenLinksFrom() {
03419                 if ( $this->getArticleID() == 0 ) {
03420                         # All links from article ID 0 are false positives
03421                         return array();
03422                 }
03423 
03424                 $dbr = wfGetDB( DB_SLAVE );
03425                 $res = $dbr->select(
03426                         array( 'page', 'pagelinks' ),
03427                         array( 'pl_namespace', 'pl_title' ),
03428                         array(
03429                                 'pl_from' => $this->getArticleID(),
03430                                 'page_namespace IS NULL'
03431                         ),
03432                         __METHOD__, array(),
03433                         array(
03434                                 'page' => array(
03435                                         'LEFT JOIN',
03436                                         array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
03437                                 )
03438                         )
03439                 );
03440 
03441                 $retVal = array();
03442                 foreach ( $res as $row ) {
03443                         $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
03444                 }
03445                 return $retVal;
03446         }
03447 
03454         public function getSquidURLs() {
03455                 $urls = array(
03456                         $this->getInternalURL(),
03457                         $this->getInternalURL( 'action=history' )
03458                 );
03459 
03460                 $pageLang = $this->getPageLanguage();
03461                 if ( $pageLang->hasVariants() ) {
03462                         $variants = $pageLang->getVariants();
03463                         foreach ( $variants as $vCode ) {
03464                                 $urls[] = $this->getInternalURL( '', $vCode );
03465                         }
03466                 }
03467 
03468                 return $urls;
03469         }
03470 
03474         public function purgeSquid() {
03475                 global $wgUseSquid;
03476                 if ( $wgUseSquid ) {
03477                         $urls = $this->getSquidURLs();
03478                         $u = new SquidUpdate( $urls );
03479                         $u->doUpdate();
03480                 }
03481         }
03482 
03489         public function moveNoAuth( &$nt ) {
03490                 return $this->moveTo( $nt, false );
03491         }
03492 
03503         public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
03504                 global $wgUser, $wgContentHandlerUseDB;
03505 
03506                 $errors = array();
03507                 if ( !$nt ) {
03508                         // Normally we'd add this to $errors, but we'll get
03509                         // lots of syntax errors if $nt is not an object
03510                         return array( array( 'badtitletext' ) );
03511                 }
03512                 if ( $this->equals( $nt ) ) {
03513                         $errors[] = array( 'selfmove' );
03514                 }
03515                 if ( !$this->isMovable() ) {
03516                         $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
03517                 }
03518                 if ( $nt->getInterwiki() != '' ) {
03519                         $errors[] = array( 'immobile-target-namespace-iw' );
03520                 }
03521                 if ( !$nt->isMovable() ) {
03522                         $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
03523                 }
03524 
03525                 $oldid = $this->getArticleID();
03526                 $newid = $nt->getArticleID();
03527 
03528                 if ( strlen( $nt->getDBkey() ) < 1 ) {
03529                         $errors[] = array( 'articleexists' );
03530                 }
03531                 if (
03532                         ( $this->getDBkey() == '' ) ||
03533                         ( !$oldid ) ||
03534                         ( $nt->getDBkey() == '' )
03535                 ) {
03536                         $errors[] = array( 'badarticleerror' );
03537                 }
03538 
03539                 // Content model checks
03540                 if ( !$wgContentHandlerUseDB &&
03541                                 $this->getContentModel() !== $nt->getContentModel() ) {
03542                         // can't move a page if that would change the page's content model
03543                         $errors[] = array(
03544                                 'bad-target-model',
03545                                 ContentHandler::getLocalizedName( $this->getContentModel() ),
03546                                 ContentHandler::getLocalizedName( $nt->getContentModel() )
03547                         );
03548                 }
03549 
03550                 // Image-specific checks
03551                 if ( $this->getNamespace() == NS_FILE ) {
03552                         $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
03553                 }
03554 
03555                 if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
03556                         $errors[] = array( 'nonfile-cannot-move-to-file' );
03557                 }
03558 
03559                 if ( $auth ) {
03560                         $errors = wfMergeErrorArrays( $errors,
03561                                 $this->getUserPermissionsErrors( 'move', $wgUser ),
03562                                 $this->getUserPermissionsErrors( 'edit', $wgUser ),
03563                                 $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
03564                                 $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
03565                 }
03566 
03567                 $match = EditPage::matchSummarySpamRegex( $reason );
03568                 if ( $match !== false ) {
03569                         // This is kind of lame, won't display nice
03570                         $errors[] = array( 'spamprotectiontext' );
03571                 }
03572 
03573                 $err = null;
03574                 if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
03575                         $errors[] = array( 'hookaborted', $err );
03576                 }
03577 
03578                 # The move is allowed only if (1) the target doesn't exist, or
03579                 # (2) the target is a redirect to the source, and has no history
03580                 # (so we can undo bad moves right after they're done).
03581 
03582                 if ( 0 != $newid ) { # Target exists; check for validity
03583                         if ( !$this->isValidMoveTarget( $nt ) ) {
03584                                 $errors[] = array( 'articleexists' );
03585                         }
03586                 } else {
03587                         $tp = $nt->getTitleProtection();
03588                         $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
03589                         if ( $tp and !$wgUser->isAllowed( $right ) ) {
03590                                 $errors[] = array( 'cantmove-titleprotected' );
03591                         }
03592                 }
03593                 if ( empty( $errors ) ) {
03594                         return true;
03595                 }
03596                 return $errors;
03597         }
03598 
03604         protected function validateFileMoveOperation( $nt ) {
03605                 global $wgUser;
03606 
03607                 $errors = array();
03608 
03609                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
03610 
03611                 $file = wfLocalFile( $this );
03612                 if ( $file->exists() ) {
03613                         if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
03614                                 $errors[] = array( 'imageinvalidfilename' );
03615                         }
03616                         if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
03617                                 $errors[] = array( 'imagetypemismatch' );
03618                         }
03619                 }
03620 
03621                 if ( $nt->getNamespace() != NS_FILE ) {
03622                         $errors[] = array( 'imagenocrossnamespace' );
03623                         // From here we want to do checks on a file object, so if we can't
03624                         // create one, we must return.
03625                         return $errors;
03626                 }
03627 
03628                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
03629 
03630                 $destFile = wfLocalFile( $nt );
03631                 if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
03632                         $errors[] = array( 'file-exists-sharedrepo' );
03633                 }
03634 
03635                 return $errors;
03636         }
03637 
03649         public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
03650                 global $wgUser;
03651                 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
03652                 if ( is_array( $err ) ) {
03653                         // Auto-block user's IP if the account was "hard" blocked
03654                         $wgUser->spreadAnyEditBlock();
03655                         return $err;
03656                 }
03657                 // Check suppressredirect permission
03658                 if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
03659                         $createRedirect = true;
03660                 }
03661 
03662                 // If it is a file, move it first.
03663                 // It is done before all other moving stuff is done because it's hard to revert.
03664                 $dbw = wfGetDB( DB_MASTER );
03665                 if ( $this->getNamespace() == NS_FILE ) {
03666                         $file = wfLocalFile( $this );
03667                         if ( $file->exists() ) {
03668                                 $status = $file->move( $nt );
03669                                 if ( !$status->isOk() ) {
03670                                         return $status->getErrorsArray();
03671                                 }
03672                         }
03673                         // Clear RepoGroup process cache
03674                         RepoGroup::singleton()->clearCache( $this );
03675                         RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
03676                 }
03677 
03678                 $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
03679                 $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
03680                 $protected = $this->isProtected();
03681 
03682                 // Do the actual move
03683                 $this->moveToInternal( $nt, $reason, $createRedirect );
03684 
03685                 // Refresh the sortkey for this row.  Be careful to avoid resetting
03686                 // cl_timestamp, which may disturb time-based lists on some sites.
03687                 $prefixes = $dbw->select(
03688                         'categorylinks',
03689                         array( 'cl_sortkey_prefix', 'cl_to' ),
03690                         array( 'cl_from' => $pageid ),
03691                         __METHOD__
03692                 );
03693                 foreach ( $prefixes as $prefixRow ) {
03694                         $prefix = $prefixRow->cl_sortkey_prefix;
03695                         $catTo = $prefixRow->cl_to;
03696                         $dbw->update( 'categorylinks',
03697                                 array(
03698                                         'cl_sortkey' => Collation::singleton()->getSortKey(
03699                                                 $nt->getCategorySortkey( $prefix ) ),
03700                                         'cl_timestamp=cl_timestamp' ),
03701                                 array(
03702                                         'cl_from' => $pageid,
03703                                         'cl_to' => $catTo ),
03704                                 __METHOD__
03705                         );
03706                 }
03707 
03708                 $redirid = $this->getArticleID();
03709 
03710                 if ( $protected ) {
03711                         # Protect the redirect title as the title used to be...
03712                         $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
03713                                 array(
03714                                         'pr_page'    => $redirid,
03715                                         'pr_type'    => 'pr_type',
03716                                         'pr_level'   => 'pr_level',
03717                                         'pr_cascade' => 'pr_cascade',
03718                                         'pr_user'    => 'pr_user',
03719                                         'pr_expiry'  => 'pr_expiry'
03720                                 ),
03721                                 array( 'pr_page' => $pageid ),
03722                                 __METHOD__,
03723                                 array( 'IGNORE' )
03724                         );
03725                         # Update the protection log
03726                         $log = new LogPage( 'protect' );
03727                         $comment = wfMessage(
03728                                 'prot_1movedto2',
03729                                 $this->getPrefixedText(),
03730                                 $nt->getPrefixedText()
03731                         )->inContentLanguage()->text();
03732                         if ( $reason ) {
03733                                 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03734                         }
03735                         // @todo FIXME: $params?
03736                         $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
03737                 }
03738 
03739                 # Update watchlists
03740                 $oldnamespace = MWNamespace::getSubject( $this->getNamespace() );
03741                 $newnamespace = MWNamespace::getSubject( $nt->getNamespace() );
03742                 $oldtitle = $this->getDBkey();
03743                 $newtitle = $nt->getDBkey();
03744 
03745                 if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
03746                         WatchedItem::duplicateEntries( $this, $nt );
03747                 }
03748 
03749                 $dbw->commit( __METHOD__ );
03750 
03751                 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
03752                 return true;
03753         }
03754 
03765         private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
03766                 global $wgUser, $wgContLang;
03767 
03768                 if ( $nt->exists() ) {
03769                         $moveOverRedirect = true;
03770                         $logType = 'move_redir';
03771                 } else {
03772                         $moveOverRedirect = false;
03773                         $logType = 'move';
03774                 }
03775 
03776                 if ( $createRedirect ) {
03777                         $contentHandler = ContentHandler::getForTitle( $this );
03778                         $redirectContent = $contentHandler->makeRedirectContent( $nt );
03779 
03780                         // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
03781                 } else {
03782                         $redirectContent = null;
03783                 }
03784 
03785                 $logEntry = new ManualLogEntry( 'move', $logType );
03786                 $logEntry->setPerformer( $wgUser );
03787                 $logEntry->setTarget( $this );
03788                 $logEntry->setComment( $reason );
03789                 $logEntry->setParameters( array(
03790                         '4::target' => $nt->getPrefixedText(),
03791                         '5::noredir' => $redirectContent ? '0': '1',
03792                 ) );
03793 
03794                 $formatter = LogFormatter::newFromEntry( $logEntry );
03795                 $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
03796                 $comment = $formatter->getPlainActionText();
03797                 if ( $reason ) {
03798                         $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
03799                 }
03800                 # Truncate for whole multibyte characters.
03801                 $comment = $wgContLang->truncate( $comment, 255 );
03802 
03803                 $oldid = $this->getArticleID();
03804 
03805                 $dbw = wfGetDB( DB_MASTER );
03806 
03807                 $newpage = WikiPage::factory( $nt );
03808 
03809                 if ( $moveOverRedirect ) {
03810                         $newid = $nt->getArticleID();
03811 
03812                         # Delete the old redirect. We don't save it to history since
03813                         # by definition if we've got here it's rather uninteresting.
03814                         # We have to remove it so that the next step doesn't trigger
03815                         # a conflict on the unique namespace+title index...
03816                         $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
03817 
03818                         $newpage->doDeleteUpdates( $newid );
03819                 }
03820 
03821                 # Save a null revision in the page's history notifying of the move
03822                 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
03823                 if ( !is_object( $nullRevision ) ) {
03824                         throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
03825                 }
03826 
03827                 $nullRevision->insertOn( $dbw );
03828 
03829                 # Change the name of the target page:
03830                 $dbw->update( 'page',
03831                         /* SET */ array(
03832                                 'page_namespace' => $nt->getNamespace(),
03833                                 'page_title'     => $nt->getDBkey(),
03834                         ),
03835                         /* WHERE */ array( 'page_id' => $oldid ),
03836                         __METHOD__
03837                 );
03838 
03839                 $this->resetArticleID( 0 );
03840                 $nt->resetArticleID( $oldid );
03841 
03842                 $newpage->updateRevisionOn( $dbw, $nullRevision );
03843 
03844                 wfRunHooks( 'NewRevisionFromEditComplete',
03845                         array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
03846 
03847                 $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
03848 
03849                 if ( !$moveOverRedirect ) {
03850                         WikiPage::onArticleCreate( $nt );
03851                 }
03852 
03853                 # Recreate the redirect, this time in the other direction.
03854                 if ( !$redirectContent ) {
03855                         WikiPage::onArticleDelete( $this );
03856                 } else {
03857                         $redirectArticle = WikiPage::factory( $this );
03858                         $newid = $redirectArticle->insertOn( $dbw );
03859                         if ( $newid ) { // sanity
03860                                 $redirectRevision = new Revision( array(
03861                                         'title'   => $this, // for determining the default content model
03862                                         'page'    => $newid,
03863                                         'comment' => $comment,
03864                                         'content'    => $redirectContent ) );
03865                                 $redirectRevision->insertOn( $dbw );
03866                                 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
03867 
03868                                 wfRunHooks( 'NewRevisionFromEditComplete',
03869                                         array( $redirectArticle, $redirectRevision, false, $wgUser ) );
03870 
03871                                 $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
03872                         }
03873                 }
03874 
03875                 # Log the move
03876                 $logid = $logEntry->insert();
03877                 $logEntry->publish( $logid );
03878         }
03879 
03892         public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03893                 global $wgMaximumMovedPages;
03894                 // Check permissions
03895                 if ( !$this->userCan( 'move-subpages' ) ) {
03896                         return array( 'cant-move-subpages' );
03897                 }
03898                 // Do the source and target namespaces support subpages?
03899                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03900                         return array( 'namespace-nosubpages',
03901                                 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03902                 }
03903                 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
03904                         return array( 'namespace-nosubpages',
03905                                 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03906                 }
03907 
03908                 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
03909                 $retval = array();
03910                 $count = 0;
03911                 foreach ( $subpages as $oldSubpage ) {
03912                         $count++;
03913                         if ( $count > $wgMaximumMovedPages ) {
03914                                 $retval[$oldSubpage->getPrefixedTitle()] =
03915                                                 array( 'movepage-max-pages',
03916                                                         $wgMaximumMovedPages );
03917                                 break;
03918                         }
03919 
03920                         // We don't know whether this function was called before
03921                         // or after moving the root page, so check both
03922                         // $this and $nt
03923                         if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
03924                                         $oldSubpage->getArticleID() == $nt->getArticleID() )
03925                         {
03926                                 // When moving a page to a subpage of itself,
03927                                 // don't move it twice
03928                                 continue;
03929                         }
03930                         $newPageName = preg_replace(
03931                                         '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
03932                                         StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03933                                         $oldSubpage->getDBkey() );
03934                         if ( $oldSubpage->isTalkPage() ) {
03935                                 $newNs = $nt->getTalkPage()->getNamespace();
03936                         } else {
03937                                 $newNs = $nt->getSubjectPage()->getNamespace();
03938                         }
03939                         # Bug 14385: we need makeTitleSafe because the new page names may
03940                         # be longer than 255 characters.
03941                         $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03942 
03943                         $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03944                         if ( $success === true ) {
03945                                 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03946                         } else {
03947                                 $retval[$oldSubpage->getPrefixedText()] = $success;
03948                         }
03949                 }
03950                 return $retval;
03951         }
03952 
03959         public function isSingleRevRedirect() {
03960                 global $wgContentHandlerUseDB;
03961 
03962                 $dbw = wfGetDB( DB_MASTER );
03963 
03964                 # Is it a redirect?
03965                 $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
03966                 if ( $wgContentHandlerUseDB ) $fields[] = 'page_content_model';
03967 
03968                 $row = $dbw->selectRow( 'page',
03969                         $fields,
03970                         $this->pageCond(),
03971                         __METHOD__,
03972                         array( 'FOR UPDATE' )
03973                 );
03974                 # Cache some fields we may want
03975                 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
03976                 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03977                 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
03978                 $this->mContentModel = $row && isset( $row->page_content_model ) ? strval( $row->page_content_model ) : false;
03979                 if ( !$this->mRedirect ) {
03980                         return false;
03981                 }
03982                 # Does the article have a history?
03983                 $row = $dbw->selectField( array( 'page', 'revision' ),
03984                         'rev_id',
03985                         array( 'page_namespace' => $this->getNamespace(),
03986                                 'page_title' => $this->getDBkey(),
03987                                 'page_id=rev_page',
03988                                 'page_latest != rev_id'
03989                         ),
03990                         __METHOD__,
03991                         array( 'FOR UPDATE' )
03992                 );
03993                 # Return true if there was no history
03994                 return ( $row === false );
03995         }
03996 
04004         public function isValidMoveTarget( $nt ) {
04005                 # Is it an existing file?
04006                 if ( $nt->getNamespace() == NS_FILE ) {
04007                         $file = wfLocalFile( $nt );
04008                         if ( $file->exists() ) {
04009                                 wfDebug( __METHOD__ . ": file exists\n" );
04010                                 return false;
04011                         }
04012                 }
04013                 # Is it a redirect with no history?
04014                 if ( !$nt->isSingleRevRedirect() ) {
04015                         wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
04016                         return false;
04017                 }
04018                 # Get the article text
04019                 $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
04020                 if( !is_object( $rev ) ) {
04021                         return false;
04022                 }
04023                 $content = $rev->getContent();
04024                 # Does the redirect point to the source?
04025                 # Or is it a broken self-redirect, usually caused by namespace collisions?
04026                 $redirTitle = $content ? $content->getRedirectTarget() : null;
04027 
04028                 if ( $redirTitle ) {
04029                         if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
04030                                 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
04031                                 wfDebug( __METHOD__ . ": redirect points to other page\n" );
04032                                 return false;
04033                         } else {
04034                                 return true;
04035                         }
04036                 } else {
04037                         # Fail safe (not a redirect after all. strange.)
04038                         wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
04039                                                 " is a redirect, but it doesn't contain a valid redirect.\n" );
04040                         return false;
04041                 }
04042         }
04043 
04051         public function getParentCategories() {
04052                 global $wgContLang;
04053 
04054                 $data = array();
04055 
04056                 $titleKey = $this->getArticleID();
04057 
04058                 if ( $titleKey === 0 ) {
04059                         return $data;
04060                 }
04061 
04062                 $dbr = wfGetDB( DB_SLAVE );
04063 
04064                 $res = $dbr->select(
04065                         'categorylinks',
04066                         'cl_to',
04067                         array( 'cl_from' => $titleKey ),
04068                         __METHOD__
04069                 );
04070 
04071                 if ( $res->numRows() > 0 ) {
04072                         foreach ( $res as $row ) {
04073                                 // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
04074                                 $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
04075                         }
04076                 }
04077                 return $data;
04078         }
04079 
04086         public function getParentCategoryTree( $children = array() ) {
04087                 $stack = array();
04088                 $parents = $this->getParentCategories();
04089 
04090                 if ( $parents ) {
04091                         foreach ( $parents as $parent => $current ) {
04092                                 if ( array_key_exists( $parent, $children ) ) {
04093                                         # Circular reference
04094                                         $stack[$parent] = array();
04095                                 } else {
04096                                         $nt = Title::newFromText( $parent );
04097                                         if ( $nt ) {
04098                                                 $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
04099                                         }
04100                                 }
04101                         }
04102                 }
04103 
04104                 return $stack;
04105         }
04106 
04113         public function pageCond() {
04114                 if ( $this->mArticleID > 0 ) {
04115                         // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
04116                         return array( 'page_id' => $this->mArticleID );
04117                 } else {
04118                         return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
04119                 }
04120         }
04121 
04129         public function getPreviousRevisionID( $revId, $flags = 0 ) {
04130                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04131                 $revId = $db->selectField( 'revision', 'rev_id',
04132                         array(
04133                                 'rev_page' => $this->getArticleID( $flags ),
04134                                 'rev_id < ' . intval( $revId )
04135                         ),
04136                         __METHOD__,
04137                         array( 'ORDER BY' => 'rev_id DESC' )
04138                 );
04139 
04140                 if ( $revId === false ) {
04141                         return false;
04142                 } else {
04143                         return intval( $revId );
04144                 }
04145         }
04146 
04154         public function getNextRevisionID( $revId, $flags = 0 ) {
04155                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04156                 $revId = $db->selectField( 'revision', 'rev_id',
04157                         array(
04158                                 'rev_page' => $this->getArticleID( $flags ),
04159                                 'rev_id > ' . intval( $revId )
04160                         ),
04161                         __METHOD__,
04162                         array( 'ORDER BY' => 'rev_id' )
04163                 );
04164 
04165                 if ( $revId === false ) {
04166                         return false;
04167                 } else {
04168                         return intval( $revId );
04169                 }
04170         }
04171 
04178         public function getFirstRevision( $flags = 0 ) {
04179                 $pageId = $this->getArticleID( $flags );
04180                 if ( $pageId ) {
04181                         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04182                         $row = $db->selectRow( 'revision', Revision::selectFields(),
04183                                 array( 'rev_page' => $pageId ),
04184                                 __METHOD__,
04185                                 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
04186                         );
04187                         if ( $row ) {
04188                                 return new Revision( $row );
04189                         }
04190                 }
04191                 return null;
04192         }
04193 
04200         public function getEarliestRevTime( $flags = 0 ) {
04201                 $rev = $this->getFirstRevision( $flags );
04202                 return $rev ? $rev->getTimestamp() : null;
04203         }
04204 
04210         public function isNewPage() {
04211                 $dbr = wfGetDB( DB_SLAVE );
04212                 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
04213         }
04214 
04220         public function isBigDeletion() {
04221                 global $wgDeleteRevisionsLimit;
04222 
04223                 if ( !$wgDeleteRevisionsLimit ) {
04224                         return false;
04225                 }
04226 
04227                 $revCount = $this->estimateRevisionCount();
04228                 return $revCount > $wgDeleteRevisionsLimit;
04229         }
04230 
04236         public function estimateRevisionCount() {
04237                 if ( !$this->exists() ) {
04238                         return 0;
04239                 }
04240 
04241                 if ( $this->mEstimateRevisions === null ) {
04242                         $dbr = wfGetDB( DB_SLAVE );
04243                         $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
04244                                 array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
04245                 }
04246 
04247                 return $this->mEstimateRevisions;
04248         }
04249 
04258         public function countRevisionsBetween( $old, $new ) {
04259                 if ( !( $old instanceof Revision ) ) {
04260                         $old = Revision::newFromTitle( $this, (int)$old );
04261                 }
04262                 if ( !( $new instanceof Revision ) ) {
04263                         $new = Revision::newFromTitle( $this, (int)$new );
04264                 }
04265                 if ( !$old || !$new ) {
04266                         return 0; // nothing to compare
04267                 }
04268                 $dbr = wfGetDB( DB_SLAVE );
04269                 return (int)$dbr->selectField( 'revision', 'count(*)',
04270                         array(
04271                                 'rev_page' => $this->getArticleID(),
04272                                 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04273                                 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04274                         ),
04275                         __METHOD__
04276                 );
04277         }
04278 
04293         public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
04294                 if ( !( $old instanceof Revision ) ) {
04295                         $old = Revision::newFromTitle( $this, (int)$old );
04296                 }
04297                 if ( !( $new instanceof Revision ) ) {
04298                         $new = Revision::newFromTitle( $this, (int)$new );
04299                 }
04300                 // XXX: what if Revision objects are passed in, but they don't refer to this title?
04301                 // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
04302                 // in the sanity check below?
04303                 if ( !$old || !$new ) {
04304                         return 0; // nothing to compare
04305                 }
04306                 $old_cmp = '>';
04307                 $new_cmp = '<';
04308                 $options = (array)$options;
04309                 if ( in_array( 'include_old', $options ) ) {
04310                         $old_cmp = '>=';
04311                 }
04312                 if ( in_array( 'include_new', $options ) ) {
04313                         $new_cmp = '<=';
04314                 }
04315                 if ( in_array( 'include_both', $options ) ) {
04316                         $old_cmp = '>=';
04317                         $new_cmp = '<=';
04318                 }
04319                 // No DB query needed if $old and $new are the same or successive revisions:
04320                 if ( $old->getId() === $new->getId() ) {
04321                         return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04322                 } else if ( $old->getId() === $new->getParentId() ) {
04323                         if ( $old_cmp === '>' || $new_cmp === '<' ) {
04324                                 return ( $old_cmp === '>' && $new_cmp === '<' ) ? 0 : 1;
04325                         }
04326                         return ( $old->getRawUserText() === $new->getRawUserText() ) ? 1 : 2;
04327                 }
04328                 $dbr = wfGetDB( DB_SLAVE );
04329                 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
04330                         array(
04331                                 'rev_page' => $this->getArticleID(),
04332                                 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04333                                 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04334                         ), __METHOD__,
04335                         array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
04336                 );
04337                 return (int)$dbr->numRows( $res );
04338         }
04339 
04346         public function equals( Title $title ) {
04347                 // Note: === is necessary for proper matching of number-like titles.
04348                 return $this->getInterwiki() === $title->getInterwiki()
04349                         && $this->getNamespace() == $title->getNamespace()
04350                         && $this->getDBkey() === $title->getDBkey();
04351         }
04352 
04359         public function isSubpageOf( Title $title ) {
04360                 return $this->getInterwiki() === $title->getInterwiki()
04361                         && $this->getNamespace() == $title->getNamespace()
04362                         && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
04363         }
04364 
04374         public function exists() {
04375                 return $this->getArticleID() != 0;
04376         }
04377 
04394         public function isAlwaysKnown() {
04395                 $isKnown = null;
04396 
04407                 wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
04408 
04409                 if ( !is_null( $isKnown ) ) {
04410                         return $isKnown;
04411                 }
04412 
04413                 if ( $this->mInterwiki != '' ) {
04414                         return true;  // any interwiki link might be viewable, for all we know
04415                 }
04416 
04417                 switch( $this->mNamespace ) {
04418                         case NS_MEDIA:
04419                         case NS_FILE:
04420                                 // file exists, possibly in a foreign repo
04421                                 return (bool)wfFindFile( $this );
04422                         case NS_SPECIAL:
04423                                 // valid special page
04424                                 return SpecialPageFactory::exists( $this->getDBkey() );
04425                         case NS_MAIN:
04426                                 // selflink, possibly with fragment
04427                                 return $this->mDbkeyform == '';
04428                         case NS_MEDIAWIKI:
04429                                 // known system message
04430                                 return $this->hasSourceText() !== false;
04431                         default:
04432                                 return false;
04433                 }
04434         }
04435 
04447         public function isKnown() {
04448                 return $this->isAlwaysKnown() || $this->exists();
04449         }
04450 
04456         public function hasSourceText() {
04457                 if ( $this->exists() ) {
04458                         return true;
04459                 }
04460 
04461                 if ( $this->mNamespace == NS_MEDIAWIKI ) {
04462                         // If the page doesn't exist but is a known system message, default
04463                         // message content will be displayed, same for language subpages-
04464                         // Use always content language to avoid loading hundreds of languages
04465                         // to get the link color.
04466                         global $wgContLang;
04467                         list( $name, ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04468                         $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
04469                         return $message->exists();
04470                 }
04471 
04472                 return false;
04473         }
04474 
04480         public function getDefaultMessageText() {
04481                 global $wgContLang;
04482 
04483                 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
04484                         return false;
04485                 }
04486 
04487                 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04488                 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
04489 
04490                 if ( $message->exists() ) {
04491                         return $message->plain();
04492                 } else {
04493                         return false;
04494                 }
04495         }
04496 
04502         public function invalidateCache() {
04503                 global $wgMemc;
04504                 if ( wfReadOnly() ) {
04505                         return false;
04506                 }
04507                 $dbw = wfGetDB( DB_MASTER );
04508                 $success = $dbw->update(
04509                         'page',
04510                         array( 'page_touched' => $dbw->timestamp() ),
04511                         $this->pageCond(),
04512                         __METHOD__
04513                 );
04514                 HTMLFileCache::clearFileCache( $this );
04515 
04516                 // Clear page info.
04517                 $revision = WikiPage::factory( $this )->getRevision();
04518                 if( $revision !== null ) {
04519                         $memcKey = wfMemcKey( 'infoaction', $this->getPrefixedText(), $revision->getId() );
04520                         $success = $success && $wgMemc->delete( $memcKey );
04521                 }
04522 
04523                 return $success;
04524         }
04525 
04531         public function touchLinks() {
04532                 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
04533                 $u->doUpdate();
04534 
04535                 if ( $this->getNamespace() == NS_CATEGORY ) {
04536                         $u = new HTMLCacheUpdate( $this, 'categorylinks' );
04537                         $u->doUpdate();
04538                 }
04539         }
04540 
04547         public function getTouched( $db = null ) {
04548                 $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
04549                 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
04550                 return $touched;
04551         }
04552 
04559         public function getNotificationTimestamp( $user = null ) {
04560                 global $wgUser, $wgShowUpdatedMarker;
04561                 // Assume current user if none given
04562                 if ( !$user ) {
04563                         $user = $wgUser;
04564                 }
04565                 // Check cache first
04566                 $uid = $user->getId();
04567                 // avoid isset here, as it'll return false for null entries
04568                 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
04569                         return $this->mNotificationTimestamp[$uid];
04570                 }
04571                 if ( !$uid || !$wgShowUpdatedMarker ) {
04572                         return $this->mNotificationTimestamp[$uid] = false;
04573                 }
04574                 // Don't cache too much!
04575                 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
04576                         $this->mNotificationTimestamp = array();
04577                 }
04578                 $dbr = wfGetDB( DB_SLAVE );
04579                 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
04580                         'wl_notificationtimestamp',
04581                         array(
04582                                 'wl_user' => $user->getId(),
04583                                 'wl_namespace' => $this->getNamespace(),
04584                                 'wl_title' => $this->getDBkey(),
04585                         ),
04586                         __METHOD__
04587                 );
04588                 return $this->mNotificationTimestamp[$uid];
04589         }
04590 
04597         public function getNamespaceKey( $prepend = 'nstab-' ) {
04598                 global $wgContLang;
04599                 // Gets the subject namespace if this title
04600                 $namespace = MWNamespace::getSubject( $this->getNamespace() );
04601                 // Checks if canonical namespace name exists for namespace
04602                 if ( MWNamespace::exists( $this->getNamespace() ) ) {
04603                         // Uses canonical namespace name
04604                         $namespaceKey = MWNamespace::getCanonicalName( $namespace );
04605                 } else {
04606                         // Uses text of namespace
04607                         $namespaceKey = $this->getSubjectNsText();
04608                 }
04609                 // Makes namespace key lowercase
04610                 $namespaceKey = $wgContLang->lc( $namespaceKey );
04611                 // Uses main
04612                 if ( $namespaceKey == '' ) {
04613                         $namespaceKey = 'main';
04614                 }
04615                 // Changes file to image for backwards compatibility
04616                 if ( $namespaceKey == 'file' ) {
04617                         $namespaceKey = 'image';
04618                 }
04619                 return $prepend . $namespaceKey;
04620         }
04621 
04628         public function getRedirectsHere( $ns = null ) {
04629                 $redirs = array();
04630 
04631                 $dbr = wfGetDB( DB_SLAVE );
04632                 $where = array(
04633                         'rd_namespace' => $this->getNamespace(),
04634                         'rd_title' => $this->getDBkey(),
04635                         'rd_from = page_id'
04636                 );
04637                 if ( $this->isExternal() ) {
04638                         $where['rd_interwiki'] = $this->getInterwiki();
04639                 } else {
04640                         $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
04641                 }
04642                 if ( !is_null( $ns ) ) {
04643                         $where['page_namespace'] = $ns;
04644                 }
04645 
04646                 $res = $dbr->select(
04647                         array( 'redirect', 'page' ),
04648                         array( 'page_namespace', 'page_title' ),
04649                         $where,
04650                         __METHOD__
04651                 );
04652 
04653                 foreach ( $res as $row ) {
04654                         $redirs[] = self::newFromRow( $row );
04655                 }
04656                 return $redirs;
04657         }
04658 
04664         public function isValidRedirectTarget() {
04665                 global $wgInvalidRedirectTargets;
04666 
04667                 // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
04668                 if ( $this->isSpecial( 'Userlogout' ) ) {
04669                         return false;
04670                 }
04671 
04672                 foreach ( $wgInvalidRedirectTargets as $target ) {
04673                         if ( $this->isSpecial( $target ) ) {
04674                                 return false;
04675                         }
04676                 }
04677 
04678                 return true;
04679         }
04680 
04686         public function getBacklinkCache() {
04687                 return BacklinkCache::get( $this );
04688         }
04689 
04695         public function canUseNoindex() {
04696                 global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
04697 
04698                 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
04699                         ? $wgContentNamespaces
04700                         : $wgExemptFromUserRobotsControl;
04701 
04702                 return !in_array( $this->mNamespace, $bannedNamespaces );
04703 
04704         }
04705 
04716         public function getCategorySortkey( $prefix = '' ) {
04717                 $unprefixed = $this->getText();
04718 
04719                 // Anything that uses this hook should only depend
04720                 // on the Title object passed in, and should probably
04721                 // tell the users to run updateCollations.php --force
04722                 // in order to re-sort existing category relations.
04723                 wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
04724                 if ( $prefix !== '' ) {
04725                         # Separate with a line feed, so the unprefixed part is only used as
04726                         # a tiebreaker when two pages have the exact same prefix.
04727                         # In UCA, tab is the only character that can sort above LF
04728                         # so we strip both of them from the original prefix.
04729                         $prefix = strtr( $prefix, "\n\t", '  ' );
04730                         return "$prefix\n$unprefixed";
04731                 }
04732                 return $unprefixed;
04733         }
04734 
04743         public function getPageLanguage() {
04744                 global $wgLang;
04745                 if ( $this->isSpecialPage() ) {
04746                         // special pages are in the user language
04747                         return $wgLang;
04748                 }
04749 
04750                 //TODO: use the LinkCache to cache this! Note that this may depend on user settings, so the cache should be only per-request.
04751                 //NOTE: ContentHandler::getPageLanguage() may need to load the content to determine the page language!
04752                 $contentHandler = ContentHandler::getForTitle( $this );
04753                 $pageLang = $contentHandler->getPageLanguage( $this );
04754 
04755                 return wfGetLangObj( $pageLang );
04756         }
04757 
04766         public function getPageViewLanguage() {
04767                 global $wgLang;
04768 
04769                 if ( $this->isSpecialPage() ) {
04770                         // If the user chooses a variant, the content is actually
04771                         // in a language whose code is the variant code.
04772                         $variant = $wgLang->getPreferredVariant();
04773                         if ( $wgLang->getCode() !== $variant ) {
04774                                 return Language::factory( $variant );
04775                         }
04776 
04777                         return $wgLang;
04778                 }
04779 
04780                 //NOTE: can't be cached persistently, depends on user settings
04781                 //NOTE: ContentHandler::getPageViewLanguage() may need to load the content to determine the page language!
04782                 $contentHandler = ContentHandler::getForTitle( $this );
04783                 $pageLang = $contentHandler->getPageViewLanguage( $this );
04784                 return $pageLang;
04785         }
04786 
04796         public function getEditNotices() {
04797                 $notices = array();
04798 
04799                 # Optional notices on a per-namespace and per-page basis
04800                 $editnotice_ns = 'editnotice-' . $this->getNamespace();
04801                 $editnotice_ns_message = wfMessage( $editnotice_ns );
04802                 if ( $editnotice_ns_message->exists() ) {
04803                         $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
04804                 }
04805                 if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
04806                         $parts = explode( '/', $this->getDBkey() );
04807                         $editnotice_base = $editnotice_ns;
04808                         while ( count( $parts ) > 0 ) {
04809                                 $editnotice_base .= '-' . array_shift( $parts );
04810                                 $editnotice_base_msg = wfMessage( $editnotice_base );
04811                                 if ( $editnotice_base_msg->exists() ) {
04812                                         $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
04813                                 }
04814                         }
04815                 } else {
04816                         # Even if there are no subpages in namespace, we still don't want / in MW ns.
04817                         $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
04818                         $editnoticeMsg = wfMessage( $editnoticeText );
04819                         if ( $editnoticeMsg->exists() ) {
04820                                 $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
04821                         }
04822                 }
04823                 return $notices;
04824         }
04825 }