MediaWiki  REL1_19
Title.php
Go to the documentation of this file.
00001 <?php
00031 class Title {
00033         // @{
00034         static private $titleCache = array();
00035         // @}
00036 
00042         const CACHE_MAX = 1000;
00043 
00048         const GAID_FOR_UPDATE = 1;
00049 
00055         // @{
00056 
00057         var $mTextform = '';              // /< Text form (spaces not underscores) of the main part
00058         var $mUrlform = '';               // /< URL-encoded form of the main part
00059         var $mDbkeyform = '';             // /< Main part with underscores
00060         var $mUserCaseDBKey;              // /< DB key with the initial letter in the case specified by the user
00061         var $mNamespace = NS_MAIN;        // /< Namespace index, i.e. one of the NS_xxxx constants
00062         var $mInterwiki = '';             // /< Interwiki prefix (or null string)
00063         var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
00064         var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
00065         var $mLatestID = false;           // /< ID of most recent revision
00066         private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
00067         var $mRestrictions = array();     // /< Array of groups allowed to edit this article
00068         var $mOldRestrictions = false;
00069         var $mCascadeRestriction;         
00070         var $mCascadingRestrictions;      // Caching the results of getCascadeProtectionSources
00071         var $mRestrictionsExpiry = array(); 
00072         var $mHasCascadingRestrictions;   
00073         var $mCascadeSources;             
00074         var $mRestrictionsLoaded = false; 
00075         var $mPrefixedText;               
00076         var $mTitleProtection;            
00077         # Don't change the following default, NS_MAIN is hardcoded in several
00078         # places.  See bug 696.
00079         var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
00080                                                                           # Zero except in {{transclusion}} tags
00081         var $mWatched = null;             // /< Is $wgUser watching this page? null if unfilled, accessed through userIsWatching()
00082         var $mLength = -1;                // /< The page length, 0 for special pages
00083         var $mRedirect = null;            // /< Is the article at this title a redirect?
00084         var $mNotificationTimestamp = array(); // /< Associative array of user ID -> timestamp/false
00085         var $mBacklinkCache = null;       // /< Cache of links to this title
00086         // @}
00087 
00088 
00092         /*protected*/ function __construct() { }
00093 
00102         public static function newFromDBkey( $key ) {
00103                 $t = new Title();
00104                 $t->mDbkeyform = $key;
00105                 if ( $t->secureAndSplit() ) {
00106                         return $t;
00107                 } else {
00108                         return null;
00109                 }
00110         }
00111 
00124         public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00125                 if ( is_object( $text ) ) {
00126                         throw new MWException( 'Title::newFromText given an object' );
00127                 }
00128 
00137                 if ( $defaultNamespace == NS_MAIN && isset( Title::$titleCache[$text] ) ) {
00138                         return Title::$titleCache[$text];
00139                 }
00140 
00141                 # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
00142                 $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
00143 
00144                 $t = new Title();
00145                 $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00146                 $t->mDefaultNamespace = $defaultNamespace;
00147 
00148                 static $cachedcount = 0 ;
00149                 if ( $t->secureAndSplit() ) {
00150                         if ( $defaultNamespace == NS_MAIN ) {
00151                                 if ( $cachedcount >= self::CACHE_MAX ) {
00152                                         # Avoid memory leaks on mass operations...
00153                                         Title::$titleCache = array();
00154                                         $cachedcount = 0;
00155                                 }
00156                                 $cachedcount++;
00157                                 Title::$titleCache[$text] =& $t;
00158                         }
00159                         return $t;
00160                 } else {
00161                         $ret = null;
00162                         return $ret;
00163                 }
00164         }
00165 
00181         public static function newFromURL( $url ) {
00182                 global $wgLegalTitleChars;
00183                 $t = new Title();
00184 
00185                 # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00186                 # but some URLs used it as a space replacement and they still come
00187                 # from some external search tools.
00188                 if ( strpos( $wgLegalTitleChars, '+' ) === false ) {
00189                         $url = str_replace( '+', ' ', $url );
00190                 }
00191 
00192                 $t->mDbkeyform = str_replace( ' ', '_', $url );
00193                 if ( $t->secureAndSplit() ) {
00194                         return $t;
00195                 } else {
00196                         return null;
00197                 }
00198         }
00199 
00207         public static function newFromID( $id, $flags = 0 ) {
00208                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00209                 $row = $db->selectRow( 'page', '*', array( 'page_id' => $id ), __METHOD__ );
00210                 if ( $row !== false ) {
00211                         $title = Title::newFromRow( $row );
00212                 } else {
00213                         $title = null;
00214                 }
00215                 return $title;
00216         }
00217 
00224         public static function newFromIDs( $ids ) {
00225                 if ( !count( $ids ) ) {
00226                         return array();
00227                 }
00228                 $dbr = wfGetDB( DB_SLAVE );
00229 
00230                 $res = $dbr->select(
00231                         'page',
00232                         array(
00233                                 'page_namespace', 'page_title', 'page_id',
00234                                 'page_len', 'page_is_redirect', 'page_latest',
00235                         ),
00236                         array( 'page_id' => $ids ),
00237                         __METHOD__
00238                 );
00239 
00240                 $titles = array();
00241                 foreach ( $res as $row ) {
00242                         $titles[] = Title::newFromRow( $row );
00243                 }
00244                 return $titles;
00245         }
00246 
00253         public static function newFromRow( $row ) {
00254                 $t = self::makeTitle( $row->page_namespace, $row->page_title );
00255                 $t->loadFromRow( $row );
00256                 return $t;
00257         }
00258 
00266         public function loadFromRow( $row ) {
00267                 if ( $row ) { // page found
00268                         if ( isset( $row->page_id ) )
00269                                 $this->mArticleID = (int)$row->page_id;
00270                         if ( isset( $row->page_len ) )
00271                                 $this->mLength = (int)$row->page_len;
00272                         if ( isset( $row->page_is_redirect ) )
00273                                 $this->mRedirect = (bool)$row->page_is_redirect;
00274                         if ( isset( $row->page_latest ) )
00275                                 $this->mLatestID = (int)$row->page_latest;
00276                 } else { // page not found
00277                         $this->mArticleID = 0;
00278                         $this->mLength = 0;
00279                         $this->mRedirect = false;
00280                         $this->mLatestID = 0;
00281                 }
00282         }
00283 
00297         public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
00298                 $t = new Title();
00299                 $t->mInterwiki = $interwiki;
00300                 $t->mFragment = $fragment;
00301                 $t->mNamespace = $ns = intval( $ns );
00302                 $t->mDbkeyform = str_replace( ' ', '_', $title );
00303                 $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00304                 $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00305                 $t->mTextform = str_replace( '_', ' ', $title );
00306                 return $t;
00307         }
00308 
00320         public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
00321                 $t = new Title();
00322                 $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
00323                 if ( $t->secureAndSplit() ) {
00324                         return $t;
00325                 } else {
00326                         return null;
00327                 }
00328         }
00329 
00335         public static function newMainPage() {
00336                 $title = Title::newFromText( wfMsgForContent( 'mainpage' ) );
00337                 // Don't give fatal errors if the message is broken
00338                 if ( !$title ) {
00339                         $title = Title::newFromText( 'Main Page' );
00340                 }
00341                 return $title;
00342         }
00343 
00353         public static function newFromRedirect( $text ) {
00354                 return self::newFromRedirectInternal( $text );
00355         }
00356 
00366         public static function newFromRedirectRecurse( $text ) {
00367                 $titles = self::newFromRedirectArray( $text );
00368                 return $titles ? array_pop( $titles ) : null;
00369         }
00370 
00380         public static function newFromRedirectArray( $text ) {
00381                 global $wgMaxRedirects;
00382                 $title = self::newFromRedirectInternal( $text );
00383                 if ( is_null( $title ) ) {
00384                         return null;
00385                 }
00386                 // recursive check to follow double redirects
00387                 $recurse = $wgMaxRedirects;
00388                 $titles = array( $title );
00389                 while ( --$recurse > 0 ) {
00390                         if ( $title->isRedirect() ) {
00391                                 $page = WikiPage::factory( $title );
00392                                 $newtitle = $page->getRedirectTarget();
00393                         } else {
00394                                 break;
00395                         }
00396                         // Redirects to some special pages are not permitted
00397                         if ( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
00398                                 // the new title passes the checks, so make that our current title so that further recursion can be checked
00399                                 $title = $newtitle;
00400                                 $titles[] = $newtitle;
00401                         } else {
00402                                 break;
00403                         }
00404                 }
00405                 return $titles;
00406         }
00407 
00415         protected static function newFromRedirectInternal( $text ) {
00416                 global $wgMaxRedirects;
00417                 if ( $wgMaxRedirects < 1 ) {
00418                         //redirects are disabled, so quit early
00419                         return null;
00420                 }
00421                 $redir = MagicWord::get( 'redirect' );
00422                 $text = trim( $text );
00423                 if ( $redir->matchStartAndRemove( $text ) ) {
00424                         // Extract the first link and see if it's usable
00425                         // Ensure that it really does come directly after #REDIRECT
00426                         // Some older redirects included a colon, so don't freak about that!
00427                         $m = array();
00428                         if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}!', $text, $m ) ) {
00429                                 // Strip preceding colon used to "escape" categories, etc.
00430                                 // and URL-decode links
00431                                 if ( strpos( $m[1], '%' ) !== false ) {
00432                                         // Match behavior of inline link parsing here;
00433                                         $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
00434                                 }
00435                                 $title = Title::newFromText( $m[1] );
00436                                 // If the title is a redirect to bad special pages or is invalid, return null
00437                                 if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
00438                                         return null;
00439                                 }
00440                                 return $title;
00441                         }
00442                 }
00443                 return null;
00444         }
00445 
00452         public static function nameOf( $id ) {
00453                 $dbr = wfGetDB( DB_SLAVE );
00454 
00455                 $s = $dbr->selectRow(
00456                         'page',
00457                         array( 'page_namespace', 'page_title' ),
00458                         array( 'page_id' => $id ),
00459                         __METHOD__
00460                 );
00461                 if ( $s === false ) {
00462                         return null;
00463                 }
00464 
00465                 $n = self::makeName( $s->page_namespace, $s->page_title );
00466                 return $n;
00467         }
00468 
00474         public static function legalChars() {
00475                 global $wgLegalTitleChars;
00476                 return $wgLegalTitleChars;
00477         }
00478 
00486         static function getTitleInvalidRegex() {
00487                 static $rxTc = false;
00488                 if ( !$rxTc ) {
00489                         # Matching titles will be held as illegal.
00490                         $rxTc = '/' .
00491                                 # Any character not allowed is forbidden...
00492                                 '[^' . self::legalChars() . ']' .
00493                                 # URL percent encoding sequences interfere with the ability
00494                                 # to round-trip titles -- you can't link to them consistently.
00495                                 '|%[0-9A-Fa-f]{2}' .
00496                                 # XML/HTML character references produce similar issues.
00497                                 '|&[A-Za-z0-9\x80-\xff]+;' .
00498                                 '|&#[0-9]+;' .
00499                                 '|&#x[0-9A-Fa-f]+;' .
00500                                 '/S';
00501                 }
00502 
00503                 return $rxTc;
00504         }
00505 
00514         public static function indexTitle( $ns, $title ) {
00515                 global $wgContLang;
00516 
00517                 $lc = SearchEngine::legalSearchChars() . '&#;';
00518                 $t = $wgContLang->normalizeForSearch( $title );
00519                 $t = preg_replace( "/[^{$lc}]+/", ' ', $t );
00520                 $t = $wgContLang->lc( $t );
00521 
00522                 # Handle 's, s'
00523                 $t = preg_replace( "/([{$lc}]+)'s( |$)/", "\\1 \\1's ", $t );
00524                 $t = preg_replace( "/([{$lc}]+)s'( |$)/", "\\1s ", $t );
00525 
00526                 $t = preg_replace( "/\\s+/", ' ', $t );
00527 
00528                 if ( $ns == NS_FILE ) {
00529                         $t = preg_replace( "/ (png|gif|jpg|jpeg|ogg)$/", "", $t );
00530                 }
00531                 return trim( $t );
00532         }
00533 
00543         public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
00544                 global $wgContLang;
00545 
00546                 $namespace = $wgContLang->getNsText( $ns );
00547                 $name = $namespace == '' ? $title : "$namespace:$title";
00548                 if ( strval( $interwiki ) != '' ) {
00549                         $name = "$interwiki:$name";
00550                 }
00551                 if ( strval( $fragment ) != '' ) {
00552                         $name .= '#' . $fragment;
00553                 }
00554                 return $name;
00555         }
00556 
00563         static function escapeFragmentForURL( $fragment ) {
00564                 # Note that we don't urlencode the fragment.  urlencoded Unicode
00565                 # fragments appear not to work in IE (at least up to 7) or in at least
00566                 # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
00567                 # to care if they aren't encoded.
00568                 return Sanitizer::escapeId( $fragment, 'noninitial' );
00569         }
00570 
00579         public static function compare( $a, $b ) {
00580                 if ( $a->getNamespace() == $b->getNamespace() ) {
00581                         return strcmp( $a->getText(), $b->getText() );
00582                 } else {
00583                         return $a->getNamespace() - $b->getNamespace();
00584                 }
00585         }
00586 
00593         public function isLocal() {
00594                 if ( $this->mInterwiki != '' ) {
00595                         return Interwiki::fetch( $this->mInterwiki )->isLocal();
00596                 } else {
00597                         return true;
00598                 }
00599         }
00600 
00606         public function isExternal() {
00607                 return ( $this->mInterwiki != '' );
00608         }
00609 
00615         public function getInterwiki() {
00616                 return $this->mInterwiki;
00617         }
00618 
00625         public function isTrans() {
00626                 if ( $this->mInterwiki == '' ) {
00627                         return false;
00628                 }
00629 
00630                 return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00631         }
00632 
00638         public function getTransWikiID() {
00639                 if ( $this->mInterwiki == '' ) {
00640                         return false;
00641                 }
00642 
00643                 return Interwiki::fetch( $this->mInterwiki )->getWikiID();
00644         }
00645 
00651         public function getText() {
00652                 return $this->mTextform;
00653         }
00654 
00660         public function getPartialURL() {
00661                 return $this->mUrlform;
00662         }
00663 
00669         public function getDBkey() {
00670                 return $this->mDbkeyform;
00671         }
00672 
00678         function getUserCaseDBKey() {
00679                 return $this->mUserCaseDBKey;
00680         }
00681 
00687         public function getNamespace() {
00688                 return $this->mNamespace;
00689         }
00690 
00696         public function getNsText() {
00697                 global $wgContLang;
00698 
00699                 if ( $this->mInterwiki != '' ) {
00700                         // This probably shouldn't even happen. ohh man, oh yuck.
00701                         // But for interwiki transclusion it sometimes does.
00702                         // Shit. Shit shit shit.
00703                         //
00704                         // Use the canonical namespaces if possible to try to
00705                         // resolve a foreign namespace.
00706                         if ( MWNamespace::exists( $this->mNamespace ) ) {
00707                                 return MWNamespace::getCanonicalName( $this->mNamespace );
00708                         }
00709                 }
00710 
00711                 // Strip off subpages
00712                 $pagename = $this->getText();
00713                 if ( strpos( $pagename, '/' ) !== false ) {
00714                         list( $username , ) = explode( '/', $pagename, 2 );
00715                 } else {
00716                         $username = $pagename;
00717                 }
00718 
00719                 if ( $wgContLang->needsGenderDistinction() &&
00720                                 MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
00721                         $gender = GenderCache::singleton()->getGenderOf( $username, __METHOD__ );
00722                         return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
00723                 }
00724 
00725                 return $wgContLang->getNsText( $this->mNamespace );
00726         }
00727 
00733         public function getSubjectNsText() {
00734                 global $wgContLang;
00735                 return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
00736         }
00737 
00743         public function getTalkNsText() {
00744                 global $wgContLang;
00745                 return( $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) ) );
00746         }
00747 
00753         public function canTalk() {
00754                 return( MWNamespace::canTalk( $this->mNamespace ) );
00755         }
00756 
00763         public function canExist() {
00764                 return $this->mNamespace >= NS_MAIN;
00765         }
00766 
00772         public function isWatchable() {
00773                 return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
00774         }
00775 
00781         public function isSpecialPage() {
00782                 return $this->getNamespace() == NS_SPECIAL;
00783         }
00784 
00791         public function isSpecial( $name ) {
00792                 if ( $this->isSpecialPage() ) {
00793                         list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
00794                         if ( $name == $thisName ) {
00795                                 return true;
00796                         }
00797                 }
00798                 return false;
00799         }
00800 
00807         public function fixSpecialName() {
00808                 if ( $this->isSpecialPage() ) {
00809                         list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
00810                         if ( $canonicalName ) {
00811                                 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
00812                                 if ( $localName != $this->mDbkeyform ) {
00813                                         return Title::makeTitle( NS_SPECIAL, $localName );
00814                                 }
00815                         }
00816                 }
00817                 return $this;
00818         }
00819 
00830         public function inNamespace( $ns ) {
00831                 return MWNamespace::equals( $this->getNamespace(), $ns );
00832         }
00833 
00841         public function inNamespaces( /* ... */ ) {
00842                 $namespaces = func_get_args();
00843                 if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
00844                         $namespaces = $namespaces[0];
00845                 }
00846 
00847                 foreach ( $namespaces as $ns ) {
00848                         if ( $this->inNamespace( $ns ) ) {
00849                                 return true;
00850                         }
00851                 }
00852 
00853                 return false;
00854         }
00855 
00867         public function hasSubjectNamespace( $ns ) {
00868                 return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
00869         }
00870 
00878         public function isContentPage() {
00879                 return MWNamespace::isContent( $this->getNamespace() );
00880         }
00881 
00888         public function isMovable() {
00889                 if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
00890                         // Interwiki title or immovable namespace. Hooks don't get to override here
00891                         return false;
00892                 }
00893 
00894                 $result = true;
00895                 wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
00896                 return $result;
00897         }
00898 
00909         public function isMainPage() {
00910                 return $this->equals( Title::newMainPage() );
00911         }
00912 
00918         public function isSubpage() {
00919                 return MWNamespace::hasSubpages( $this->mNamespace )
00920                         ? strpos( $this->getText(), '/' ) !== false
00921                         : false;
00922         }
00923 
00929         public function isConversionTable() {
00930                 return $this->getNamespace() == NS_MEDIAWIKI &&
00931                         strpos( $this->getText(), 'Conversiontable' ) !== false;
00932         }
00933 
00939         public function isWikitextPage() {
00940                 $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
00941                 wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
00942                 return $retval;
00943         }
00944 
00951         public function isCssOrJsPage() {
00952                 $retval = $this->mNamespace == NS_MEDIAWIKI
00953                         && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
00954                 wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
00955                 return $retval;
00956         }
00957 
00962         public function isCssJsSubpage() {
00963                 return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
00964         }
00965 
00971         public function getSkinFromCssJsSubpage() {
00972                 $subpage = explode( '/', $this->mTextform );
00973                 $subpage = $subpage[ count( $subpage ) - 1 ];
00974                 $lastdot = strrpos( $subpage, '.' );
00975                 if ( $lastdot === false )
00976                         return $subpage; # Never happens: only called for names ending in '.css' or '.js'
00977                 return substr( $subpage, 0, $lastdot );
00978         }
00979 
00985         public function isCssSubpage() {
00986                 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
00987         }
00988 
00994         public function isJsSubpage() {
00995                 return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
00996         }
00997 
01003         public function isTalkPage() {
01004                 return MWNamespace::isTalk( $this->getNamespace() );
01005         }
01006 
01012         public function getTalkPage() {
01013                 return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
01014         }
01015 
01022         public function getSubjectPage() {
01023                 // Is this the same title?
01024                 $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
01025                 if ( $this->getNamespace() == $subjectNS ) {
01026                         return $this;
01027                 }
01028                 return Title::makeTitle( $subjectNS, $this->getDBkey() );
01029         }
01030 
01036         public function getDefaultNamespace() {
01037                 return $this->mDefaultNamespace;
01038         }
01039 
01046         public function getIndexTitle() {
01047                 return Title::indexTitle( $this->mNamespace, $this->mTextform );
01048         }
01049 
01055         public function getFragment() {
01056                 return $this->mFragment;
01057         }
01058 
01063         public function getFragmentForURL() {
01064                 if ( $this->mFragment == '' ) {
01065                         return '';
01066                 } else {
01067                         return '#' . Title::escapeFragmentForURL( $this->mFragment );
01068                 }
01069         }
01070 
01081         public function setFragment( $fragment ) {
01082                 $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
01083         }
01084 
01093         private function prefix( $name ) {
01094                 $p = '';
01095                 if ( $this->mInterwiki != '' ) {
01096                         $p = $this->mInterwiki . ':';
01097                 }
01098 
01099                 if ( 0 != $this->mNamespace ) {
01100                         $p .= $this->getNsText() . ':';
01101                 }
01102                 return $p . $name;
01103         }
01104 
01111         public function getPrefixedDBkey() {
01112                 $s = $this->prefix( $this->mDbkeyform );
01113                 $s = str_replace( ' ', '_', $s );
01114                 return $s;
01115         }
01116 
01123         public function getPrefixedText() {
01124                 // @todo FIXME: Bad usage of empty() ?
01125                 if ( empty( $this->mPrefixedText ) ) {
01126                         $s = $this->prefix( $this->mTextform );
01127                         $s = str_replace( '_', ' ', $s );
01128                         $this->mPrefixedText = $s;
01129                 }
01130                 return $this->mPrefixedText;
01131         }
01132 
01138         public function __toString() {
01139                 return $this->getPrefixedText();
01140         }
01141 
01148         public function getFullText() {
01149                 $text = $this->getPrefixedText();
01150                 if ( $this->mFragment != '' ) {
01151                         $text .= '#' . $this->mFragment;
01152                 }
01153                 return $text;
01154         }
01155 
01161         public function getBaseText() {
01162                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01163                         return $this->getText();
01164                 }
01165 
01166                 $parts = explode( '/', $this->getText() );
01167                 # Don't discard the real title if there's no subpage involved
01168                 if ( count( $parts ) > 1 ) {
01169                         unset( $parts[count( $parts ) - 1] );
01170                 }
01171                 return implode( '/', $parts );
01172         }
01173 
01179         public function getSubpageText() {
01180                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01181                         return( $this->mTextform );
01182                 }
01183                 $parts = explode( '/', $this->mTextform );
01184                 return( $parts[count( $parts ) - 1] );
01185         }
01186 
01193         public function getEscapedText() {
01194                 wfDeprecated( __METHOD__, '1.19' );
01195                 return htmlspecialchars( $this->getPrefixedText() );
01196         }
01197 
01203         public function getSubpageUrlForm() {
01204                 $text = $this->getSubpageText();
01205                 $text = wfUrlencode( str_replace( ' ', '_', $text ) );
01206                 return( $text );
01207         }
01208 
01214         public function getPrefixedURL() {
01215                 $s = $this->prefix( $this->mDbkeyform );
01216                 $s = wfUrlencode( str_replace( ' ', '_', $s ) );
01217                 return $s;
01218         }
01219 
01230         private static function fixUrlQueryArgs( $query, $query2 = false ) {
01231                 if( $query2 !== false ) {
01232                         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" );
01233                 }
01234                 if ( is_array( $query ) ) {
01235                         $query = wfArrayToCGI( $query );
01236                 }
01237                 if ( $query2 ) {
01238                         if ( is_string( $query2 ) ) {
01239                                 // $query2 is a string, we will consider this to be
01240                                 // a deprecated $variant argument and add it to the query
01241                                 $query2 = wfArrayToCGI( array( 'variant' => $query2 ) );
01242                         } else {
01243                                 $query2 = wfArrayToCGI( $query2 );
01244                         }
01245                         // If we have $query content add a & to it first
01246                         if ( $query ) {
01247                                 $query .= '&';
01248                         }
01249                         // Now append the queries together
01250                         $query .= $query2;
01251                 }
01252                 return $query;
01253         }
01254 
01264         public function getFullURL( $query = '', $query2 = false ) {
01265                 $query = self::fixUrlQueryArgs( $query, $query2 );
01266 
01267                 # Hand off all the decisions on urls to getLocalURL
01268                 $url = $this->getLocalURL( $query );
01269 
01270                 # Expand the url to make it a full url. Note that getLocalURL has the
01271                 # potential to output full urls for a variety of reasons, so we use
01272                 # wfExpandUrl instead of simply prepending $wgServer
01273                 $url = wfExpandUrl( $url, PROTO_RELATIVE );
01274 
01275                 # Finally, add the fragment.
01276                 $url .= $this->getFragmentForURL();
01277 
01278                 wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
01279                 return $url;
01280         }
01281 
01300         public function getLocalURL( $query = '', $query2 = false ) {
01301                 global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
01302 
01303                 $query = self::fixUrlQueryArgs( $query, $query2 );
01304 
01305                 $interwiki = Interwiki::fetch( $this->mInterwiki );
01306                 if ( $interwiki ) {
01307                         $namespace = $this->getNsText();
01308                         if ( $namespace != '' ) {
01309                                 # Can this actually happen? Interwikis shouldn't be parsed.
01310                                 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
01311                                 $namespace .= ':';
01312                         }
01313                         $url = $interwiki->getURL( $namespace . $this->getDBkey() );
01314                         $url = wfAppendQuery( $url, $query );
01315                 } else {
01316                         $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
01317                         if ( $query == '' ) {
01318                                 $url = str_replace( '$1', $dbkey, $wgArticlePath );
01319                                 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
01320                         } else {
01321                                 global $wgVariantArticlePath, $wgActionPaths;
01322                                 $url = false;
01323                                 $matches = array();
01324 
01325                                 if ( !empty( $wgActionPaths ) &&
01326                                         preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
01327                                 {
01328                                         $action = urldecode( $matches[2] );
01329                                         if ( isset( $wgActionPaths[$action] ) ) {
01330                                                 $query = $matches[1];
01331                                                 if ( isset( $matches[4] ) ) {
01332                                                         $query .= $matches[4];
01333                                                 }
01334                                                 $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
01335                                                 if ( $query != '' ) {
01336                                                         $url = wfAppendQuery( $url, $query );
01337                                                 }
01338                                         }
01339                                 }
01340 
01341                                 if ( $url === false &&
01342                                         $wgVariantArticlePath &&
01343                                         $this->getPageLanguage()->hasVariants() &&
01344                                         preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
01345                                 {
01346                                         $variant = urldecode( $matches[1] );
01347                                         if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
01348                                                 // Only do the variant replacement if the given variant is a valid
01349                                                 // variant for the page's language.
01350                                                 $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
01351                                                 $url = str_replace( '$1', $dbkey, $url );
01352                                         }
01353                                 }
01354 
01355                                 if ( $url === false ) {
01356                                         if ( $query == '-' ) {
01357                                                 $query = '';
01358                                         }
01359                                         $url = "{$wgScript}?title={$dbkey}&{$query}";
01360                                 }
01361                         }
01362 
01363                         wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
01364 
01365                         // @todo FIXME: This causes breakage in various places when we
01366                         // actually expected a local URL and end up with dupe prefixes.
01367                         if ( $wgRequest->getVal( 'action' ) == 'render' ) {
01368                                 $url = $wgServer . $url;
01369                         }
01370                 }
01371                 wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
01372                 return $url;
01373         }
01374 
01390         public function getLinkURL( $query = '', $query2 = false ) {
01391                 wfProfileIn( __METHOD__ );
01392                 if ( $this->isExternal() ) {
01393                         $ret = $this->getFullURL( $query, $query2 );
01394                 } elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
01395                         $ret = $this->getFragmentForURL();
01396                 } else {
01397                         $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
01398                 }
01399                 wfProfileOut( __METHOD__ );
01400                 return $ret;
01401         }
01402 
01412         public function escapeLocalURL( $query = '', $query2 = false ) {
01413                 wfDeprecated( __METHOD__, '1.19' );
01414                 return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
01415         }
01416 
01426         public function escapeFullURL( $query = '', $query2 = false ) {
01427                 wfDeprecated( __METHOD__, '1.19' );
01428                 return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
01429         }
01430 
01445         public function getInternalURL( $query = '', $query2 = false ) {
01446                 global $wgInternalServer, $wgServer;
01447                 $query = self::fixUrlQueryArgs( $query, $query2 );
01448                 $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
01449                 $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
01450                 wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
01451                 return $url;
01452         }
01453 
01467         public function getCanonicalURL( $query = '', $query2 = false ) {
01468                 $query = self::fixUrlQueryArgs( $query, $query2 );
01469                 $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
01470                 wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
01471                 return $url;
01472         }
01473 
01482         public function escapeCanonicalURL( $query = '', $query2 = false ) {
01483                 wfDeprecated( __METHOD__, '1.19' );
01484                 return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
01485         }
01486 
01493         public function getEditURL() {
01494                 if ( $this->mInterwiki != '' ) {
01495                         return '';
01496                 }
01497                 $s = $this->getLocalURL( 'action=edit' );
01498 
01499                 return $s;
01500         }
01501 
01507         public function userIsWatching() {
01508                 global $wgUser;
01509 
01510                 if ( is_null( $this->mWatched ) ) {
01511                         if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
01512                                 $this->mWatched = false;
01513                         } else {
01514                                 $this->mWatched = $wgUser->isWatched( $this );
01515                         }
01516                 }
01517                 return $this->mWatched;
01518         }
01519 
01527         public function userCanRead() {
01528                 wfDeprecated( __METHOD__, '1.19' );
01529                 return $this->userCan( 'read' );
01530         }
01531 
01547         public function quickUserCan( $action, $user = null ) {
01548                 return $this->userCan( $action, $user, false );
01549         }
01550 
01561         public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
01562                 if ( !$user instanceof User ) {
01563                         global $wgUser;
01564                         $user = $wgUser;
01565                 }
01566                 return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
01567         }
01568 
01582         public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
01583                 $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01584 
01585                 // Remove the errors being ignored.
01586                 foreach ( $errors as $index => $error ) {
01587                         $error_key = is_array( $error ) ? $error[0] : $error;
01588 
01589                         if ( in_array( $error_key, $ignoreErrors ) ) {
01590                                 unset( $errors[$index] );
01591                         }
01592                 }
01593 
01594                 return $errors;
01595         }
01596 
01608         private function checkQuickPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01609                 if ( $action == 'create' ) {
01610                         if ( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01611                                  ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) {
01612                                 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
01613                         }
01614                 } elseif ( $action == 'move' ) {
01615                         if ( !$user->isAllowed( 'move-rootuserpages' )
01616                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01617                                 // Show user page-specific message only if the user can move other pages
01618                                 $errors[] = array( 'cant-move-user-page' );
01619                         }
01620 
01621                         // Check if user is allowed to move files if it's a file
01622                         if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01623                                 $errors[] = array( 'movenotallowedfile' );
01624                         }
01625 
01626                         if ( !$user->isAllowed( 'move' ) ) {
01627                                 // User can't move anything
01628                                 global $wgGroupPermissions;
01629                                 $userCanMove = false;
01630                                 if ( isset( $wgGroupPermissions['user']['move'] ) ) {
01631                                         $userCanMove = $wgGroupPermissions['user']['move'];
01632                                 }
01633                                 $autoconfirmedCanMove = false;
01634                                 if ( isset( $wgGroupPermissions['autoconfirmed']['move'] ) ) {
01635                                         $autoconfirmedCanMove = $wgGroupPermissions['autoconfirmed']['move'];
01636                                 }
01637                                 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01638                                         // custom message if logged-in users without any special rights can move
01639                                         $errors[] = array( 'movenologintext' );
01640                                 } else {
01641                                         $errors[] = array( 'movenotallowed' );
01642                                 }
01643                         }
01644                 } elseif ( $action == 'move-target' ) {
01645                         if ( !$user->isAllowed( 'move' ) ) {
01646                                 // User can't move anything
01647                                 $errors[] = array( 'movenotallowed' );
01648                         } elseif ( !$user->isAllowed( 'move-rootuserpages' )
01649                                         && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01650                                 // Show user page-specific message only if the user can move other pages
01651                                 $errors[] = array( 'cant-move-to-user-page' );
01652                         }
01653                 } elseif ( !$user->isAllowed( $action ) ) {
01654                         $errors[] = $this->missingPermissionError( $action, $short );
01655                 }
01656 
01657                 return $errors;
01658         }
01659 
01668         private function resultToError( $errors, $result ) {
01669                 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
01670                         // A single array representing an error
01671                         $errors[] = $result;
01672                 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
01673                         // A nested array representing multiple errors
01674                         $errors = array_merge( $errors, $result );
01675                 } elseif ( $result !== '' && is_string( $result ) ) {
01676                         // A string representing a message-id
01677                         $errors[] = array( $result );
01678                 } elseif ( $result === false ) {
01679                         // a generic "We don't want them to do that"
01680                         $errors[] = array( 'badaccess-group0' );
01681                 }
01682                 return $errors;
01683         }
01684 
01696         private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
01697                 // Use getUserPermissionsErrors instead
01698                 $result = '';
01699                 if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
01700                         return $result ? array() : array( array( 'badaccess-group0' ) );
01701                 }
01702                 // Check getUserPermissionsErrors hook
01703                 if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
01704                         $errors = $this->resultToError( $errors, $result );
01705                 }
01706                 // Check getUserPermissionsErrorsExpensive hook
01707                 if ( $doExpensiveQueries && !( $short && count( $errors ) > 0 ) &&
01708                          !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) ) ) {
01709                         $errors = $this->resultToError( $errors, $result );
01710                 }
01711 
01712                 return $errors;
01713         }
01714 
01726         private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01727                 # Only 'createaccount' and 'execute' can be performed on
01728                 # special pages, which don't actually exist in the DB.
01729                 $specialOKActions = array( 'createaccount', 'execute', 'read' );
01730                 if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
01731                         $errors[] = array( 'ns-specialprotected' );
01732                 }
01733 
01734                 # Check $wgNamespaceProtection for restricted namespaces
01735                 if ( $this->isNamespaceProtected( $user ) ) {
01736                         $ns = $this->mNamespace == NS_MAIN ?
01737                                 wfMsg( 'nstab-main' ) : $this->getNsText();
01738                         $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
01739                                 array( 'protectedinterface' ) : array( 'namespaceprotected',  $ns );
01740                 }
01741 
01742                 return $errors;
01743         }
01744 
01756         private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01757                 # Protect css/js subpages of user pages
01758                 # XXX: this might be better using restrictions
01759                 # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
01760                 if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
01761                                 && !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
01762                         if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
01763                                 $errors[] = array( 'customcssprotected' );
01764                         } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
01765                                 $errors[] = array( 'customjsprotected' );
01766                         }
01767                 }
01768 
01769                 return $errors;
01770         }
01771 
01785         private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01786                 foreach ( $this->getRestrictions( $action ) as $right ) {
01787                         // Backwards compatibility, rewrite sysop -> protect
01788                         if ( $right == 'sysop' ) {
01789                                 $right = 'protect';
01790                         }
01791                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01792                                 // Users with 'editprotected' permission can edit protected pages
01793                                 // without cascading option turned on.
01794                                 if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
01795                                         || $this->mCascadeRestriction )
01796                                 {
01797                                         $errors[] = array( 'protectedpagetext', $right );
01798                                 }
01799                         }
01800                 }
01801 
01802                 return $errors;
01803         }
01804 
01816         private function checkCascadingSourcesRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01817                 if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
01818                         # We /could/ use the protection level on the source page, but it's
01819                         # fairly ugly as we have to establish a precedence hierarchy for pages
01820                         # included by multiple cascade-protected pages. So just restrict
01821                         # it to people with 'protect' permission, as they could remove the
01822                         # protection anyway.
01823                         list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
01824                         # Cascading protection depends on more than this page...
01825                         # Several cascading protected pages may include this page...
01826                         # Check each cascading level
01827                         # This is only for protection restrictions, not for all actions
01828                         if ( isset( $restrictions[$action] ) ) {
01829                                 foreach ( $restrictions[$action] as $right ) {
01830                                         $right = ( $right == 'sysop' ) ? 'protect' : $right;
01831                                         if ( $right != '' && !$user->isAllowed( $right ) ) {
01832                                                 $pages = '';
01833                                                 foreach ( $cascadingSources as $page )
01834                                                         $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
01835                                                 $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages );
01836                                         }
01837                                 }
01838                         }
01839                 }
01840 
01841                 return $errors;
01842         }
01843 
01855         private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01856                 global $wgDeleteRevisionsLimit, $wgLang;
01857 
01858                 if ( $action == 'protect' ) {
01859                         if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
01860                                 // If they can't edit, they shouldn't protect.
01861                                 $errors[] = array( 'protect-cantedit' );
01862                         }
01863                 } elseif ( $action == 'create' ) {
01864                         $title_protection = $this->getTitleProtection();
01865                         if( $title_protection ) {
01866                                 if( $title_protection['pt_create_perm'] == 'sysop' ) {
01867                                         $title_protection['pt_create_perm'] = 'protect'; // B/C
01868                                 }
01869                                 if( $title_protection['pt_create_perm'] == '' ||
01870                                         !$user->isAllowed( $title_protection['pt_create_perm'] ) ) 
01871                                 {
01872                                         $errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
01873                                 }
01874                         }
01875                 } elseif ( $action == 'move' ) {
01876                         // Check for immobile pages
01877                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01878                                 // Specific message for this case
01879                                 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
01880                         } elseif ( !$this->isMovable() ) {
01881                                 // Less specific message for rarer cases
01882                                 $errors[] = array( 'immobile-source-page' );
01883                         }
01884                 } elseif ( $action == 'move-target' ) {
01885                         if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
01886                                 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
01887                         } elseif ( !$this->isMovable() ) {
01888                                 $errors[] = array( 'immobile-target-page' );
01889                         }
01890                 } elseif ( $action == 'delete' ) {
01891                         if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
01892                                 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
01893                         {
01894                                 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
01895                         }
01896                 }
01897                 return $errors;
01898         }
01899 
01911         private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
01912                 // Account creation blocks handled at userlogin.
01913                 // Unblocking handled in SpecialUnblock
01914                 if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
01915                         return $errors;
01916                 }
01917 
01918                 global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
01919 
01920                 if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
01921                         $errors[] = array( 'confirmedittext' );
01922                 }
01923 
01924                 if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
01925                         // Don't block the user from editing their own talk page unless they've been
01926                         // explicitly blocked from that too.
01927                 } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
01928                         $block = $user->mBlock;
01929 
01930                         // This is from OutputPage::blockedPage
01931                         // Copied at r23888 by werdna
01932 
01933                         $id = $user->blockedBy();
01934                         $reason = $user->blockedFor();
01935                         if ( $reason == '' ) {
01936                                 $reason = wfMsg( 'blockednoreason' );
01937                         }
01938                         $ip = $user->getRequest()->getIP();
01939 
01940                         if ( is_numeric( $id ) ) {
01941                                 $name = User::whoIs( $id );
01942                         } else {
01943                                 $name = $id;
01944                         }
01945 
01946                         $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
01947                         $blockid = $block->getId();
01948                         $blockExpiry = $user->mBlock->mExpiry;
01949                         $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
01950                         if ( $blockExpiry == 'infinity' ) {
01951                                 $blockExpiry = wfMessage( 'infiniteblock' )->text();
01952                         } else {
01953                                 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
01954                         }
01955 
01956                         $intended = strval( $user->mBlock->getTarget() );
01957 
01958                         $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
01959                                 $blockid, $blockExpiry, $intended, $blockTimestamp );
01960                 }
01961 
01962                 return $errors;
01963         }
01964 
01976         private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
01977                 global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
01978                 static $useShortcut = null;
01979 
01980                 # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
01981                 if ( is_null( $useShortcut ) ) {
01982                         $useShortcut = true;
01983                         if ( empty( $wgGroupPermissions['*']['read'] ) ) {
01984                                 # Not a public wiki, so no shortcut
01985                                 $useShortcut = false;
01986                         } elseif ( !empty( $wgRevokePermissions ) ) {
01993                                 foreach ( $wgRevokePermissions as $perms ) {
01994                                         if ( !empty( $perms['read'] ) ) {
01995                                                 # We might be removing the read right from the user, so no shortcut
01996                                                 $useShortcut = false;
01997                                                 break;
01998                                         }
01999                                 }
02000                         }
02001                 }
02002 
02003                 $whitelisted = false;
02004                 if ( $useShortcut ) {
02005                         # Shortcut for public wikis, allows skipping quite a bit of code
02006                         $whitelisted = true;
02007                 } elseif ( $user->isAllowed( 'read' ) ) {
02008                         # If the user is allowed to read pages, he is allowed to read all pages
02009                         $whitelisted = true;
02010                 } elseif ( $this->isSpecial( 'Userlogin' )
02011                         || $this->isSpecial( 'ChangePassword' )
02012                         || $this->isSpecial( 'PasswordReset' )
02013                 ) {
02014                         # Always grant access to the login page.
02015                         # Even anons need to be able to log in.
02016                         $whitelisted = true;
02017                 } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
02018                         # Time to check the whitelist
02019                         # Only do these checks is there's something to check against
02020                         $name = $this->getPrefixedText();
02021                         $dbName = $this->getPrefixedDBKey();
02022 
02023                         // Check with and without underscores
02024                         if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
02025                                 # Check for explicit whitelisting
02026                                 $whitelisted = true;
02027                         } elseif ( $this->getNamespace() == NS_MAIN ) {
02028                                 # Old settings might have the title prefixed with
02029                                 # a colon for main-namespace pages
02030                                 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
02031                                         $whitelisted = true;
02032                                 }
02033                         } elseif ( $this->isSpecialPage() ) {
02034                                 # If it's a special page, ditch the subpage bit and check again
02035                                 $name = $this->getDBkey();
02036                                 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
02037                                 if ( $name !== false ) {
02038                                         $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
02039                                         if ( in_array( $pure, $wgWhitelistRead, true ) ) {
02040                                                 $whitelisted = true;
02041                                         }
02042                                 }
02043                         }
02044                 }
02045 
02046                 if ( !$whitelisted ) {
02047                         # If the title is not whitelisted, give extensions a chance to do so...
02048                         wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
02049                         if ( !$whitelisted ) {
02050                                 $errors[] = $this->missingPermissionError( $action, $short );
02051                         }
02052                 }
02053 
02054                 return $errors;
02055         }
02056 
02065         private function missingPermissionError( $action, $short ) {
02066                 // We avoid expensive display logic for quickUserCan's and such
02067                 if ( $short ) {
02068                         return array( 'badaccess-group0' );
02069                 }
02070 
02071                 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
02072                         User::getGroupsWithPermission( $action ) );
02073 
02074                 if ( count( $groups ) ) {
02075                         global $wgLang;
02076                         return array(
02077                                 'badaccess-groups',
02078                                 $wgLang->commaList( $groups ),
02079                                 count( $groups )
02080                         );
02081                 } else {
02082                         return array( 'badaccess-group0' );
02083                 }
02084         }
02085 
02097         protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
02098                 wfProfileIn( __METHOD__ );
02099 
02100                 # Read has special handling
02101                 if ( $action == 'read' ) {
02102                         $checks = array(
02103                                 'checkPermissionHooks',
02104                                 'checkReadPermissions',
02105                         );
02106                 } else {
02107                         $checks = array(
02108                                 'checkQuickPermissions',
02109                                 'checkPermissionHooks',
02110                                 'checkSpecialsAndNSPermissions',
02111                                 'checkCSSandJSPermissions',
02112                                 'checkPageRestrictions',
02113                                 'checkCascadingSourcesRestrictions',
02114                                 'checkActionPermissions',
02115                                 'checkUserBlock'
02116                         );
02117                 }
02118 
02119                 $errors = array();
02120                 while( count( $checks ) > 0 &&
02121                                 !( $short && count( $errors ) > 0 ) ) {
02122                         $method = array_shift( $checks );
02123                         $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
02124                 }
02125 
02126                 wfProfileOut( __METHOD__ );
02127                 return $errors;
02128         }
02129 
02137         public function userCanEditCssSubpage() {
02138                 global $wgUser;
02139                 wfDeprecated( __METHOD__, '1.19' );
02140                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
02141                         || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02142         }
02143 
02151         public function userCanEditJsSubpage() {
02152                 global $wgUser;
02153                 wfDeprecated( __METHOD__, '1.19' );
02154                 return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
02155                            || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
02156         }
02157 
02165         public static function getFilteredRestrictionTypes( $exists = true ) {
02166                 global $wgRestrictionTypes;
02167                 $types = $wgRestrictionTypes;
02168                 if ( $exists ) {
02169                         # Remove the create restriction for existing titles
02170                         $types = array_diff( $types, array( 'create' ) );
02171                 } else {
02172                         # Only the create and upload restrictions apply to non-existing titles
02173                         $types = array_intersect( $types, array( 'create', 'upload' ) );
02174                 }
02175                 return $types;
02176         }
02177 
02183         public function getRestrictionTypes() {
02184                 if ( $this->isSpecialPage() ) {
02185                         return array();
02186                 }
02187 
02188                 $types = self::getFilteredRestrictionTypes( $this->exists() );
02189 
02190                 if ( $this->getNamespace() != NS_FILE ) {
02191                         # Remove the upload restriction for non-file titles
02192                         $types = array_diff( $types, array( 'upload' ) );
02193                 }
02194 
02195                 wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
02196 
02197                 wfDebug( __METHOD__ . ': applicable restrictions to [[' .
02198                         $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
02199 
02200                 return $types;
02201         }
02202 
02210         private function getTitleProtection() {
02211                 // Can't protect pages in special namespaces
02212                 if ( $this->getNamespace() < 0 ) {
02213                         return false;
02214                 }
02215 
02216                 // Can't protect pages that exist.
02217                 if ( $this->exists() ) {
02218                         return false;
02219                 }
02220 
02221                 if ( !isset( $this->mTitleProtection ) ) {
02222                         $dbr = wfGetDB( DB_SLAVE );
02223                         $res = $dbr->select( 'protected_titles', '*',
02224                                 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02225                                 __METHOD__ );
02226 
02227                         // fetchRow returns false if there are no rows.
02228                         $this->mTitleProtection = $dbr->fetchRow( $res );
02229                 }
02230                 return $this->mTitleProtection;
02231         }
02232 
02242         public function updateTitleProtection( $create_perm, $reason, $expiry ) {
02243                 wfDeprecated( __METHOD__, '1.19' );
02244 
02245                 global $wgUser;
02246 
02247                 $limit = array( 'create' => $create_perm );
02248                 $expiry = array( 'create' => $expiry );
02249 
02250                 $page = WikiPage::factory( $this );
02251                 $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser );
02252 
02253                 return $status->isOK();
02254         }
02255 
02259         public function deleteTitleProtection() {
02260                 $dbw = wfGetDB( DB_MASTER );
02261 
02262                 $dbw->delete(
02263                         'protected_titles',
02264                         array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02265                         __METHOD__
02266                 );
02267                 $this->mTitleProtection = false;
02268         }
02269 
02276         public function isSemiProtected( $action = 'edit' ) {
02277                 if ( $this->exists() ) {
02278                         $restrictions = $this->getRestrictions( $action );
02279                         if ( count( $restrictions ) > 0 ) {
02280                                 foreach ( $restrictions as $restriction ) {
02281                                         if ( strtolower( $restriction ) != 'autoconfirmed' ) {
02282                                                 return false;
02283                                         }
02284                                 }
02285                         } else {
02286                                 # Not protected
02287                                 return false;
02288                         }
02289                         return true;
02290                 } else {
02291                         # If it doesn't exist, it can't be protected
02292                         return false;
02293                 }
02294         }
02295 
02303         public function isProtected( $action = '' ) {
02304                 global $wgRestrictionLevels;
02305 
02306                 $restrictionTypes = $this->getRestrictionTypes();
02307 
02308                 # Special pages have inherent protection
02309                 if( $this->isSpecialPage() ) {
02310                         return true;
02311                 }
02312 
02313                 # Check regular protection levels
02314                 foreach ( $restrictionTypes as $type ) {
02315                         if ( $action == $type || $action == '' ) {
02316                                 $r = $this->getRestrictions( $type );
02317                                 foreach ( $wgRestrictionLevels as $level ) {
02318                                         if ( in_array( $level, $r ) && $level != '' ) {
02319                                                 return true;
02320                                         }
02321                                 }
02322                         }
02323                 }
02324 
02325                 return false;
02326         }
02327 
02335         public function isNamespaceProtected( User $user ) {
02336                 global $wgNamespaceProtection;
02337 
02338                 if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
02339                         foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
02340                                 if ( $right != '' && !$user->isAllowed( $right ) ) {
02341                                         return true;
02342                                 }
02343                         }
02344                 }
02345                 return false;
02346         }
02347 
02353         public function isCascadeProtected() {
02354                 list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
02355                 return ( $sources > 0 );
02356         }
02357 
02368         public function getCascadeProtectionSources( $getPages = true ) {
02369                 global $wgContLang;
02370                 $pagerestrictions = array();
02371 
02372                 if ( isset( $this->mCascadeSources ) && $getPages ) {
02373                         return array( $this->mCascadeSources, $this->mCascadingRestrictions );
02374                 } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
02375                         return array( $this->mHasCascadingRestrictions, $pagerestrictions );
02376                 }
02377 
02378                 wfProfileIn( __METHOD__ );
02379 
02380                 $dbr = wfGetDB( DB_SLAVE );
02381 
02382                 if ( $this->getNamespace() == NS_FILE ) {
02383                         $tables = array( 'imagelinks', 'page_restrictions' );
02384                         $where_clauses = array(
02385                                 'il_to' => $this->getDBkey(),
02386                                 'il_from=pr_page',
02387                                 'pr_cascade' => 1
02388                         );
02389                 } else {
02390                         $tables = array( 'templatelinks', 'page_restrictions' );
02391                         $where_clauses = array(
02392                                 'tl_namespace' => $this->getNamespace(),
02393                                 'tl_title' => $this->getDBkey(),
02394                                 'tl_from=pr_page',
02395                                 'pr_cascade' => 1
02396                         );
02397                 }
02398 
02399                 if ( $getPages ) {
02400                         $cols = array( 'pr_page', 'page_namespace', 'page_title',
02401                                                    'pr_expiry', 'pr_type', 'pr_level' );
02402                         $where_clauses[] = 'page_id=pr_page';
02403                         $tables[] = 'page';
02404                 } else {
02405                         $cols = array( 'pr_expiry' );
02406                 }
02407 
02408                 $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
02409 
02410                 $sources = $getPages ? array() : false;
02411                 $now = wfTimestampNow();
02412                 $purgeExpired = false;
02413 
02414                 foreach ( $res as $row ) {
02415                         $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02416                         if ( $expiry > $now ) {
02417                                 if ( $getPages ) {
02418                                         $page_id = $row->pr_page;
02419                                         $page_ns = $row->page_namespace;
02420                                         $page_title = $row->page_title;
02421                                         $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
02422                                         # Add groups needed for each restriction type if its not already there
02423                                         # Make sure this restriction type still exists
02424 
02425                                         if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
02426                                                 $pagerestrictions[$row->pr_type] = array();
02427                                         }
02428 
02429                                         if ( isset( $pagerestrictions[$row->pr_type] ) &&
02430                                                  !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] ) ) {
02431                                                 $pagerestrictions[$row->pr_type][] = $row->pr_level;
02432                                         }
02433                                 } else {
02434                                         $sources = true;
02435                                 }
02436                         } else {
02437                                 // Trigger lazy purge of expired restrictions from the db
02438                                 $purgeExpired = true;
02439                         }
02440                 }
02441                 if ( $purgeExpired ) {
02442                         Title::purgeExpiredRestrictions();
02443                 }
02444 
02445                 if ( $getPages ) {
02446                         $this->mCascadeSources = $sources;
02447                         $this->mCascadingRestrictions = $pagerestrictions;
02448                 } else {
02449                         $this->mHasCascadingRestrictions = $sources;
02450                 }
02451 
02452                 wfProfileOut( __METHOD__ );
02453                 return array( $sources, $pagerestrictions );
02454         }
02455 
02462         public function getRestrictions( $action ) {
02463                 if ( !$this->mRestrictionsLoaded ) {
02464                         $this->loadRestrictions();
02465                 }
02466                 return isset( $this->mRestrictions[$action] )
02467                                 ? $this->mRestrictions[$action]
02468                                 : array();
02469         }
02470 
02477         public function getRestrictionExpiry( $action ) {
02478                 if ( !$this->mRestrictionsLoaded ) {
02479                         $this->loadRestrictions();
02480                 }
02481                 return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02482         }
02483 
02489         function areRestrictionsCascading() {
02490                 if ( !$this->mRestrictionsLoaded ) {
02491                         $this->loadRestrictions();
02492                 }
02493 
02494                 return $this->mCascadeRestriction;
02495         }
02496 
02504         private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
02505                 $rows = array();
02506 
02507                 foreach ( $res as $row ) {
02508                         $rows[] = $row;
02509                 }
02510 
02511                 $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
02512         }
02513 
02523         public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
02524                 global $wgContLang;
02525                 $dbr = wfGetDB( DB_SLAVE );
02526 
02527                 $restrictionTypes = $this->getRestrictionTypes();
02528 
02529                 foreach ( $restrictionTypes as $type ) {
02530                         $this->mRestrictions[$type] = array();
02531                         $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
02532                 }
02533 
02534                 $this->mCascadeRestriction = false;
02535 
02536                 # Backwards-compatibility: also load the restrictions from the page record (old format).
02537 
02538                 if ( $oldFashionedRestrictions === null ) {
02539                         $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
02540                                 array( 'page_id' => $this->getArticleId() ), __METHOD__ );
02541                 }
02542 
02543                 if ( $oldFashionedRestrictions != '' ) {
02544 
02545                         foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
02546                                 $temp = explode( '=', trim( $restrict ) );
02547                                 if ( count( $temp ) == 1 ) {
02548                                         // old old format should be treated as edit/move restriction
02549                                         $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
02550                                         $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
02551                                 } else {
02552                                         $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) );
02553                                 }
02554                         }
02555 
02556                         $this->mOldRestrictions = true;
02557 
02558                 }
02559 
02560                 if ( count( $rows ) ) {
02561                         # Current system - load second to make them override.
02562                         $now = wfTimestampNow();
02563                         $purgeExpired = false;
02564 
02565                         # Cycle through all the restrictions.
02566                         foreach ( $rows as $row ) {
02567 
02568                                 // Don't take care of restrictions types that aren't allowed
02569                                 if ( !in_array( $row->pr_type, $restrictionTypes ) )
02570                                         continue;
02571 
02572                                 // This code should be refactored, now that it's being used more generally,
02573                                 // But I don't really see any harm in leaving it in Block for now -werdna
02574                                 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02575 
02576                                 // Only apply the restrictions if they haven't expired!
02577                                 if ( !$expiry || $expiry > $now ) {
02578                                         $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
02579                                         $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
02580 
02581                                         $this->mCascadeRestriction |= $row->pr_cascade;
02582                                 } else {
02583                                         // Trigger a lazy purge of expired restrictions
02584                                         $purgeExpired = true;
02585                                 }
02586                         }
02587 
02588                         if ( $purgeExpired ) {
02589                                 Title::purgeExpiredRestrictions();
02590                         }
02591                 }
02592 
02593                 $this->mRestrictionsLoaded = true;
02594         }
02595 
02602         public function loadRestrictions( $oldFashionedRestrictions = null ) {
02603                 global $wgContLang;
02604                 if ( !$this->mRestrictionsLoaded ) {
02605                         if ( $this->exists() ) {
02606                                 $dbr = wfGetDB( DB_SLAVE );
02607 
02608                                 $res = $dbr->select(
02609                                         'page_restrictions',
02610                                         '*',
02611                                         array( 'pr_page' => $this->getArticleId() ),
02612                                         __METHOD__
02613                                 );
02614 
02615                                 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
02616                         } else {
02617                                 $title_protection = $this->getTitleProtection();
02618 
02619                                 if ( $title_protection ) {
02620                                         $now = wfTimestampNow();
02621                                         $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
02622 
02623                                         if ( !$expiry || $expiry > $now ) {
02624                                                 // Apply the restrictions
02625                                                 $this->mRestrictionsExpiry['create'] = $expiry;
02626                                                 $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
02627                                         } else { // Get rid of the old restrictions
02628                                                 Title::purgeExpiredRestrictions();
02629                                                 $this->mTitleProtection = false;
02630                                         }
02631                                 } else {
02632                                         $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
02633                                 }
02634                                 $this->mRestrictionsLoaded = true;
02635                         }
02636                 }
02637         }
02638 
02643         public function flushRestrictions() {
02644                 $this->mRestrictionsLoaded = false;
02645                 $this->mTitleProtection = null;
02646         }
02647 
02651         static function purgeExpiredRestrictions() {
02652                 $dbw = wfGetDB( DB_MASTER );
02653                 $dbw->delete(
02654                         'page_restrictions',
02655                         array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02656                         __METHOD__
02657                 );
02658 
02659                 $dbw->delete(
02660                         'protected_titles',
02661                         array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
02662                         __METHOD__
02663                 );
02664         }
02665 
02671         public function hasSubpages() {
02672                 if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
02673                         # Duh
02674                         return false;
02675                 }
02676 
02677                 # We dynamically add a member variable for the purpose of this method
02678                 # alone to cache the result.  There's no point in having it hanging
02679                 # around uninitialized in every Title object; therefore we only add it
02680                 # if needed and don't declare it statically.
02681                 if ( isset( $this->mHasSubpages ) ) {
02682                         return $this->mHasSubpages;
02683                 }
02684 
02685                 $subpages = $this->getSubpages( 1 );
02686                 if ( $subpages instanceof TitleArray ) {
02687                         return $this->mHasSubpages = (bool)$subpages->count();
02688                 }
02689                 return $this->mHasSubpages = false;
02690         }
02691 
02699         public function getSubpages( $limit = -1 ) {
02700                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
02701                         return array();
02702                 }
02703 
02704                 $dbr = wfGetDB( DB_SLAVE );
02705                 $conds['page_namespace'] = $this->getNamespace();
02706                 $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
02707                 $options = array();
02708                 if ( $limit > -1 ) {
02709                         $options['LIMIT'] = $limit;
02710                 }
02711                 return $this->mSubpages = TitleArray::newFromResult(
02712                         $dbr->select( 'page',
02713                                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
02714                                 $conds,
02715                                 __METHOD__,
02716                                 $options
02717                         )
02718                 );
02719         }
02720 
02726         public function isDeleted() {
02727                 if ( $this->getNamespace() < 0 ) {
02728                         $n = 0;
02729                 } else {
02730                         $dbr = wfGetDB( DB_SLAVE );
02731 
02732                         $n = $dbr->selectField( 'archive', 'COUNT(*)',
02733                                 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02734                                 __METHOD__
02735                         );
02736                         if ( $this->getNamespace() == NS_FILE ) {
02737                                 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
02738                                         array( 'fa_name' => $this->getDBkey() ),
02739                                         __METHOD__
02740                                 );
02741                         }
02742                 }
02743                 return (int)$n;
02744         }
02745 
02751         public function isDeletedQuick() {
02752                 if ( $this->getNamespace() < 0 ) {
02753                         return false;
02754                 }
02755                 $dbr = wfGetDB( DB_SLAVE );
02756                 $deleted = (bool)$dbr->selectField( 'archive', '1',
02757                         array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
02758                         __METHOD__
02759                 );
02760                 if ( !$deleted && $this->getNamespace() == NS_FILE ) {
02761                         $deleted = (bool)$dbr->selectField( 'filearchive', '1',
02762                                 array( 'fa_name' => $this->getDBkey() ),
02763                                 __METHOD__
02764                         );
02765                 }
02766                 return $deleted;
02767         }
02768 
02777         public function getArticleID( $flags = 0 ) {
02778                 if ( $this->getNamespace() < 0 ) {
02779                         return $this->mArticleID = 0;
02780                 }
02781                 $linkCache = LinkCache::singleton();
02782                 if ( $flags & self::GAID_FOR_UPDATE ) {
02783                         $oldUpdate = $linkCache->forUpdate( true );
02784                         $linkCache->clearLink( $this );
02785                         $this->mArticleID = $linkCache->addLinkObj( $this );
02786                         $linkCache->forUpdate( $oldUpdate );
02787                 } else {
02788                         if ( -1 == $this->mArticleID ) {
02789                                 $this->mArticleID = $linkCache->addLinkObj( $this );
02790                         }
02791                 }
02792                 return $this->mArticleID;
02793         }
02794 
02802         public function isRedirect( $flags = 0 ) {
02803                 if ( !is_null( $this->mRedirect ) ) {
02804                         return $this->mRedirect;
02805                 }
02806                 # Calling getArticleID() loads the field from cache as needed
02807                 if ( !$this->getArticleID( $flags ) ) {
02808                         return $this->mRedirect = false;
02809                 }
02810                 $linkCache = LinkCache::singleton();
02811                 $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
02812 
02813                 return $this->mRedirect;
02814         }
02815 
02823         public function getLength( $flags = 0 ) {
02824                 if ( $this->mLength != -1 ) {
02825                         return $this->mLength;
02826                 }
02827                 # Calling getArticleID() loads the field from cache as needed
02828                 if ( !$this->getArticleID( $flags ) ) {
02829                         return $this->mLength = 0;
02830                 }
02831                 $linkCache = LinkCache::singleton();
02832                 $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
02833 
02834                 return $this->mLength;
02835         }
02836 
02843         public function getLatestRevID( $flags = 0 ) {
02844                 if ( $this->mLatestID !== false ) {
02845                         return intval( $this->mLatestID );
02846                 }
02847                 # Calling getArticleID() loads the field from cache as needed
02848                 if ( !$this->getArticleID( $flags ) ) {
02849                         return $this->mLatestID = 0;
02850                 }
02851                 $linkCache = LinkCache::singleton();
02852                 $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
02853 
02854                 return $this->mLatestID;
02855         }
02856 
02867         public function resetArticleID( $newid ) {
02868                 $linkCache = LinkCache::singleton();
02869                 $linkCache->clearLink( $this );
02870 
02871                 if ( $newid === false ) {
02872                         $this->mArticleID = -1;
02873                 } else {
02874                         $this->mArticleID = intval( $newid );
02875                 }
02876                 $this->mRestrictionsLoaded = false;
02877                 $this->mRestrictions = array();
02878                 $this->mRedirect = null;
02879                 $this->mLength = -1;
02880                 $this->mLatestID = false;
02881                 $this->mEstimateRevisions = null;
02882         }
02883 
02891         public static function capitalize( $text, $ns = NS_MAIN ) {
02892                 global $wgContLang;
02893 
02894                 if ( MWNamespace::isCapitalized( $ns ) ) {
02895                         return $wgContLang->ucfirst( $text );
02896                 } else {
02897                         return $text;
02898                 }
02899         }
02900 
02912         private function secureAndSplit() {
02913                 global $wgContLang, $wgLocalInterwiki;
02914 
02915                 # Initialisation
02916                 $this->mInterwiki = $this->mFragment = '';
02917                 $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
02918 
02919                 $dbkey = $this->mDbkeyform;
02920 
02921                 # Strip Unicode bidi override characters.
02922                 # Sometimes they slip into cut-n-pasted page titles, where the
02923                 # override chars get included in list displays.
02924                 $dbkey = preg_replace( '/\xE2\x80[\x8E\x8F\xAA-\xAE]/S', '', $dbkey );
02925 
02926                 # Clean up whitespace
02927                 # Note: use of the /u option on preg_replace here will cause
02928                 # input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
02929                 # conveniently disabling them.
02930                 $dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
02931                 $dbkey = trim( $dbkey, '_' );
02932 
02933                 if ( $dbkey == '' ) {
02934                         return false;
02935                 }
02936 
02937                 if ( false !== strpos( $dbkey, UTF8_REPLACEMENT ) ) {
02938                         # Contained illegal UTF-8 sequences or forbidden Unicode chars.
02939                         return false;
02940                 }
02941 
02942                 $this->mDbkeyform = $dbkey;
02943 
02944                 # Initial colon indicates main namespace rather than specified default
02945                 # but should not create invalid {ns,title} pairs such as {0,Project:Foo}
02946                 if ( ':' == $dbkey[0] ) {
02947                         $this->mNamespace = NS_MAIN;
02948                         $dbkey = substr( $dbkey, 1 ); # remove the colon but continue processing
02949                         $dbkey = trim( $dbkey, '_' ); # remove any subsequent whitespace
02950                 }
02951 
02952                 # Namespace or interwiki prefix
02953                 $firstPass = true;
02954                 $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
02955                 do {
02956                         $m = array();
02957                         if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
02958                                 $p = $m[1];
02959                                 if ( ( $ns = $wgContLang->getNsIndex( $p ) ) !== false ) {
02960                                         # Ordinary namespace
02961                                         $dbkey = $m[2];
02962                                         $this->mNamespace = $ns;
02963                                         # For Talk:X pages, check if X has a "namespace" prefix
02964                                         if ( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
02965                                                 if ( $wgContLang->getNsIndex( $x[1] ) ) {
02966                                                         # Disallow Talk:File:x type titles...
02967                                                         return false;
02968                                                 } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
02969                                                         # Disallow Talk:Interwiki:x type titles...
02970                                                         return false;
02971                                                 }
02972                                         }
02973                                 } elseif ( Interwiki::isValidInterwiki( $p ) ) {
02974                                         if ( !$firstPass ) {
02975                                                 # Can't make a local interwiki link to an interwiki link.
02976                                                 # That's just crazy!
02977                                                 return false;
02978                                         }
02979 
02980                                         # Interwiki link
02981                                         $dbkey = $m[2];
02982                                         $this->mInterwiki = $wgContLang->lc( $p );
02983 
02984                                         # Redundant interwiki prefix to the local wiki
02985                                         if ( $wgLocalInterwiki !== false
02986                                                 && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
02987                                         {
02988                                                 if ( $dbkey == '' ) {
02989                                                         # Can't have an empty self-link
02990                                                         return false;
02991                                                 }
02992                                                 $this->mInterwiki = '';
02993                                                 $firstPass = false;
02994                                                 # Do another namespace split...
02995                                                 continue;
02996                                         }
02997 
02998                                         # If there's an initial colon after the interwiki, that also
02999                                         # resets the default namespace
03000                                         if ( $dbkey !== '' && $dbkey[0] == ':' ) {
03001                                                 $this->mNamespace = NS_MAIN;
03002                                                 $dbkey = substr( $dbkey, 1 );
03003                                         }
03004                                 }
03005                                 # If there's no recognized interwiki or namespace,
03006                                 # then let the colon expression be part of the title.
03007                         }
03008                         break;
03009                 } while ( true );
03010 
03011                 # We already know that some pages won't be in the database!
03012                 if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
03013                         $this->mArticleID = 0;
03014                 }
03015                 $fragment = strstr( $dbkey, '#' );
03016                 if ( false !== $fragment ) {
03017                         $this->setFragment( $fragment );
03018                         $dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
03019                         # remove whitespace again: prevents "Foo_bar_#"
03020                         # becoming "Foo_bar_"
03021                         $dbkey = preg_replace( '/_*$/', '', $dbkey );
03022                 }
03023 
03024                 # Reject illegal characters.
03025                 $rxTc = self::getTitleInvalidRegex();
03026                 if ( preg_match( $rxTc, $dbkey ) ) {
03027                         return false;
03028                 }
03029 
03030                 # Pages with "/./" or "/../" appearing in the URLs will often be un-
03031                 # reachable due to the way web browsers deal with 'relative' URLs.
03032                 # Also, they conflict with subpage syntax.  Forbid them explicitly.
03033                 if ( strpos( $dbkey, '.' ) !== false &&
03034                          ( $dbkey === '.' || $dbkey === '..' ||
03035                            strpos( $dbkey, './' ) === 0  ||
03036                            strpos( $dbkey, '../' ) === 0 ||
03037                            strpos( $dbkey, '/./' ) !== false ||
03038                            strpos( $dbkey, '/../' ) !== false  ||
03039                            substr( $dbkey, -2 ) == '/.' ||
03040                            substr( $dbkey, -3 ) == '/..' ) )
03041                 {
03042                         return false;
03043                 }
03044 
03045                 # Magic tilde sequences? Nu-uh!
03046                 if ( strpos( $dbkey, '~~~' ) !== false ) {
03047                         return false;
03048                 }
03049 
03050                 # Limit the size of titles to 255 bytes. This is typically the size of the
03051                 # underlying database field. We make an exception for special pages, which
03052                 # don't need to be stored in the database, and may edge over 255 bytes due
03053                 # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
03054                 if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
03055                   strlen( $dbkey ) > 512 )
03056                 {
03057                         return false;
03058                 }
03059 
03060                 # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
03061                 # and [[Foo]] point to the same place.  Don't force it for interwikis, since the
03062                 # other site might be case-sensitive.
03063                 $this->mUserCaseDBKey = $dbkey;
03064                 if ( $this->mInterwiki == '' ) {
03065                         $dbkey = self::capitalize( $dbkey, $this->mNamespace );
03066                 }
03067 
03068                 # Can't make a link to a namespace alone... "empty" local links can only be
03069                 # self-links with a fragment identifier.
03070                 if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
03071                         return false;
03072                 }
03073 
03074                 // Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
03075                 // IP names are not allowed for accounts, and can only be referring to
03076                 // edits from the IP. Given '::' abbreviations and caps/lowercaps,
03077                 // there are numerous ways to present the same IP. Having sp:contribs scan
03078                 // them all is silly and having some show the edits and others not is
03079                 // inconsistent. Same for talk/userpages. Keep them normalized instead.
03080                 $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
03081                         ? IP::sanitizeIP( $dbkey )
03082                         : $dbkey;
03083 
03084                 // Any remaining initial :s are illegal.
03085                 if ( $dbkey !== '' && ':' == $dbkey[0] ) {
03086                         return false;
03087                 }
03088 
03089                 # Fill fields
03090                 $this->mDbkeyform = $dbkey;
03091                 $this->mUrlform = wfUrlencode( $dbkey );
03092 
03093                 $this->mTextform = str_replace( '_', ' ', $dbkey );
03094 
03095                 return true;
03096         }
03097 
03110         public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03111                 if ( count( $options ) > 0 ) {
03112                         $db = wfGetDB( DB_MASTER );
03113                 } else {
03114                         $db = wfGetDB( DB_SLAVE );
03115                 }
03116 
03117                 $res = $db->select(
03118                         array( 'page', $table ),
03119                         array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
03120                         array(
03121                                 "{$prefix}_from=page_id",
03122                                 "{$prefix}_namespace" => $this->getNamespace(),
03123                                 "{$prefix}_title"     => $this->getDBkey() ),
03124                         __METHOD__,
03125                         $options
03126                 );
03127 
03128                 $retVal = array();
03129                 if ( $res->numRows() ) {
03130                         $linkCache = LinkCache::singleton();
03131                         foreach ( $res as $row ) {
03132                                 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
03133                                 if ( $titleObj ) {
03134                                         $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03135                                         $retVal[] = $titleObj;
03136                                 }
03137                         }
03138                 }
03139                 return $retVal;
03140         }
03141 
03152         public function getTemplateLinksTo( $options = array() ) {
03153                 return $this->getLinksTo( $options, 'templatelinks', 'tl' );
03154         }
03155 
03168         public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03169                 $id = $this->getArticleId();
03170 
03171                 # If the page doesn't exist; there can't be any link from this page
03172                 if ( !$id ) {
03173                         return array();
03174                 }
03175 
03176                 if ( count( $options ) > 0 ) {
03177                         $db = wfGetDB( DB_MASTER );
03178                 } else {
03179                         $db = wfGetDB( DB_SLAVE );
03180                 }
03181 
03182                 $namespaceFiled = "{$prefix}_namespace";
03183                 $titleField = "{$prefix}_title";
03184 
03185                 $res = $db->select(
03186                         array( $table, 'page' ),
03187                         array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
03188                         array( "{$prefix}_from" => $id ),
03189                         __METHOD__,
03190                         $options,
03191                         array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
03192                 );
03193 
03194                 $retVal = array();
03195                 if ( $res->numRows() ) {
03196                         $linkCache = LinkCache::singleton();
03197                         foreach ( $res as $row ) {
03198                                 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
03199                                 if ( $titleObj ) {
03200                                         if ( $row->page_id ) {
03201                                                 $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03202                                         } else {
03203                                                 $linkCache->addBadLinkObj( $titleObj );
03204                                         }
03205                                         $retVal[] = $titleObj;
03206                                 }
03207                         }
03208                 }
03209                 return $retVal;
03210         }
03211 
03222         public function getTemplateLinksFrom( $options = array() ) {
03223                 return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
03224         }
03225 
03232         public function getBrokenLinksFrom() {
03233                 if ( $this->getArticleId() == 0 ) {
03234                         # All links from article ID 0 are false positives
03235                         return array();
03236                 }
03237 
03238                 $dbr = wfGetDB( DB_SLAVE );
03239                 $res = $dbr->select(
03240                         array( 'page', 'pagelinks' ),
03241                         array( 'pl_namespace', 'pl_title' ),
03242                         array(
03243                                 'pl_from' => $this->getArticleId(),
03244                                 'page_namespace IS NULL'
03245                         ),
03246                         __METHOD__, array(),
03247                         array(
03248                                 'page' => array(
03249                                         'LEFT JOIN',
03250                                         array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
03251                                 )
03252                         )
03253                 );
03254 
03255                 $retVal = array();
03256                 foreach ( $res as $row ) {
03257                         $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
03258                 }
03259                 return $retVal;
03260         }
03261 
03262 
03269         public function getSquidURLs() {
03270                 global $wgContLang;
03271 
03272                 $urls = array(
03273                         $this->getInternalURL(),
03274                         $this->getInternalURL( 'action=history' )
03275                 );
03276 
03277                 // purge variant urls as well
03278                 if ( $wgContLang->hasVariants() ) {
03279                         $variants = $wgContLang->getVariants();
03280                         foreach ( $variants as $vCode ) {
03281                                 $urls[] = $this->getInternalURL( '', $vCode );
03282                         }
03283                 }
03284 
03285                 return $urls;
03286         }
03287 
03291         public function purgeSquid() {
03292                 global $wgUseSquid;
03293                 if ( $wgUseSquid ) {
03294                         $urls = $this->getSquidURLs();
03295                         $u = new SquidUpdate( $urls );
03296                         $u->doUpdate();
03297                 }
03298         }
03299 
03306         public function moveNoAuth( &$nt ) {
03307                 return $this->moveTo( $nt, false );
03308         }
03309 
03320         public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
03321                 global $wgUser;
03322 
03323                 $errors = array();
03324                 if ( !$nt ) {
03325                         // Normally we'd add this to $errors, but we'll get
03326                         // lots of syntax errors if $nt is not an object
03327                         return array( array( 'badtitletext' ) );
03328                 }
03329                 if ( $this->equals( $nt ) ) {
03330                         $errors[] = array( 'selfmove' );
03331                 }
03332                 if ( !$this->isMovable() ) {
03333                         $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
03334                 }
03335                 if ( $nt->getInterwiki() != '' ) {
03336                         $errors[] = array( 'immobile-target-namespace-iw' );
03337                 }
03338                 if ( !$nt->isMovable() ) {
03339                         $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
03340                 }
03341 
03342                 $oldid = $this->getArticleID();
03343                 $newid = $nt->getArticleID();
03344 
03345                 if ( strlen( $nt->getDBkey() ) < 1 ) {
03346                         $errors[] = array( 'articleexists' );
03347                 }
03348                 if ( ( $this->getDBkey() == '' ) ||
03349                          ( !$oldid ) ||
03350                          ( $nt->getDBkey() == '' ) ) {
03351                         $errors[] = array( 'badarticleerror' );
03352                 }
03353 
03354                 // Image-specific checks
03355                 if ( $this->getNamespace() == NS_FILE ) {
03356                         $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
03357                 }
03358 
03359                 if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
03360                         $errors[] = array( 'nonfile-cannot-move-to-file' );
03361                 }
03362 
03363                 if ( $auth ) {
03364                         $errors = wfMergeErrorArrays( $errors,
03365                                 $this->getUserPermissionsErrors( 'move', $wgUser ),
03366                                 $this->getUserPermissionsErrors( 'edit', $wgUser ),
03367                                 $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
03368                                 $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
03369                 }
03370 
03371                 $match = EditPage::matchSummarySpamRegex( $reason );
03372                 if ( $match !== false ) {
03373                         // This is kind of lame, won't display nice
03374                         $errors[] = array( 'spamprotectiontext' );
03375                 }
03376 
03377                 $err = null;
03378                 if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
03379                         $errors[] = array( 'hookaborted', $err );
03380                 }
03381 
03382                 # The move is allowed only if (1) the target doesn't exist, or
03383                 # (2) the target is a redirect to the source, and has no history
03384                 # (so we can undo bad moves right after they're done).
03385 
03386                 if ( 0 != $newid ) { # Target exists; check for validity
03387                         if ( !$this->isValidMoveTarget( $nt ) ) {
03388                                 $errors[] = array( 'articleexists' );
03389                         }
03390                 } else {
03391                         $tp = $nt->getTitleProtection();
03392                         $right = ( $tp['pt_create_perm'] == 'sysop' ) ? 'protect' : $tp['pt_create_perm'];
03393                         if ( $tp and !$wgUser->isAllowed( $right ) ) {
03394                                 $errors[] = array( 'cantmove-titleprotected' );
03395                         }
03396                 }
03397                 if ( empty( $errors ) ) {
03398                         return true;
03399                 }
03400                 return $errors;
03401         }
03402 
03408         protected function validateFileMoveOperation( $nt ) {
03409                 global $wgUser;
03410 
03411                 $errors = array();
03412 
03413                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
03414 
03415                 $file = wfLocalFile( $this );
03416                 if ( $file->exists() ) {
03417                         if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
03418                                 $errors[] = array( 'imageinvalidfilename' );
03419                         }
03420                         if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
03421                                 $errors[] = array( 'imagetypemismatch' );
03422                         }
03423                 }
03424 
03425                 if ( $nt->getNamespace() != NS_FILE ) {
03426                         $errors[] = array( 'imagenocrossnamespace' );
03427                         // From here we want to do checks on a file object, so if we can't
03428                         // create one, we must return.
03429                         return $errors;
03430                 }
03431 
03432                 // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
03433 
03434                 $destFile = wfLocalFile( $nt );
03435                 if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
03436                         $errors[] = array( 'file-exists-sharedrepo' );
03437                 }
03438 
03439                 return $errors;
03440         }
03441 
03453         public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
03454                 global $wgUser;
03455                 $err = $this->isValidMoveOperation( $nt, $auth, $reason );
03456                 if ( is_array( $err ) ) {
03457                         // Auto-block user's IP if the account was "hard" blocked
03458                         $wgUser->spreadAnyEditBlock();
03459                         return $err;
03460                 }
03461 
03462                 // If it is a file, move it first.
03463                 // It is done before all other moving stuff is done because it's hard to revert.
03464                 $dbw = wfGetDB( DB_MASTER );
03465                 if ( $this->getNamespace() == NS_FILE ) {
03466                         $file = wfLocalFile( $this );
03467                         if ( $file->exists() ) {
03468                                 $status = $file->move( $nt );
03469                                 if ( !$status->isOk() ) {
03470                                         return $status->getErrorsArray();
03471                                 }
03472                         }
03473                         // Clear RepoGroup process cache
03474                         RepoGroup::singleton()->clearCache( $this );
03475                         RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
03476                 }
03477 
03478                 $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
03479                 $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
03480                 $protected = $this->isProtected();
03481 
03482                 // Do the actual move
03483                 $err = $this->moveToInternal( $nt, $reason, $createRedirect );
03484                 if ( is_array( $err ) ) {
03485                         # @todo FIXME: What about the File we have already moved?
03486                         $dbw->rollback();
03487                         return $err;
03488                 }
03489 
03490                 // Refresh the sortkey for this row.  Be careful to avoid resetting
03491                 // cl_timestamp, which may disturb time-based lists on some sites.
03492                 $prefixes = $dbw->select(
03493                         'categorylinks',
03494                         array( 'cl_sortkey_prefix', 'cl_to' ),
03495                         array( 'cl_from' => $pageid ),
03496                         __METHOD__
03497                 );
03498                 foreach ( $prefixes as $prefixRow ) {
03499                         $prefix = $prefixRow->cl_sortkey_prefix;
03500                         $catTo = $prefixRow->cl_to;
03501                         $dbw->update( 'categorylinks',
03502                                 array(
03503                                         'cl_sortkey' => Collation::singleton()->getSortKey(
03504                                                 $nt->getCategorySortkey( $prefix ) ),
03505                                         'cl_timestamp=cl_timestamp' ),
03506                                 array(
03507                                         'cl_from' => $pageid,
03508                                         'cl_to' => $catTo ),
03509                                 __METHOD__
03510                         );
03511                 }
03512 
03513                 $redirid = $this->getArticleID();
03514 
03515                 if ( $protected ) {
03516                         # Protect the redirect title as the title used to be...
03517                         $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
03518                                 array(
03519                                         'pr_page'    => $redirid,
03520                                         'pr_type'    => 'pr_type',
03521                                         'pr_level'   => 'pr_level',
03522                                         'pr_cascade' => 'pr_cascade',
03523                                         'pr_user'    => 'pr_user',
03524                                         'pr_expiry'  => 'pr_expiry'
03525                                 ),
03526                                 array( 'pr_page' => $pageid ),
03527                                 __METHOD__,
03528                                 array( 'IGNORE' )
03529                         );
03530                         # Update the protection log
03531                         $log = new LogPage( 'protect' );
03532                         $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
03533                         if ( $reason ) {
03534                                 $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
03535                         }
03536                         // @todo FIXME: $params?
03537                         $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
03538                 }
03539 
03540                 # Update watchlists
03541                 $oldnamespace = $this->getNamespace() & ~1;
03542                 $newnamespace = $nt->getNamespace() & ~1;
03543                 $oldtitle = $this->getDBkey();
03544                 $newtitle = $nt->getDBkey();
03545 
03546                 if ( $oldnamespace != $newnamespace || $oldtitle != $newtitle ) {
03547                         WatchedItem::duplicateEntries( $this, $nt );
03548                 }
03549 
03550                 $dbw->commit();
03551 
03552                 wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
03553                 return true;
03554         }
03555 
03565         private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
03566                 global $wgUser, $wgContLang;
03567 
03568                 if ( $nt->exists() ) {
03569                         $moveOverRedirect = true;
03570                         $logType = 'move_redir';
03571                 } else {
03572                         $moveOverRedirect = false;
03573                         $logType = 'move';
03574                 }
03575 
03576                 $redirectSuppressed = !$createRedirect && $wgUser->isAllowed( 'suppressredirect' );
03577 
03578                 $logEntry = new ManualLogEntry( 'move', $logType );
03579                 $logEntry->setPerformer( $wgUser );
03580                 $logEntry->setTarget( $this );
03581                 $logEntry->setComment( $reason );
03582                 $logEntry->setParameters( array(
03583                         '4::target' => $nt->getPrefixedText(),
03584                         '5::noredir' => $redirectSuppressed ? '1': '0',
03585                 ) );
03586 
03587                 $formatter = LogFormatter::newFromEntry( $logEntry );
03588                 $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
03589                 $comment = $formatter->getPlainActionText();
03590                 if ( $reason ) {
03591                         $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
03592                 }
03593                 # Truncate for whole multibyte characters.
03594                 $comment = $wgContLang->truncate( $comment, 255 );
03595 
03596                 $oldid = $this->getArticleID();
03597                 $latest = $this->getLatestRevID();
03598 
03599                 $dbw = wfGetDB( DB_MASTER );
03600 
03601                 $newpage = WikiPage::factory( $nt );
03602 
03603                 if ( $moveOverRedirect ) {
03604                         $newid = $nt->getArticleID();
03605 
03606                         # Delete the old redirect. We don't save it to history since
03607                         # by definition if we've got here it's rather uninteresting.
03608                         # We have to remove it so that the next step doesn't trigger
03609                         # a conflict on the unique namespace+title index...
03610                         $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
03611 
03612                         $newpage->doDeleteUpdates( $newid );
03613                 }
03614 
03615                 # Save a null revision in the page's history notifying of the move
03616                 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
03617                 if ( !is_object( $nullRevision ) ) {
03618                         throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
03619                 }
03620                 $nullRevId = $nullRevision->insertOn( $dbw );
03621 
03622                 # Change the name of the target page:
03623                 $dbw->update( 'page',
03624                         /* SET */ array(
03625                                 'page_namespace' => $nt->getNamespace(),
03626                                 'page_title'     => $nt->getDBkey(),
03627                         ),
03628                         /* WHERE */ array( 'page_id' => $oldid ),
03629                         __METHOD__
03630                 );
03631 
03632                 $this->resetArticleID( 0 );
03633                 $nt->resetArticleID( $oldid );
03634 
03635                 $newpage->updateRevisionOn( $dbw, $nullRevision );
03636 
03637                 wfRunHooks( 'NewRevisionFromEditComplete',
03638                         array( $newpage, $nullRevision, $latest, $wgUser ) );
03639 
03640                 $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
03641 
03642                 if ( !$moveOverRedirect ) {
03643                         WikiPage::onArticleCreate( $nt );
03644                 }
03645 
03646                 # Recreate the redirect, this time in the other direction.
03647                 if ( $redirectSuppressed ) {
03648                         WikiPage::onArticleDelete( $this );
03649                 } else {
03650                         $mwRedir = MagicWord::get( 'redirect' );
03651                         $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
03652                         $redirectArticle = WikiPage::factory( $this );
03653                         $newid = $redirectArticle->insertOn( $dbw );
03654                         if ( $newid ) { // sanity
03655                                 $redirectRevision = new Revision( array(
03656                                         'page'    => $newid,
03657                                         'comment' => $comment,
03658                                         'text'    => $redirectText ) );
03659                                 $redirectRevision->insertOn( $dbw );
03660                                 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
03661 
03662                                 wfRunHooks( 'NewRevisionFromEditComplete',
03663                                         array( $redirectArticle, $redirectRevision, false, $wgUser ) );
03664 
03665                                 $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
03666                         }
03667                 }
03668 
03669                 # Log the move
03670                 $logid = $logEntry->insert();
03671                 $logEntry->publish( $logid );
03672         }
03673 
03686         public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03687                 global $wgMaximumMovedPages;
03688                 // Check permissions
03689                 if ( !$this->userCan( 'move-subpages' ) ) {
03690                         return array( 'cant-move-subpages' );
03691                 }
03692                 // Do the source and target namespaces support subpages?
03693                 if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03694                         return array( 'namespace-nosubpages',
03695                                 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03696                 }
03697                 if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
03698                         return array( 'namespace-nosubpages',
03699                                 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03700                 }
03701 
03702                 $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
03703                 $retval = array();
03704                 $count = 0;
03705                 foreach ( $subpages as $oldSubpage ) {
03706                         $count++;
03707                         if ( $count > $wgMaximumMovedPages ) {
03708                                 $retval[$oldSubpage->getPrefixedTitle()] =
03709                                                 array( 'movepage-max-pages',
03710                                                         $wgMaximumMovedPages );
03711                                 break;
03712                         }
03713 
03714                         // We don't know whether this function was called before
03715                         // or after moving the root page, so check both
03716                         // $this and $nt
03717                         if ( $oldSubpage->getArticleId() == $this->getArticleId() ||
03718                                         $oldSubpage->getArticleID() == $nt->getArticleId() )
03719                         {
03720                                 // When moving a page to a subpage of itself,
03721                                 // don't move it twice
03722                                 continue;
03723                         }
03724                         $newPageName = preg_replace(
03725                                         '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
03726                                         StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03727                                         $oldSubpage->getDBkey() );
03728                         if ( $oldSubpage->isTalkPage() ) {
03729                                 $newNs = $nt->getTalkPage()->getNamespace();
03730                         } else {
03731                                 $newNs = $nt->getSubjectPage()->getNamespace();
03732                         }
03733                         # Bug 14385: we need makeTitleSafe because the new page names may
03734                         # be longer than 255 characters.
03735                         $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03736 
03737                         $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03738                         if ( $success === true ) {
03739                                 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03740                         } else {
03741                                 $retval[$oldSubpage->getPrefixedText()] = $success;
03742                         }
03743                 }
03744                 return $retval;
03745         }
03746 
03753         public function isSingleRevRedirect() {
03754                 $dbw = wfGetDB( DB_MASTER );
03755                 # Is it a redirect?
03756                 $row = $dbw->selectRow( 'page',
03757                         array( 'page_is_redirect', 'page_latest', 'page_id' ),
03758                         $this->pageCond(),
03759                         __METHOD__,
03760                         array( 'FOR UPDATE' )
03761                 );
03762                 # Cache some fields we may want
03763                 $this->mArticleID = $row ? intval( $row->page_id ) : 0;
03764                 $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03765                 $this->mLatestID = $row ? intval( $row->page_latest ) : false;
03766                 if ( !$this->mRedirect ) {
03767                         return false;
03768                 }
03769                 # Does the article have a history?
03770                 $row = $dbw->selectField( array( 'page', 'revision' ),
03771                         'rev_id',
03772                         array( 'page_namespace' => $this->getNamespace(),
03773                                 'page_title' => $this->getDBkey(),
03774                                 'page_id=rev_page',
03775                                 'page_latest != rev_id'
03776                         ),
03777                         __METHOD__,
03778                         array( 'FOR UPDATE' )
03779                 );
03780                 # Return true if there was no history
03781                 return ( $row === false );
03782         }
03783 
03791         public function isValidMoveTarget( $nt ) {
03792                 # Is it an existing file?
03793                 if ( $nt->getNamespace() == NS_FILE ) {
03794                         $file = wfLocalFile( $nt );
03795                         if ( $file->exists() ) {
03796                                 wfDebug( __METHOD__ . ": file exists\n" );
03797                                 return false;
03798                         }
03799                 }
03800                 # Is it a redirect with no history?
03801                 if ( !$nt->isSingleRevRedirect() ) {
03802                         wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03803                         return false;
03804                 }
03805                 # Get the article text
03806                 $rev = Revision::newFromTitle( $nt );
03807                 if( !is_object( $rev ) ){
03808                         return false;
03809                 }
03810                 $text = $rev->getText();
03811                 # Does the redirect point to the source?
03812                 # Or is it a broken self-redirect, usually caused by namespace collisions?
03813                 $m = array();
03814                 if ( preg_match( "/\\[\\[\\s*([^\\]\\|]*)]]/", $text, $m ) ) {
03815                         $redirTitle = Title::newFromText( $m[1] );
03816                         if ( !is_object( $redirTitle ) ||
03817                                 ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03818                                 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) ) {
03819                                 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03820                                 return false;
03821                         }
03822                 } else {
03823                         # Fail safe
03824                         wfDebug( __METHOD__ . ": failsafe\n" );
03825                         return false;
03826                 }
03827                 return true;
03828         }
03829 
03837         public function getParentCategories() {
03838                 global $wgContLang;
03839 
03840                 $data = array();
03841 
03842                 $titleKey = $this->getArticleId();
03843 
03844                 if ( $titleKey === 0 ) {
03845                         return $data;
03846                 }
03847 
03848                 $dbr = wfGetDB( DB_SLAVE );
03849 
03850                 $res = $dbr->select( 'categorylinks', '*',
03851                         array(
03852                                 'cl_from' => $titleKey,
03853                         ),
03854                         __METHOD__,
03855                         array()
03856                 );
03857 
03858                 if ( $dbr->numRows( $res ) > 0 ) {
03859                         foreach ( $res as $row ) {
03860                                 // $data[] = Title::newFromText($wgContLang->getNSText ( NS_CATEGORY ).':'.$row->cl_to);
03861                                 $data[$wgContLang->getNSText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
03862                         }
03863                 }
03864                 return $data;
03865         }
03866 
03873         public function getParentCategoryTree( $children = array() ) {
03874                 $stack = array();
03875                 $parents = $this->getParentCategories();
03876 
03877                 if ( $parents ) {
03878                         foreach ( $parents as $parent => $current ) {
03879                                 if ( array_key_exists( $parent, $children ) ) {
03880                                         # Circular reference
03881                                         $stack[$parent] = array();
03882                                 } else {
03883                                         $nt = Title::newFromText( $parent );
03884                                         if ( $nt ) {
03885                                                 $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
03886                                         }
03887                                 }
03888                         }
03889                 }
03890 
03891                 return $stack;
03892         }
03893 
03900         public function pageCond() {
03901                 if ( $this->mArticleID > 0 ) {
03902                         // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
03903                         return array( 'page_id' => $this->mArticleID );
03904                 } else {
03905                         return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
03906                 }
03907         }
03908 
03916         public function getPreviousRevisionID( $revId, $flags = 0 ) {
03917                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03918                 return $db->selectField( 'revision', 'rev_id',
03919                         array(
03920                                 'rev_page' => $this->getArticleId( $flags ),
03921                                 'rev_id < ' . intval( $revId )
03922                         ),
03923                         __METHOD__,
03924                         array( 'ORDER BY' => 'rev_id DESC' )
03925                 );
03926         }
03927 
03935         public function getNextRevisionID( $revId, $flags = 0 ) {
03936                 $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03937                 return $db->selectField( 'revision', 'rev_id',
03938                         array(
03939                                 'rev_page' => $this->getArticleId( $flags ),
03940                                 'rev_id > ' . intval( $revId )
03941                         ),
03942                         __METHOD__,
03943                         array( 'ORDER BY' => 'rev_id' )
03944                 );
03945         }
03946 
03953         public function getFirstRevision( $flags = 0 ) {
03954                 $pageId = $this->getArticleId( $flags );
03955                 if ( $pageId ) {
03956                         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
03957                         $row = $db->selectRow( 'revision', '*',
03958                                 array( 'rev_page' => $pageId ),
03959                                 __METHOD__,
03960                                 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
03961                         );
03962                         if ( $row ) {
03963                                 return new Revision( $row );
03964                         }
03965                 }
03966                 return null;
03967         }
03968 
03975         public function getEarliestRevTime( $flags = 0 ) {
03976                 $rev = $this->getFirstRevision( $flags );
03977                 return $rev ? $rev->getTimestamp() : null;
03978         }
03979 
03985         public function isNewPage() {
03986                 $dbr = wfGetDB( DB_SLAVE );
03987                 return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
03988         }
03989 
03995         public function isBigDeletion() {
03996                 global $wgDeleteRevisionsLimit;
03997 
03998                 if ( !$wgDeleteRevisionsLimit ) {
03999                         return false;
04000                 }
04001 
04002                 $revCount = $this->estimateRevisionCount();
04003                 return $revCount > $wgDeleteRevisionsLimit;
04004         }
04005 
04011         public function estimateRevisionCount() {
04012                 if ( !$this->exists() ) {
04013                         return 0;
04014                 }
04015 
04016                 if ( $this->mEstimateRevisions === null ) {
04017                         $dbr = wfGetDB( DB_SLAVE );
04018                         $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
04019                                 array( 'rev_page' => $this->getArticleId() ), __METHOD__ );
04020                 }
04021 
04022                 return $this->mEstimateRevisions;
04023         }
04024 
04033         public function countRevisionsBetween( $old, $new ) {
04034                 if ( !( $old instanceof Revision ) ) {
04035                         $old = Revision::newFromTitle( $this, (int)$old );
04036                 }
04037                 if ( !( $new instanceof Revision ) ) {
04038                         $new = Revision::newFromTitle( $this, (int)$new );
04039                 }
04040                 if ( !$old || !$new ) {
04041                         return 0; // nothing to compare
04042                 }
04043                 $dbr = wfGetDB( DB_SLAVE );
04044                 return (int)$dbr->selectField( 'revision', 'count(*)',
04045                         array(
04046                                 'rev_page' => $this->getArticleId(),
04047                                 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04048                                 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04049                         ),
04050                         __METHOD__
04051                 );
04052         }
04053 
04063         public function countAuthorsBetween( $old, $new, $limit ) {
04064                 if ( !( $old instanceof Revision ) ) {
04065                         $old = Revision::newFromTitle( $this, (int)$old );
04066                 }
04067                 if ( !( $new instanceof Revision ) ) {
04068                         $new = Revision::newFromTitle( $this, (int)$new );
04069                 }
04070                 if ( !$old || !$new ) {
04071                         return 0; // nothing to compare
04072                 }
04073                 $dbr = wfGetDB( DB_SLAVE );
04074                 $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
04075                         array(
04076                                 'rev_page' => $this->getArticleID(),
04077                                 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04078                                 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04079                         ), __METHOD__,
04080                         array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
04081                 );
04082                 return (int)$dbr->numRows( $res );
04083         }
04084 
04091         public function equals( Title $title ) {
04092                 // Note: === is necessary for proper matching of number-like titles.
04093                 return $this->getInterwiki() === $title->getInterwiki()
04094                         && $this->getNamespace() == $title->getNamespace()
04095                         && $this->getDBkey() === $title->getDBkey();
04096         }
04097 
04104         public function isSubpageOf( Title $title ) {
04105                 return $this->getInterwiki() === $title->getInterwiki()
04106                         && $this->getNamespace() == $title->getNamespace()
04107                         && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
04108         }
04109 
04119         public function exists() {
04120                 return $this->getArticleId() != 0;
04121         }
04122 
04139         public function isAlwaysKnown() {
04140                 if ( $this->mInterwiki != '' ) {
04141                         return true;  // any interwiki link might be viewable, for all we know
04142                 }
04143                 switch( $this->mNamespace ) {
04144                         case NS_MEDIA:
04145                         case NS_FILE:
04146                                 // file exists, possibly in a foreign repo
04147                                 return (bool)wfFindFile( $this );
04148                         case NS_SPECIAL:
04149                                 // valid special page
04150                                 return SpecialPageFactory::exists( $this->getDBkey() );
04151                         case NS_MAIN:
04152                                 // selflink, possibly with fragment
04153                                 return $this->mDbkeyform == '';
04154                         case NS_MEDIAWIKI:
04155                                 // known system message
04156                                 return $this->hasSourceText() !== false;
04157                         default:
04158                                 return false;
04159                 }
04160         }
04161 
04170         public function isKnown() {
04171                 return $this->isAlwaysKnown() || $this->exists();
04172         }
04173 
04179         public function hasSourceText() {
04180                 if ( $this->exists() ) {
04181                         return true;
04182                 }
04183 
04184                 if ( $this->mNamespace == NS_MEDIAWIKI ) {
04185                         // If the page doesn't exist but is a known system message, default
04186                         // message content will be displayed, same for language subpages-
04187                         // Use always content language to avoid loading hundreds of languages
04188                         // to get the link color.
04189                         global $wgContLang;
04190                         list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04191                         $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
04192                         return $message->exists();
04193                 }
04194 
04195                 return false;
04196         }
04197 
04203         public function getDefaultMessageText() {
04204                 global $wgContLang;
04205 
04206                 if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
04207                         return false;
04208                 }
04209 
04210                 list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
04211                 $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
04212 
04213                 if ( $message->exists() ) {
04214                         return $message->plain();
04215                 } else {
04216                         return false;
04217                 }
04218         }
04219 
04225         public function invalidateCache() {
04226                 if ( wfReadOnly() ) {
04227                         return false;
04228                 }
04229                 $dbw = wfGetDB( DB_MASTER );
04230                 $success = $dbw->update(
04231                         'page',
04232                         array( 'page_touched' => $dbw->timestamp() ),
04233                         $this->pageCond(),
04234                         __METHOD__
04235                 );
04236                 HTMLFileCache::clearFileCache( $this );
04237                 return $success;
04238         }
04239 
04245         public function touchLinks() {
04246                 $u = new HTMLCacheUpdate( $this, 'pagelinks' );
04247                 $u->doUpdate();
04248 
04249                 if ( $this->getNamespace() == NS_CATEGORY ) {
04250                         $u = new HTMLCacheUpdate( $this, 'categorylinks' );
04251                         $u->doUpdate();
04252                 }
04253         }
04254 
04261         public function getTouched( $db = null ) {
04262                 $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
04263                 $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
04264                 return $touched;
04265         }
04266 
04273         public function getNotificationTimestamp( $user = null ) {
04274                 global $wgUser, $wgShowUpdatedMarker;
04275                 // Assume current user if none given
04276                 if ( !$user ) {
04277                         $user = $wgUser;
04278                 }
04279                 // Check cache first
04280                 $uid = $user->getId();
04281                 // avoid isset here, as it'll return false for null entries
04282                 if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
04283                         return $this->mNotificationTimestamp[$uid];
04284                 }
04285                 if ( !$uid || !$wgShowUpdatedMarker ) {
04286                         return $this->mNotificationTimestamp[$uid] = false;
04287                 }
04288                 // Don't cache too much!
04289                 if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
04290                         $this->mNotificationTimestamp = array();
04291                 }
04292                 $dbr = wfGetDB( DB_SLAVE );
04293                 $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
04294                         'wl_notificationtimestamp',
04295                         array( 'wl_namespace' => $this->getNamespace(),
04296                                 'wl_title' => $this->getDBkey(),
04297                                 'wl_user' => $user->getId()
04298                         ),
04299                         __METHOD__
04300                 );
04301                 return $this->mNotificationTimestamp[$uid];
04302         }
04303 
04310         public function getNamespaceKey( $prepend = 'nstab-' ) {
04311                 global $wgContLang;
04312                 // Gets the subject namespace if this title
04313                 $namespace = MWNamespace::getSubject( $this->getNamespace() );
04314                 // Checks if cononical namespace name exists for namespace
04315                 if ( MWNamespace::exists( $this->getNamespace() ) ) {
04316                         // Uses canonical namespace name
04317                         $namespaceKey = MWNamespace::getCanonicalName( $namespace );
04318                 } else {
04319                         // Uses text of namespace
04320                         $namespaceKey = $this->getSubjectNsText();
04321                 }
04322                 // Makes namespace key lowercase
04323                 $namespaceKey = $wgContLang->lc( $namespaceKey );
04324                 // Uses main
04325                 if ( $namespaceKey == '' ) {
04326                         $namespaceKey = 'main';
04327                 }
04328                 // Changes file to image for backwards compatibility
04329                 if ( $namespaceKey == 'file' ) {
04330                         $namespaceKey = 'image';
04331                 }
04332                 return $prepend . $namespaceKey;
04333         }
04334 
04341         public function getRedirectsHere( $ns = null ) {
04342                 $redirs = array();
04343 
04344                 $dbr = wfGetDB( DB_SLAVE );
04345                 $where = array(
04346                         'rd_namespace' => $this->getNamespace(),
04347                         'rd_title' => $this->getDBkey(),
04348                         'rd_from = page_id'
04349                 );
04350                 if ( !is_null( $ns ) ) {
04351                         $where['page_namespace'] = $ns;
04352                 }
04353 
04354                 $res = $dbr->select(
04355                         array( 'redirect', 'page' ),
04356                         array( 'page_namespace', 'page_title' ),
04357                         $where,
04358                         __METHOD__
04359                 );
04360 
04361                 foreach ( $res as $row ) {
04362                         $redirs[] = self::newFromRow( $row );
04363                 }
04364                 return $redirs;
04365         }
04366 
04372         public function isValidRedirectTarget() {
04373                 global $wgInvalidRedirectTargets;
04374 
04375                 // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
04376                 if ( $this->isSpecial( 'Userlogout' ) ) {
04377                         return false;
04378                 }
04379 
04380                 foreach ( $wgInvalidRedirectTargets as $target ) {
04381                         if ( $this->isSpecial( $target ) ) {
04382                                 return false;
04383                         }
04384                 }
04385 
04386                 return true;
04387         }
04388 
04394         function getBacklinkCache() {
04395                 if ( is_null( $this->mBacklinkCache ) ) {
04396                         $this->mBacklinkCache = new BacklinkCache( $this );
04397                 }
04398                 return $this->mBacklinkCache;
04399         }
04400 
04406         public function canUseNoindex() {
04407                 global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
04408 
04409                 $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
04410                         ? $wgContentNamespaces
04411                         : $wgExemptFromUserRobotsControl;
04412 
04413                 return !in_array( $this->mNamespace, $bannedNamespaces );
04414 
04415         }
04416 
04427         public function getCategorySortkey( $prefix = '' ) {
04428                 $unprefixed = $this->getText();
04429 
04430                 // Anything that uses this hook should only depend
04431                 // on the Title object passed in, and should probably
04432                 // tell the users to run updateCollations.php --force
04433                 // in order to re-sort existing category relations.
04434                 wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
04435                 if ( $prefix !== '' ) {
04436                         # Separate with a line feed, so the unprefixed part is only used as
04437                         # a tiebreaker when two pages have the exact same prefix.
04438                         # In UCA, tab is the only character that can sort above LF
04439                         # so we strip both of them from the original prefix.
04440                         $prefix = strtr( $prefix, "\n\t", '  ' );
04441                         return "$prefix\n$unprefixed";
04442                 }
04443                 return $unprefixed;
04444         }
04445 
04454         public function getPageLanguage() {
04455                 global $wgLang;
04456                 if ( $this->isSpecialPage() ) {
04457                         // special pages are in the user language
04458                         return $wgLang;
04459                 } elseif ( $this->isCssOrJsPage() ) {
04460                         // css/js should always be LTR and is, in fact, English
04461                         return wfGetLangObj( 'en' );
04462                 } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
04463                         // Parse mediawiki messages with correct target language
04464                         list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
04465                         return wfGetLangObj( $lang );
04466                 }
04467                 global $wgContLang;
04468                 // If nothing special, it should be in the wiki content language
04469                 $pageLang = $wgContLang;
04470                 // Hook at the end because we don't want to override the above stuff
04471                 wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
04472                 return wfGetLangObj( $pageLang );
04473         }
04474 }