MediaWiki  REL1_24
Title.php
Go to the documentation of this file.
00001 <?php
00035 class Title {
00037     static private $titleCache = null;
00038 
00044     const CACHE_MAX = 1000;
00045 
00050     const GAID_FOR_UPDATE = 1;
00051 
00057     // @{
00058 
00060     public $mTextform = '';
00061 
00063     public $mUrlform = '';
00064 
00066     public $mDbkeyform = '';
00067 
00069     protected $mUserCaseDBKey;
00070 
00072     public $mNamespace = NS_MAIN;
00073 
00075     public $mInterwiki = '';
00076 
00078     private $mLocalInterwiki = false;
00079 
00081     public $mFragment = '';
00082 
00084     public $mArticleID = -1;
00085 
00087     protected $mLatestID = false;
00088 
00093     public $mContentModel = false;
00094 
00096     private $mEstimateRevisions;
00097 
00099     public $mRestrictions = array();
00100 
00102     protected $mOldRestrictions = false;
00103 
00105     public $mCascadeRestriction;
00106 
00108     public $mCascadingRestrictions;
00109 
00111     protected $mRestrictionsExpiry = array();
00112 
00114     protected $mHasCascadingRestrictions;
00115 
00117     public $mCascadeSources;
00118 
00120     public $mRestrictionsLoaded = false;
00121 
00123     protected $mPrefixedText = null;
00124 
00126     public $mTitleProtection;
00127 
00133     public $mDefaultNamespace = NS_MAIN;
00134 
00139     protected $mWatched = null;
00140 
00142     protected $mLength = -1;
00143 
00145     public $mRedirect = null;
00146 
00148     private $mNotificationTimestamp = array();
00149 
00151     private $mHasSubpages;
00152 
00154     private $mPageLanguage = false;
00155 
00157     private $mDbPageLanguage = null;
00158 
00160     private $mTitleValue = null;
00161 
00163     private $mIsBigDeletion = null;
00164     // @}
00165 
00174     private static function getTitleParser() {
00175         global $wgContLang, $wgLocalInterwikis;
00176 
00177         static $titleCodec = null;
00178         static $titleCodecFingerprint = null;
00179 
00180         // $wgContLang and $wgLocalInterwikis may change (especially while testing),
00181         // make sure we are using the right one. To detect changes over the course
00182         // of a request, we remember a fingerprint of the config used to create the
00183         // codec singleton, and re-create it if the fingerprint doesn't match.
00184         $fingerprint = spl_object_hash( $wgContLang ) . '|' . join( '+', $wgLocalInterwikis );
00185 
00186         if ( $fingerprint !== $titleCodecFingerprint ) {
00187             $titleCodec = null;
00188         }
00189 
00190         if ( !$titleCodec ) {
00191             $titleCodec = new MediaWikiTitleCodec(
00192                 $wgContLang,
00193                 GenderCache::singleton(),
00194                 $wgLocalInterwikis
00195             );
00196             $titleCodecFingerprint = $fingerprint;
00197         }
00198 
00199         return $titleCodec;
00200     }
00201 
00210     private static function getTitleFormatter() {
00211         //NOTE: we know that getTitleParser() returns a MediaWikiTitleCodec,
00212         //      which implements TitleFormatter.
00213         return self::getTitleParser();
00214     }
00215 
00216     function __construct() {
00217     }
00218 
00227     public static function newFromDBkey( $key ) {
00228         $t = new Title();
00229         $t->mDbkeyform = $key;
00230         if ( $t->secureAndSplit() ) {
00231             return $t;
00232         } else {
00233             return null;
00234         }
00235     }
00236 
00244     public static function newFromTitleValue( TitleValue $titleValue ) {
00245         return self::makeTitle(
00246             $titleValue->getNamespace(),
00247             $titleValue->getText(),
00248             $titleValue->getFragment() );
00249     }
00250 
00264     public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
00265         if ( is_object( $text ) ) {
00266             throw new MWException( 'Title::newFromText given an object' );
00267         }
00268 
00269         $cache = self::getTitleCache();
00270 
00279         if ( $defaultNamespace == NS_MAIN && $cache->has( $text ) ) {
00280             return $cache->get( $text );
00281         }
00282 
00283         # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
00284         $filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
00285 
00286         $t = new Title();
00287         $t->mDbkeyform = str_replace( ' ', '_', $filteredText );
00288         $t->mDefaultNamespace = intval( $defaultNamespace );
00289 
00290         if ( $t->secureAndSplit() ) {
00291             if ( $defaultNamespace == NS_MAIN ) {
00292                 $cache->set( $text, $t );
00293             }
00294             return $t;
00295         } else {
00296             return null;
00297         }
00298     }
00299 
00315     public static function newFromURL( $url ) {
00316         $t = new Title();
00317 
00318         # For compatibility with old buggy URLs. "+" is usually not valid in titles,
00319         # but some URLs used it as a space replacement and they still come
00320         # from some external search tools.
00321         if ( strpos( self::legalChars(), '+' ) === false ) {
00322             $url = str_replace( '+', ' ', $url );
00323         }
00324 
00325         $t->mDbkeyform = str_replace( ' ', '_', $url );
00326         if ( $t->secureAndSplit() ) {
00327             return $t;
00328         } else {
00329             return null;
00330         }
00331     }
00332 
00336     private static function getTitleCache() {
00337         if ( self::$titleCache == null ) {
00338             self::$titleCache = new MapCacheLRU( self::CACHE_MAX );
00339         }
00340         return self::$titleCache;
00341     }
00342 
00350     protected static function getSelectFields() {
00351         global $wgContentHandlerUseDB;
00352 
00353         $fields = array(
00354             'page_namespace', 'page_title', 'page_id',
00355             'page_len', 'page_is_redirect', 'page_latest',
00356         );
00357 
00358         if ( $wgContentHandlerUseDB ) {
00359             $fields[] = 'page_content_model';
00360         }
00361 
00362         return $fields;
00363     }
00364 
00372     public static function newFromID( $id, $flags = 0 ) {
00373         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
00374         $row = $db->selectRow(
00375             'page',
00376             self::getSelectFields(),
00377             array( 'page_id' => $id ),
00378             __METHOD__
00379         );
00380         if ( $row !== false ) {
00381             $title = Title::newFromRow( $row );
00382         } else {
00383             $title = null;
00384         }
00385         return $title;
00386     }
00387 
00394     public static function newFromIDs( $ids ) {
00395         if ( !count( $ids ) ) {
00396             return array();
00397         }
00398         $dbr = wfGetDB( DB_SLAVE );
00399 
00400         $res = $dbr->select(
00401             'page',
00402             self::getSelectFields(),
00403             array( 'page_id' => $ids ),
00404             __METHOD__
00405         );
00406 
00407         $titles = array();
00408         foreach ( $res as $row ) {
00409             $titles[] = Title::newFromRow( $row );
00410         }
00411         return $titles;
00412     }
00413 
00420     public static function newFromRow( $row ) {
00421         $t = self::makeTitle( $row->page_namespace, $row->page_title );
00422         $t->loadFromRow( $row );
00423         return $t;
00424     }
00425 
00432     public function loadFromRow( $row ) {
00433         if ( $row ) { // page found
00434             if ( isset( $row->page_id ) ) {
00435                 $this->mArticleID = (int)$row->page_id;
00436             }
00437             if ( isset( $row->page_len ) ) {
00438                 $this->mLength = (int)$row->page_len;
00439             }
00440             if ( isset( $row->page_is_redirect ) ) {
00441                 $this->mRedirect = (bool)$row->page_is_redirect;
00442             }
00443             if ( isset( $row->page_latest ) ) {
00444                 $this->mLatestID = (int)$row->page_latest;
00445             }
00446             if ( isset( $row->page_content_model ) ) {
00447                 $this->mContentModel = strval( $row->page_content_model );
00448             } else {
00449                 $this->mContentModel = false; # initialized lazily in getContentModel()
00450             }
00451             if ( isset( $row->page_lang ) ) {
00452                 $this->mDbPageLanguage = (string)$row->page_lang;
00453             }
00454         } else { // page not found
00455             $this->mArticleID = 0;
00456             $this->mLength = 0;
00457             $this->mRedirect = false;
00458             $this->mLatestID = 0;
00459             $this->mContentModel = false; # initialized lazily in getContentModel()
00460         }
00461     }
00462 
00476     public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
00477         $t = new Title();
00478         $t->mInterwiki = $interwiki;
00479         $t->mFragment = $fragment;
00480         $t->mNamespace = $ns = intval( $ns );
00481         $t->mDbkeyform = str_replace( ' ', '_', $title );
00482         $t->mArticleID = ( $ns >= 0 ) ? -1 : 0;
00483         $t->mUrlform = wfUrlencode( $t->mDbkeyform );
00484         $t->mTextform = str_replace( '_', ' ', $title );
00485         $t->mContentModel = false; # initialized lazily in getContentModel()
00486         return $t;
00487     }
00488 
00500     public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
00501         if ( !MWNamespace::exists( $ns ) ) {
00502             return null;
00503         }
00504 
00505         $t = new Title();
00506         $t->mDbkeyform = Title::makeName( $ns, $title, $fragment, $interwiki );
00507         if ( $t->secureAndSplit() ) {
00508             return $t;
00509         } else {
00510             return null;
00511         }
00512     }
00513 
00519     public static function newMainPage() {
00520         $title = Title::newFromText( wfMessage( 'mainpage' )->inContentLanguage()->text() );
00521         // Don't give fatal errors if the message is broken
00522         if ( !$title ) {
00523             $title = Title::newFromText( 'Main Page' );
00524         }
00525         return $title;
00526     }
00527 
00538     public static function newFromRedirect( $text ) {
00539         ContentHandler::deprecated( __METHOD__, '1.21' );
00540 
00541         $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00542         return $content->getRedirectTarget();
00543     }
00544 
00555     public static function newFromRedirectRecurse( $text ) {
00556         ContentHandler::deprecated( __METHOD__, '1.21' );
00557 
00558         $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00559         return $content->getUltimateRedirectTarget();
00560     }
00561 
00572     public static function newFromRedirectArray( $text ) {
00573         ContentHandler::deprecated( __METHOD__, '1.21' );
00574 
00575         $content = ContentHandler::makeContent( $text, null, CONTENT_MODEL_WIKITEXT );
00576         return $content->getRedirectChain();
00577     }
00578 
00585     public static function nameOf( $id ) {
00586         $dbr = wfGetDB( DB_SLAVE );
00587 
00588         $s = $dbr->selectRow(
00589             'page',
00590             array( 'page_namespace', 'page_title' ),
00591             array( 'page_id' => $id ),
00592             __METHOD__
00593         );
00594         if ( $s === false ) {
00595             return null;
00596         }
00597 
00598         $n = self::makeName( $s->page_namespace, $s->page_title );
00599         return $n;
00600     }
00601 
00607     public static function legalChars() {
00608         global $wgLegalTitleChars;
00609         return $wgLegalTitleChars;
00610     }
00611 
00621     static function getTitleInvalidRegex() {
00622         static $rxTc = false;
00623         if ( !$rxTc ) {
00624             # Matching titles will be held as illegal.
00625             $rxTc = '/' .
00626                 # Any character not allowed is forbidden...
00627                 '[^' . self::legalChars() . ']' .
00628                 # URL percent encoding sequences interfere with the ability
00629                 # to round-trip titles -- you can't link to them consistently.
00630                 '|%[0-9A-Fa-f]{2}' .
00631                 # XML/HTML character references produce similar issues.
00632                 '|&[A-Za-z0-9\x80-\xff]+;' .
00633                 '|&#[0-9]+;' .
00634                 '|&#x[0-9A-Fa-f]+;' .
00635                 '/S';
00636         }
00637 
00638         return $rxTc;
00639     }
00640 
00650     public static function convertByteClassToUnicodeClass( $byteClass ) {
00651         $length = strlen( $byteClass );
00652         // Input token queue
00653         $x0 = $x1 = $x2 = '';
00654         // Decoded queue
00655         $d0 = $d1 = $d2 = '';
00656         // Decoded integer codepoints
00657         $ord0 = $ord1 = $ord2 = 0;
00658         // Re-encoded queue
00659         $r0 = $r1 = $r2 = '';
00660         // Output
00661         $out = '';
00662         // Flags
00663         $allowUnicode = false;
00664         for ( $pos = 0; $pos < $length; $pos++ ) {
00665             // Shift the queues down
00666             $x2 = $x1;
00667             $x1 = $x0;
00668             $d2 = $d1;
00669             $d1 = $d0;
00670             $ord2 = $ord1;
00671             $ord1 = $ord0;
00672             $r2 = $r1;
00673             $r1 = $r0;
00674             // Load the current input token and decoded values
00675             $inChar = $byteClass[$pos];
00676             if ( $inChar == '\\' ) {
00677                 if ( preg_match( '/x([0-9a-fA-F]{2})/A', $byteClass, $m, 0, $pos + 1 ) ) {
00678                     $x0 = $inChar . $m[0];
00679                     $d0 = chr( hexdec( $m[1] ) );
00680                     $pos += strlen( $m[0] );
00681                 } elseif ( preg_match( '/[0-7]{3}/A', $byteClass, $m, 0, $pos + 1 ) ) {
00682                     $x0 = $inChar . $m[0];
00683                     $d0 = chr( octdec( $m[0] ) );
00684                     $pos += strlen( $m[0] );
00685                 } elseif ( $pos + 1 >= $length ) {
00686                     $x0 = $d0 = '\\';
00687                 } else {
00688                     $d0 = $byteClass[$pos + 1];
00689                     $x0 = $inChar . $d0;
00690                     $pos += 1;
00691                 }
00692             } else {
00693                 $x0 = $d0 = $inChar;
00694             }
00695             $ord0 = ord( $d0 );
00696             // Load the current re-encoded value
00697             if ( $ord0 < 32 || $ord0 == 0x7f ) {
00698                 $r0 = sprintf( '\x%02x', $ord0 );
00699             } elseif ( $ord0 >= 0x80 ) {
00700                 // Allow unicode if a single high-bit character appears
00701                 $r0 = sprintf( '\x%02x', $ord0 );
00702                 $allowUnicode = true;
00703             } elseif ( strpos( '-\\[]^', $d0 ) !== false ) {
00704                 $r0 = '\\' . $d0;
00705             } else {
00706                 $r0 = $d0;
00707             }
00708             // Do the output
00709             if ( $x0 !== '' && $x1 === '-' && $x2 !== '' ) {
00710                 // Range
00711                 if ( $ord2 > $ord0 ) {
00712                     // Empty range
00713                 } elseif ( $ord0 >= 0x80 ) {
00714                     // Unicode range
00715                     $allowUnicode = true;
00716                     if ( $ord2 < 0x80 ) {
00717                         // Keep the non-unicode section of the range
00718                         $out .= "$r2-\\x7F";
00719                     }
00720                 } else {
00721                     // Normal range
00722                     $out .= "$r2-$r0";
00723                 }
00724                 // Reset state to the initial value
00725                 $x0 = $x1 = $d0 = $d1 = $r0 = $r1 = '';
00726             } elseif ( $ord2 < 0x80 ) {
00727                 // ASCII character
00728                 $out .= $r2;
00729             }
00730         }
00731         if ( $ord1 < 0x80 ) {
00732             $out .= $r1;
00733         }
00734         if ( $ord0 < 0x80 ) {
00735             $out .= $r0;
00736         }
00737         if ( $allowUnicode ) {
00738             $out .= '\u0080-\uFFFF';
00739         }
00740         return $out;
00741     }
00742 
00752     public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
00753         global $wgContLang;
00754 
00755         $namespace = $wgContLang->getNsText( $ns );
00756         $name = $namespace == '' ? $title : "$namespace:$title";
00757         if ( strval( $interwiki ) != '' ) {
00758             $name = "$interwiki:$name";
00759         }
00760         if ( strval( $fragment ) != '' ) {
00761             $name .= '#' . $fragment;
00762         }
00763         return $name;
00764     }
00765 
00772     static function escapeFragmentForURL( $fragment ) {
00773         # Note that we don't urlencode the fragment.  urlencoded Unicode
00774         # fragments appear not to work in IE (at least up to 7) or in at least
00775         # one version of Opera 9.x.  The W3C validator, for one, doesn't seem
00776         # to care if they aren't encoded.
00777         return Sanitizer::escapeId( $fragment, 'noninitial' );
00778     }
00779 
00788     public static function compare( $a, $b ) {
00789         if ( $a->getNamespace() == $b->getNamespace() ) {
00790             return strcmp( $a->getText(), $b->getText() );
00791         } else {
00792             return $a->getNamespace() - $b->getNamespace();
00793         }
00794     }
00795 
00802     public function isLocal() {
00803         if ( $this->isExternal() ) {
00804             $iw = Interwiki::fetch( $this->mInterwiki );
00805             if ( $iw ) {
00806                 return $iw->isLocal();
00807             }
00808         }
00809         return true;
00810     }
00811 
00817     public function isExternal() {
00818         return $this->mInterwiki !== '';
00819     }
00820 
00828     public function getInterwiki() {
00829         return $this->mInterwiki;
00830     }
00831 
00837     public function wasLocalInterwiki() {
00838         return $this->mLocalInterwiki;
00839     }
00840 
00847     public function isTrans() {
00848         if ( !$this->isExternal() ) {
00849             return false;
00850         }
00851 
00852         return Interwiki::fetch( $this->mInterwiki )->isTranscludable();
00853     }
00854 
00860     public function getTransWikiID() {
00861         if ( !$this->isExternal() ) {
00862             return false;
00863         }
00864 
00865         return Interwiki::fetch( $this->mInterwiki )->getWikiID();
00866     }
00867 
00877     public function getTitleValue() {
00878         if ( $this->mTitleValue === null ) {
00879             try {
00880                 $this->mTitleValue = new TitleValue(
00881                     $this->getNamespace(),
00882                     $this->getDBkey(),
00883                     $this->getFragment() );
00884             } catch ( InvalidArgumentException $ex ) {
00885                 wfDebug( __METHOD__ . ': Can\'t create a TitleValue for [[' .
00886                     $this->getPrefixedText() . ']]: ' . $ex->getMessage() . "\n" );
00887             }
00888         }
00889 
00890         return $this->mTitleValue;
00891     }
00892 
00898     public function getText() {
00899         return $this->mTextform;
00900     }
00901 
00907     public function getPartialURL() {
00908         return $this->mUrlform;
00909     }
00910 
00916     public function getDBkey() {
00917         return $this->mDbkeyform;
00918     }
00919 
00925     function getUserCaseDBKey() {
00926         if ( !is_null( $this->mUserCaseDBKey ) ) {
00927             return $this->mUserCaseDBKey;
00928         } else {
00929             // If created via makeTitle(), $this->mUserCaseDBKey is not set.
00930             return $this->mDbkeyform;
00931         }
00932     }
00933 
00939     public function getNamespace() {
00940         return $this->mNamespace;
00941     }
00942 
00950     public function getContentModel( $flags = 0 ) {
00951         # Calling getArticleID() loads the field from cache as needed
00952         if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
00953             $linkCache = LinkCache::singleton();
00954             $this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
00955         }
00956 
00957         if ( !$this->mContentModel ) {
00958             $this->mContentModel = ContentHandler::getDefaultModelFor( $this );
00959         }
00960 
00961         if ( !$this->mContentModel ) {
00962             throw new MWException( 'Failed to determine content model!' );
00963         }
00964 
00965         return $this->mContentModel;
00966     }
00967 
00974     public function hasContentModel( $id ) {
00975         return $this->getContentModel() == $id;
00976     }
00977 
00983     public function getNsText() {
00984         if ( $this->isExternal() ) {
00985             // This probably shouldn't even happen. ohh man, oh yuck.
00986             // But for interwiki transclusion it sometimes does.
00987             // Shit. Shit shit shit.
00988             //
00989             // Use the canonical namespaces if possible to try to
00990             // resolve a foreign namespace.
00991             if ( MWNamespace::exists( $this->mNamespace ) ) {
00992                 return MWNamespace::getCanonicalName( $this->mNamespace );
00993             }
00994         }
00995 
00996         try {
00997             $formatter = self::getTitleFormatter();
00998             return $formatter->getNamespaceName( $this->mNamespace, $this->mDbkeyform );
00999         } catch ( InvalidArgumentException $ex ) {
01000             wfDebug( __METHOD__ . ': ' . $ex->getMessage() . "\n" );
01001             return false;
01002         }
01003     }
01004 
01010     public function getSubjectNsText() {
01011         global $wgContLang;
01012         return $wgContLang->getNsText( MWNamespace::getSubject( $this->mNamespace ) );
01013     }
01014 
01020     public function getTalkNsText() {
01021         global $wgContLang;
01022         return $wgContLang->getNsText( MWNamespace::getTalk( $this->mNamespace ) );
01023     }
01024 
01030     public function canTalk() {
01031         return MWNamespace::canTalk( $this->mNamespace );
01032     }
01033 
01040     public function canExist() {
01041         return $this->mNamespace >= NS_MAIN;
01042     }
01043 
01049     public function isWatchable() {
01050         return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
01051     }
01052 
01058     public function isSpecialPage() {
01059         return $this->getNamespace() == NS_SPECIAL;
01060     }
01061 
01068     public function isSpecial( $name ) {
01069         if ( $this->isSpecialPage() ) {
01070             list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
01071             if ( $name == $thisName ) {
01072                 return true;
01073             }
01074         }
01075         return false;
01076     }
01077 
01084     public function fixSpecialName() {
01085         if ( $this->isSpecialPage() ) {
01086             list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
01087             if ( $canonicalName ) {
01088                 $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
01089                 if ( $localName != $this->mDbkeyform ) {
01090                     return Title::makeTitle( NS_SPECIAL, $localName );
01091                 }
01092             }
01093         }
01094         return $this;
01095     }
01096 
01107     public function inNamespace( $ns ) {
01108         return MWNamespace::equals( $this->getNamespace(), $ns );
01109     }
01110 
01118     public function inNamespaces( /* ... */ ) {
01119         $namespaces = func_get_args();
01120         if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
01121             $namespaces = $namespaces[0];
01122         }
01123 
01124         foreach ( $namespaces as $ns ) {
01125             if ( $this->inNamespace( $ns ) ) {
01126                 return true;
01127             }
01128         }
01129 
01130         return false;
01131     }
01132 
01146     public function hasSubjectNamespace( $ns ) {
01147         return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
01148     }
01149 
01157     public function isContentPage() {
01158         return MWNamespace::isContent( $this->getNamespace() );
01159     }
01160 
01167     public function isMovable() {
01168         if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->isExternal() ) {
01169             // Interwiki title or immovable namespace. Hooks don't get to override here
01170             return false;
01171         }
01172 
01173         $result = true;
01174         wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
01175         return $result;
01176     }
01177 
01188     public function isMainPage() {
01189         return $this->equals( Title::newMainPage() );
01190     }
01191 
01197     public function isSubpage() {
01198         return MWNamespace::hasSubpages( $this->mNamespace )
01199             ? strpos( $this->getText(), '/' ) !== false
01200             : false;
01201     }
01202 
01208     public function isConversionTable() {
01209         // @todo ConversionTable should become a separate content model.
01210 
01211         return $this->getNamespace() == NS_MEDIAWIKI &&
01212             strpos( $this->getText(), 'Conversiontable/' ) === 0;
01213     }
01214 
01220     public function isWikitextPage() {
01221         return $this->hasContentModel( CONTENT_MODEL_WIKITEXT );
01222     }
01223 
01237     public function isCssOrJsPage() {
01238         $isCssOrJsPage = NS_MEDIAWIKI == $this->mNamespace
01239             && ( $this->hasContentModel( CONTENT_MODEL_CSS )
01240                 || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
01241 
01242         # @note This hook is also called in ContentHandler::getDefaultModel.
01243         #   It's called here again to make sure hook functions can force this
01244         #   method to return true even outside the mediawiki namespace.
01245 
01246         wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$isCssOrJsPage ) );
01247 
01248         return $isCssOrJsPage;
01249     }
01250 
01255     public function isCssJsSubpage() {
01256         return ( NS_USER == $this->mNamespace && $this->isSubpage()
01257                 && ( $this->hasContentModel( CONTENT_MODEL_CSS )
01258                     || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
01259     }
01260 
01266     public function getSkinFromCssJsSubpage() {
01267         $subpage = explode( '/', $this->mTextform );
01268         $subpage = $subpage[count( $subpage ) - 1];
01269         $lastdot = strrpos( $subpage, '.' );
01270         if ( $lastdot === false ) {
01271             return $subpage; # Never happens: only called for names ending in '.css' or '.js'
01272         }
01273         return substr( $subpage, 0, $lastdot );
01274     }
01275 
01281     public function isCssSubpage() {
01282         return ( NS_USER == $this->mNamespace && $this->isSubpage()
01283             && $this->hasContentModel( CONTENT_MODEL_CSS ) );
01284     }
01285 
01291     public function isJsSubpage() {
01292         return ( NS_USER == $this->mNamespace && $this->isSubpage()
01293             && $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) );
01294     }
01295 
01301     public function isTalkPage() {
01302         return MWNamespace::isTalk( $this->getNamespace() );
01303     }
01304 
01310     public function getTalkPage() {
01311         return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
01312     }
01313 
01320     public function getSubjectPage() {
01321         // Is this the same title?
01322         $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
01323         if ( $this->getNamespace() == $subjectNS ) {
01324             return $this;
01325         }
01326         return Title::makeTitle( $subjectNS, $this->getDBkey() );
01327     }
01328 
01334     public function getDefaultNamespace() {
01335         return $this->mDefaultNamespace;
01336     }
01337 
01345     public function getFragment() {
01346         return $this->mFragment;
01347     }
01348 
01355     public function hasFragment() {
01356         return $this->mFragment !== '';
01357     }
01358 
01363     public function getFragmentForURL() {
01364         if ( !$this->hasFragment() ) {
01365             return '';
01366         } else {
01367             return '#' . Title::escapeFragmentForURL( $this->getFragment() );
01368         }
01369     }
01370 
01381     public function setFragment( $fragment ) {
01382         $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
01383     }
01384 
01392     private function prefix( $name ) {
01393         $p = '';
01394         if ( $this->isExternal() ) {
01395             $p = $this->mInterwiki . ':';
01396         }
01397 
01398         if ( 0 != $this->mNamespace ) {
01399             $p .= $this->getNsText() . ':';
01400         }
01401         return $p . $name;
01402     }
01403 
01410     public function getPrefixedDBkey() {
01411         $s = $this->prefix( $this->mDbkeyform );
01412         $s = str_replace( ' ', '_', $s );
01413         return $s;
01414     }
01415 
01422     public function getPrefixedText() {
01423         if ( $this->mPrefixedText === null ) {
01424             $s = $this->prefix( $this->mTextform );
01425             $s = str_replace( '_', ' ', $s );
01426             $this->mPrefixedText = $s;
01427         }
01428         return $this->mPrefixedText;
01429     }
01430 
01436     public function __toString() {
01437         return $this->getPrefixedText();
01438     }
01439 
01446     public function getFullText() {
01447         $text = $this->getPrefixedText();
01448         if ( $this->hasFragment() ) {
01449             $text .= '#' . $this->getFragment();
01450         }
01451         return $text;
01452     }
01453 
01466     public function getRootText() {
01467         if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01468             return $this->getText();
01469         }
01470 
01471         return strtok( $this->getText(), '/' );
01472     }
01473 
01486     public function getRootTitle() {
01487         return Title::makeTitle( $this->getNamespace(), $this->getRootText() );
01488     }
01489 
01501     public function getBaseText() {
01502         if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01503             return $this->getText();
01504         }
01505 
01506         $parts = explode( '/', $this->getText() );
01507         # Don't discard the real title if there's no subpage involved
01508         if ( count( $parts ) > 1 ) {
01509             unset( $parts[count( $parts ) - 1] );
01510         }
01511         return implode( '/', $parts );
01512     }
01513 
01526     public function getBaseTitle() {
01527         return Title::makeTitle( $this->getNamespace(), $this->getBaseText() );
01528     }
01529 
01541     public function getSubpageText() {
01542         if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
01543             return $this->mTextform;
01544         }
01545         $parts = explode( '/', $this->mTextform );
01546         return $parts[count( $parts ) - 1];
01547     }
01548 
01562     public function getSubpage( $text ) {
01563         return Title::makeTitleSafe( $this->getNamespace(), $this->getText() . '/' . $text );
01564     }
01565 
01571     public function getSubpageUrlForm() {
01572         $text = $this->getSubpageText();
01573         $text = wfUrlencode( str_replace( ' ', '_', $text ) );
01574         return $text;
01575     }
01576 
01582     public function getPrefixedURL() {
01583         $s = $this->prefix( $this->mDbkeyform );
01584         $s = wfUrlencode( str_replace( ' ', '_', $s ) );
01585         return $s;
01586     }
01587 
01601     private static function fixUrlQueryArgs( $query, $query2 = false ) {
01602         if ( $query2 !== false ) {
01603             wfDeprecated( "Title::get{Canonical,Full,Link,Local,Internal}URL " .
01604                 "method called with a second parameter is deprecated. Add your " .
01605                 "parameter to an array passed as the first parameter.", "1.19" );
01606         }
01607         if ( is_array( $query ) ) {
01608             $query = wfArrayToCgi( $query );
01609         }
01610         if ( $query2 ) {
01611             if ( is_string( $query2 ) ) {
01612                 // $query2 is a string, we will consider this to be
01613                 // a deprecated $variant argument and add it to the query
01614                 $query2 = wfArrayToCgi( array( 'variant' => $query2 ) );
01615             } else {
01616                 $query2 = wfArrayToCgi( $query2 );
01617             }
01618             // If we have $query content add a & to it first
01619             if ( $query ) {
01620                 $query .= '&';
01621             }
01622             // Now append the queries together
01623             $query .= $query2;
01624         }
01625         return $query;
01626     }
01627 
01639     public function getFullURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01640         $query = self::fixUrlQueryArgs( $query, $query2 );
01641 
01642         # Hand off all the decisions on urls to getLocalURL
01643         $url = $this->getLocalURL( $query );
01644 
01645         # Expand the url to make it a full url. Note that getLocalURL has the
01646         # potential to output full urls for a variety of reasons, so we use
01647         # wfExpandUrl instead of simply prepending $wgServer
01648         $url = wfExpandUrl( $url, $proto );
01649 
01650         # Finally, add the fragment.
01651         $url .= $this->getFragmentForURL();
01652 
01653         wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
01654         return $url;
01655     }
01656 
01680     public function getLocalURL( $query = '', $query2 = false ) {
01681         global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
01682 
01683         $query = self::fixUrlQueryArgs( $query, $query2 );
01684 
01685         $interwiki = Interwiki::fetch( $this->mInterwiki );
01686         if ( $interwiki ) {
01687             $namespace = $this->getNsText();
01688             if ( $namespace != '' ) {
01689                 # Can this actually happen? Interwikis shouldn't be parsed.
01690                 # Yes! It can in interwiki transclusion. But... it probably shouldn't.
01691                 $namespace .= ':';
01692             }
01693             $url = $interwiki->getURL( $namespace . $this->getDBkey() );
01694             $url = wfAppendQuery( $url, $query );
01695         } else {
01696             $dbkey = wfUrlencode( $this->getPrefixedDBkey() );
01697             if ( $query == '' ) {
01698                 $url = str_replace( '$1', $dbkey, $wgArticlePath );
01699                 wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
01700             } else {
01701                 global $wgVariantArticlePath, $wgActionPaths, $wgContLang;
01702                 $url = false;
01703                 $matches = array();
01704 
01705                 if ( !empty( $wgActionPaths )
01706                     && preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches )
01707                 ) {
01708                     $action = urldecode( $matches[2] );
01709                     if ( isset( $wgActionPaths[$action] ) ) {
01710                         $query = $matches[1];
01711                         if ( isset( $matches[4] ) ) {
01712                             $query .= $matches[4];
01713                         }
01714                         $url = str_replace( '$1', $dbkey, $wgActionPaths[$action] );
01715                         if ( $query != '' ) {
01716                             $url = wfAppendQuery( $url, $query );
01717                         }
01718                     }
01719                 }
01720 
01721                 if ( $url === false
01722                     && $wgVariantArticlePath
01723                     && $wgContLang->getCode() === $this->getPageLanguage()->getCode()
01724                     && $this->getPageLanguage()->hasVariants()
01725                     && preg_match( '/^variant=([^&]*)$/', $query, $matches )
01726                 ) {
01727                     $variant = urldecode( $matches[1] );
01728                     if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
01729                         // Only do the variant replacement if the given variant is a valid
01730                         // variant for the page's language.
01731                         $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
01732                         $url = str_replace( '$1', $dbkey, $url );
01733                     }
01734                 }
01735 
01736                 if ( $url === false ) {
01737                     if ( $query == '-' ) {
01738                         $query = '';
01739                     }
01740                     $url = "{$wgScript}?title={$dbkey}&{$query}";
01741                 }
01742             }
01743 
01744             wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
01745 
01746             // @todo FIXME: This causes breakage in various places when we
01747             // actually expected a local URL and end up with dupe prefixes.
01748             if ( $wgRequest->getVal( 'action' ) == 'render' ) {
01749                 $url = $wgServer . $url;
01750             }
01751         }
01752         wfRunHooks( 'GetLocalURL', array( &$this, &$url, $query ) );
01753         return $url;
01754     }
01755 
01772     public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
01773         wfProfileIn( __METHOD__ );
01774         if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
01775             $ret = $this->getFullURL( $query, $query2, $proto );
01776         } elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
01777             $ret = $this->getFragmentForURL();
01778         } else {
01779             $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
01780         }
01781         wfProfileOut( __METHOD__ );
01782         return $ret;
01783     }
01784 
01797     public function getInternalURL( $query = '', $query2 = false ) {
01798         global $wgInternalServer, $wgServer;
01799         $query = self::fixUrlQueryArgs( $query, $query2 );
01800         $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
01801         $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
01802         wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
01803         return $url;
01804     }
01805 
01817     public function getCanonicalURL( $query = '', $query2 = false ) {
01818         $query = self::fixUrlQueryArgs( $query, $query2 );
01819         $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
01820         wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
01821         return $url;
01822     }
01823 
01829     public function getEditURL() {
01830         if ( $this->isExternal() ) {
01831             return '';
01832         }
01833         $s = $this->getLocalURL( 'action=edit' );
01834 
01835         return $s;
01836     }
01837 
01844     public function userIsWatching() {
01845         global $wgUser;
01846 
01847         if ( is_null( $this->mWatched ) ) {
01848             if ( NS_SPECIAL == $this->mNamespace || !$wgUser->isLoggedIn() ) {
01849                 $this->mWatched = false;
01850             } else {
01851                 $this->mWatched = $wgUser->isWatched( $this );
01852             }
01853         }
01854         return $this->mWatched;
01855     }
01856 
01871     public function quickUserCan( $action, $user = null ) {
01872         return $this->userCan( $action, $user, false );
01873     }
01874 
01885     public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
01886         if ( !$user instanceof User ) {
01887             global $wgUser;
01888             $user = $wgUser;
01889         }
01890 
01891         return !count( $this->getUserPermissionsErrorsInternal(
01892             $action, $user, $doExpensiveQueries, true ) );
01893     }
01894 
01908     public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true,
01909         $ignoreErrors = array()
01910     ) {
01911         $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
01912 
01913         // Remove the errors being ignored.
01914         foreach ( $errors as $index => $error ) {
01915             $error_key = is_array( $error ) ? $error[0] : $error;
01916 
01917             if ( in_array( $error_key, $ignoreErrors ) ) {
01918                 unset( $errors[$index] );
01919             }
01920         }
01921 
01922         return $errors;
01923     }
01924 
01936     private function checkQuickPermissions( $action, $user, $errors,
01937         $doExpensiveQueries, $short
01938     ) {
01939         if ( !wfRunHooks( 'TitleQuickPermissions',
01940             array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
01941         ) {
01942             return $errors;
01943         }
01944 
01945         if ( $action == 'create' ) {
01946             if (
01947                 ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
01948                 ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) )
01949             ) {
01950                 $errors[] = $user->isAnon() ? array( 'nocreatetext' ) : array( 'nocreate-loggedin' );
01951             }
01952         } elseif ( $action == 'move' ) {
01953             if ( !$user->isAllowed( 'move-rootuserpages' )
01954                     && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01955                 // Show user page-specific message only if the user can move other pages
01956                 $errors[] = array( 'cant-move-user-page' );
01957             }
01958 
01959             // Check if user is allowed to move files if it's a file
01960             if ( $this->mNamespace == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
01961                 $errors[] = array( 'movenotallowedfile' );
01962             }
01963 
01964             // Check if user is allowed to move category pages if it's a category page
01965             if ( $this->mNamespace == NS_CATEGORY && !$user->isAllowed( 'move-categorypages' ) ) {
01966                 $errors[] = array( 'cant-move-category-page' );
01967             }
01968 
01969             if ( !$user->isAllowed( 'move' ) ) {
01970                 // User can't move anything
01971                 $userCanMove = User::groupHasPermission( 'user', 'move' );
01972                 $autoconfirmedCanMove = User::groupHasPermission( 'autoconfirmed', 'move' );
01973                 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
01974                     // custom message if logged-in users without any special rights can move
01975                     $errors[] = array( 'movenologintext' );
01976                 } else {
01977                     $errors[] = array( 'movenotallowed' );
01978                 }
01979             }
01980         } elseif ( $action == 'move-target' ) {
01981             if ( !$user->isAllowed( 'move' ) ) {
01982                 // User can't move anything
01983                 $errors[] = array( 'movenotallowed' );
01984             } elseif ( !$user->isAllowed( 'move-rootuserpages' )
01985                     && $this->mNamespace == NS_USER && !$this->isSubpage() ) {
01986                 // Show user page-specific message only if the user can move other pages
01987                 $errors[] = array( 'cant-move-to-user-page' );
01988             } elseif ( !$user->isAllowed( 'move-categorypages' )
01989                     && $this->mNamespace == NS_CATEGORY ) {
01990                 // Show category page-specific message only if the user can move other pages
01991                 $errors[] = array( 'cant-move-to-category-page' );
01992             }
01993         } elseif ( !$user->isAllowed( $action ) ) {
01994             $errors[] = $this->missingPermissionError( $action, $short );
01995         }
01996 
01997         return $errors;
01998     }
01999 
02008     private function resultToError( $errors, $result ) {
02009         if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
02010             // A single array representing an error
02011             $errors[] = $result;
02012         } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
02013             // A nested array representing multiple errors
02014             $errors = array_merge( $errors, $result );
02015         } elseif ( $result !== '' && is_string( $result ) ) {
02016             // A string representing a message-id
02017             $errors[] = array( $result );
02018         } elseif ( $result === false ) {
02019             // a generic "We don't want them to do that"
02020             $errors[] = array( 'badaccess-group0' );
02021         }
02022         return $errors;
02023     }
02024 
02036     private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
02037         // Use getUserPermissionsErrors instead
02038         $result = '';
02039         if ( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
02040             return $result ? array() : array( array( 'badaccess-group0' ) );
02041         }
02042         // Check getUserPermissionsErrors hook
02043         if ( !wfRunHooks( 'getUserPermissionsErrors', array( &$this, &$user, $action, &$result ) ) ) {
02044             $errors = $this->resultToError( $errors, $result );
02045         }
02046         // Check getUserPermissionsErrorsExpensive hook
02047         if (
02048             $doExpensiveQueries
02049             && !( $short && count( $errors ) > 0 )
02050             && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
02051         ) {
02052             $errors = $this->resultToError( $errors, $result );
02053         }
02054 
02055         return $errors;
02056     }
02057 
02069     private function checkSpecialsAndNSPermissions( $action, $user, $errors,
02070         $doExpensiveQueries, $short
02071     ) {
02072         # Only 'createaccount' can be performed on special pages,
02073         # which don't actually exist in the DB.
02074         if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
02075             $errors[] = array( 'ns-specialprotected' );
02076         }
02077 
02078         # Check $wgNamespaceProtection for restricted namespaces
02079         if ( $this->isNamespaceProtected( $user ) ) {
02080             $ns = $this->mNamespace == NS_MAIN ?
02081                 wfMessage( 'nstab-main' )->text() : $this->getNsText();
02082             $errors[] = $this->mNamespace == NS_MEDIAWIKI ?
02083                 array( 'protectedinterface', $action ) : array( 'namespaceprotected', $ns, $action );
02084         }
02085 
02086         return $errors;
02087     }
02088 
02100     private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
02101         # Protect css/js subpages of user pages
02102         # XXX: this might be better using restrictions
02103         # XXX: right 'editusercssjs' is deprecated, for backward compatibility only
02104         if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' ) ) {
02105             if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
02106                 if ( $this->isCssSubpage() && !$user->isAllowedAny( 'editmyusercss', 'editusercss' ) ) {
02107                     $errors[] = array( 'mycustomcssprotected', $action );
02108                 } elseif ( $this->isJsSubpage() && !$user->isAllowedAny( 'editmyuserjs', 'edituserjs' ) ) {
02109                     $errors[] = array( 'mycustomjsprotected', $action );
02110                 }
02111             } else {
02112                 if ( $this->isCssSubpage() && !$user->isAllowed( 'editusercss' ) ) {
02113                     $errors[] = array( 'customcssprotected', $action );
02114                 } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
02115                     $errors[] = array( 'customjsprotected', $action );
02116                 }
02117             }
02118         }
02119 
02120         return $errors;
02121     }
02122 
02136     private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
02137         foreach ( $this->getRestrictions( $action ) as $right ) {
02138             // Backwards compatibility, rewrite sysop -> editprotected
02139             if ( $right == 'sysop' ) {
02140                 $right = 'editprotected';
02141             }
02142             // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
02143             if ( $right == 'autoconfirmed' ) {
02144                 $right = 'editsemiprotected';
02145             }
02146             if ( $right == '' ) {
02147                 continue;
02148             }
02149             if ( !$user->isAllowed( $right ) ) {
02150                 $errors[] = array( 'protectedpagetext', $right, $action );
02151             } elseif ( $this->mCascadeRestriction && !$user->isAllowed( 'protect' ) ) {
02152                 $errors[] = array( 'protectedpagetext', 'protect', $action );
02153             }
02154         }
02155 
02156         return $errors;
02157     }
02158 
02170     private function checkCascadingSourcesRestrictions( $action, $user, $errors,
02171         $doExpensiveQueries, $short
02172     ) {
02173         if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
02174             # We /could/ use the protection level on the source page, but it's
02175             # fairly ugly as we have to establish a precedence hierarchy for pages
02176             # included by multiple cascade-protected pages. So just restrict
02177             # it to people with 'protect' permission, as they could remove the
02178             # protection anyway.
02179             list( $cascadingSources, $restrictions ) = $this->getCascadeProtectionSources();
02180             # Cascading protection depends on more than this page...
02181             # Several cascading protected pages may include this page...
02182             # Check each cascading level
02183             # This is only for protection restrictions, not for all actions
02184             if ( isset( $restrictions[$action] ) ) {
02185                 foreach ( $restrictions[$action] as $right ) {
02186                     // Backwards compatibility, rewrite sysop -> editprotected
02187                     if ( $right == 'sysop' ) {
02188                         $right = 'editprotected';
02189                     }
02190                     // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
02191                     if ( $right == 'autoconfirmed' ) {
02192                         $right = 'editsemiprotected';
02193                     }
02194                     if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
02195                         $pages = '';
02196                         foreach ( $cascadingSources as $page ) {
02197                             $pages .= '* [[:' . $page->getPrefixedText() . "]]\n";
02198                         }
02199                         $errors[] = array( 'cascadeprotected', count( $cascadingSources ), $pages, $action );
02200                     }
02201                 }
02202             }
02203         }
02204 
02205         return $errors;
02206     }
02207 
02219     private function checkActionPermissions( $action, $user, $errors,
02220         $doExpensiveQueries, $short
02221     ) {
02222         global $wgDeleteRevisionsLimit, $wgLang;
02223 
02224         if ( $action == 'protect' ) {
02225             if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
02226                 $user, $doExpensiveQueries, true ) )
02227             ) {
02228                 // If they can't edit, they shouldn't protect.
02229                 $errors[] = array( 'protect-cantedit' );
02230             }
02231         } elseif ( $action == 'create' ) {
02232             $title_protection = $this->getTitleProtection();
02233             if ( $title_protection ) {
02234                 if ( $title_protection['pt_create_perm'] == 'sysop' ) {
02235                     $title_protection['pt_create_perm'] = 'editprotected'; // B/C
02236                 }
02237                 if ( $title_protection['pt_create_perm'] == 'autoconfirmed' ) {
02238                     $title_protection['pt_create_perm'] = 'editsemiprotected'; // B/C
02239                 }
02240                 if ( $title_protection['pt_create_perm'] == ''
02241                     || !$user->isAllowed( $title_protection['pt_create_perm'] )
02242                 ) {
02243                     $errors[] = array(
02244                         'titleprotected',
02245                         User::whoIs( $title_protection['pt_user'] ),
02246                         $title_protection['pt_reason']
02247                     );
02248                 }
02249             }
02250         } elseif ( $action == 'move' ) {
02251             // Check for immobile pages
02252             if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
02253                 // Specific message for this case
02254                 $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
02255             } elseif ( !$this->isMovable() ) {
02256                 // Less specific message for rarer cases
02257                 $errors[] = array( 'immobile-source-page' );
02258             }
02259         } elseif ( $action == 'move-target' ) {
02260             if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
02261                 $errors[] = array( 'immobile-target-namespace', $this->getNsText() );
02262             } elseif ( !$this->isMovable() ) {
02263                 $errors[] = array( 'immobile-target-page' );
02264             }
02265         } elseif ( $action == 'delete' ) {
02266             $tempErrors = $this->checkPageRestrictions( 'edit',
02267                 $user, array(), $doExpensiveQueries, true );
02268             if ( !$tempErrors ) {
02269                 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
02270                     $user, $tempErrors, $doExpensiveQueries, true );
02271             }
02272             if ( $tempErrors ) {
02273                 // If protection keeps them from editing, they shouldn't be able to delete.
02274                 $errors[] = array( 'deleteprotected' );
02275             }
02276             if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
02277                 && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
02278             ) {
02279                 $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
02280             }
02281         }
02282         return $errors;
02283     }
02284 
02296     private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
02297         // Account creation blocks handled at userlogin.
02298         // Unblocking handled in SpecialUnblock
02299         if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
02300             return $errors;
02301         }
02302 
02303         global $wgEmailConfirmToEdit;
02304 
02305         if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
02306             $errors[] = array( 'confirmedittext' );
02307         }
02308 
02309         if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
02310             // Don't block the user from editing their own talk page unless they've been
02311             // explicitly blocked from that too.
02312         } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
02313             // @todo FIXME: Pass the relevant context into this function.
02314             $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
02315         }
02316 
02317         return $errors;
02318     }
02319 
02331     private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
02332         global $wgWhitelistRead, $wgWhitelistReadRegexp;
02333 
02334         $whitelisted = false;
02335         if ( User::isEveryoneAllowed( 'read' ) ) {
02336             # Shortcut for public wikis, allows skipping quite a bit of code
02337             $whitelisted = true;
02338         } elseif ( $user->isAllowed( 'read' ) ) {
02339             # If the user is allowed to read pages, he is allowed to read all pages
02340             $whitelisted = true;
02341         } elseif ( $this->isSpecial( 'Userlogin' )
02342             || $this->isSpecial( 'ChangePassword' )
02343             || $this->isSpecial( 'PasswordReset' )
02344         ) {
02345             # Always grant access to the login page.
02346             # Even anons need to be able to log in.
02347             $whitelisted = true;
02348         } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
02349             # Time to check the whitelist
02350             # Only do these checks is there's something to check against
02351             $name = $this->getPrefixedText();
02352             $dbName = $this->getPrefixedDBkey();
02353 
02354             // Check for explicit whitelisting with and without underscores
02355             if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
02356                 $whitelisted = true;
02357             } elseif ( $this->getNamespace() == NS_MAIN ) {
02358                 # Old settings might have the title prefixed with
02359                 # a colon for main-namespace pages
02360                 if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
02361                     $whitelisted = true;
02362                 }
02363             } elseif ( $this->isSpecialPage() ) {
02364                 # If it's a special page, ditch the subpage bit and check again
02365                 $name = $this->getDBkey();
02366                 list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
02367                 if ( $name ) {
02368                     $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
02369                     if ( in_array( $pure, $wgWhitelistRead, true ) ) {
02370                         $whitelisted = true;
02371                     }
02372                 }
02373             }
02374         }
02375 
02376         if ( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
02377             $name = $this->getPrefixedText();
02378             // Check for regex whitelisting
02379             foreach ( $wgWhitelistReadRegexp as $listItem ) {
02380                 if ( preg_match( $listItem, $name ) ) {
02381                     $whitelisted = true;
02382                     break;
02383                 }
02384             }
02385         }
02386 
02387         if ( !$whitelisted ) {
02388             # If the title is not whitelisted, give extensions a chance to do so...
02389             wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
02390             if ( !$whitelisted ) {
02391                 $errors[] = $this->missingPermissionError( $action, $short );
02392             }
02393         }
02394 
02395         return $errors;
02396     }
02397 
02406     private function missingPermissionError( $action, $short ) {
02407         // We avoid expensive display logic for quickUserCan's and such
02408         if ( $short ) {
02409             return array( 'badaccess-group0' );
02410         }
02411 
02412         $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
02413             User::getGroupsWithPermission( $action ) );
02414 
02415         if ( count( $groups ) ) {
02416             global $wgLang;
02417             return array(
02418                 'badaccess-groups',
02419                 $wgLang->commaList( $groups ),
02420                 count( $groups )
02421             );
02422         } else {
02423             return array( 'badaccess-group0' );
02424         }
02425     }
02426 
02438     protected function getUserPermissionsErrorsInternal( $action, $user,
02439         $doExpensiveQueries = true, $short = false
02440     ) {
02441         wfProfileIn( __METHOD__ );
02442 
02443         # Read has special handling
02444         if ( $action == 'read' ) {
02445             $checks = array(
02446                 'checkPermissionHooks',
02447                 'checkReadPermissions',
02448             );
02449         # Don't call checkSpecialsAndNSPermissions or checkCSSandJSPermissions
02450         # here as it will lead to duplicate error messages. This is okay to do
02451         # since anywhere that checks for create will also check for edit, and
02452         # those checks are called for edit.
02453         } elseif ( $action == 'create' ) {
02454             $checks = array(
02455                 'checkQuickPermissions',
02456                 'checkPermissionHooks',
02457                 'checkPageRestrictions',
02458                 'checkCascadingSourcesRestrictions',
02459                 'checkActionPermissions',
02460                 'checkUserBlock'
02461             );
02462         } else {
02463             $checks = array(
02464                 'checkQuickPermissions',
02465                 'checkPermissionHooks',
02466                 'checkSpecialsAndNSPermissions',
02467                 'checkCSSandJSPermissions',
02468                 'checkPageRestrictions',
02469                 'checkCascadingSourcesRestrictions',
02470                 'checkActionPermissions',
02471                 'checkUserBlock'
02472             );
02473         }
02474 
02475         $errors = array();
02476         while ( count( $checks ) > 0 &&
02477                 !( $short && count( $errors ) > 0 ) ) {
02478             $method = array_shift( $checks );
02479             $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
02480         }
02481 
02482         wfProfileOut( __METHOD__ );
02483         return $errors;
02484     }
02485 
02493     public static function getFilteredRestrictionTypes( $exists = true ) {
02494         global $wgRestrictionTypes;
02495         $types = $wgRestrictionTypes;
02496         if ( $exists ) {
02497             # Remove the create restriction for existing titles
02498             $types = array_diff( $types, array( 'create' ) );
02499         } else {
02500             # Only the create and upload restrictions apply to non-existing titles
02501             $types = array_intersect( $types, array( 'create', 'upload' ) );
02502         }
02503         return $types;
02504     }
02505 
02511     public function getRestrictionTypes() {
02512         if ( $this->isSpecialPage() ) {
02513             return array();
02514         }
02515 
02516         $types = self::getFilteredRestrictionTypes( $this->exists() );
02517 
02518         if ( $this->getNamespace() != NS_FILE ) {
02519             # Remove the upload restriction for non-file titles
02520             $types = array_diff( $types, array( 'upload' ) );
02521         }
02522 
02523         wfRunHooks( 'TitleGetRestrictionTypes', array( $this, &$types ) );
02524 
02525         wfDebug( __METHOD__ . ': applicable restrictions to [[' .
02526             $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
02527 
02528         return $types;
02529     }
02530 
02538     private function getTitleProtection() {
02539         // Can't protect pages in special namespaces
02540         if ( $this->getNamespace() < 0 ) {
02541             return false;
02542         }
02543 
02544         // Can't protect pages that exist.
02545         if ( $this->exists() ) {
02546             return false;
02547         }
02548 
02549         if ( $this->mTitleProtection === null ) {
02550             $dbr = wfGetDB( DB_SLAVE );
02551             $res = $dbr->select(
02552                 'protected_titles',
02553                 array( 'pt_user', 'pt_reason', 'pt_expiry', 'pt_create_perm' ),
02554                 array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02555                 __METHOD__
02556             );
02557 
02558             // fetchRow returns false if there are no rows.
02559             $this->mTitleProtection = $dbr->fetchRow( $res );
02560         }
02561         return $this->mTitleProtection;
02562     }
02563 
02567     public function deleteTitleProtection() {
02568         $dbw = wfGetDB( DB_MASTER );
02569 
02570         $dbw->delete(
02571             'protected_titles',
02572             array( 'pt_namespace' => $this->getNamespace(), 'pt_title' => $this->getDBkey() ),
02573             __METHOD__
02574         );
02575         $this->mTitleProtection = false;
02576     }
02577 
02585     public function isSemiProtected( $action = 'edit' ) {
02586         global $wgSemiprotectedRestrictionLevels;
02587 
02588         $restrictions = $this->getRestrictions( $action );
02589         $semi = $wgSemiprotectedRestrictionLevels;
02590         if ( !$restrictions || !$semi ) {
02591             // Not protected, or all protection is full protection
02592             return false;
02593         }
02594 
02595         // Remap autoconfirmed to editsemiprotected for BC
02596         foreach ( array_keys( $semi, 'autoconfirmed' ) as $key ) {
02597             $semi[$key] = 'editsemiprotected';
02598         }
02599         foreach ( array_keys( $restrictions, 'autoconfirmed' ) as $key ) {
02600             $restrictions[$key] = 'editsemiprotected';
02601         }
02602 
02603         return !array_diff( $restrictions, $semi );
02604     }
02605 
02613     public function isProtected( $action = '' ) {
02614         global $wgRestrictionLevels;
02615 
02616         $restrictionTypes = $this->getRestrictionTypes();
02617 
02618         # Special pages have inherent protection
02619         if ( $this->isSpecialPage() ) {
02620             return true;
02621         }
02622 
02623         # Check regular protection levels
02624         foreach ( $restrictionTypes as $type ) {
02625             if ( $action == $type || $action == '' ) {
02626                 $r = $this->getRestrictions( $type );
02627                 foreach ( $wgRestrictionLevels as $level ) {
02628                     if ( in_array( $level, $r ) && $level != '' ) {
02629                         return true;
02630                     }
02631                 }
02632             }
02633         }
02634 
02635         return false;
02636     }
02637 
02645     public function isNamespaceProtected( User $user ) {
02646         global $wgNamespaceProtection;
02647 
02648         if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
02649             foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
02650                 if ( $right != '' && !$user->isAllowed( $right ) ) {
02651                     return true;
02652                 }
02653             }
02654         }
02655         return false;
02656     }
02657 
02663     public function isCascadeProtected() {
02664         list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
02665         return ( $sources > 0 );
02666     }
02667 
02677     public function areCascadeProtectionSourcesLoaded( $getPages = true ) {
02678         return $getPages ? $this->mCascadeSources !== null : $this->mHasCascadingRestrictions !== null;
02679     }
02680 
02694     public function getCascadeProtectionSources( $getPages = true ) {
02695         global $wgContLang;
02696         $pagerestrictions = array();
02697 
02698         if ( $this->mCascadeSources !== null && $getPages ) {
02699             return array( $this->mCascadeSources, $this->mCascadingRestrictions );
02700         } elseif ( $this->mHasCascadingRestrictions !== null && !$getPages ) {
02701             return array( $this->mHasCascadingRestrictions, $pagerestrictions );
02702         }
02703 
02704         wfProfileIn( __METHOD__ );
02705 
02706         $dbr = wfGetDB( DB_SLAVE );
02707 
02708         if ( $this->getNamespace() == NS_FILE ) {
02709             $tables = array( 'imagelinks', 'page_restrictions' );
02710             $where_clauses = array(
02711                 'il_to' => $this->getDBkey(),
02712                 'il_from=pr_page',
02713                 'pr_cascade' => 1
02714             );
02715         } else {
02716             $tables = array( 'templatelinks', 'page_restrictions' );
02717             $where_clauses = array(
02718                 'tl_namespace' => $this->getNamespace(),
02719                 'tl_title' => $this->getDBkey(),
02720                 'tl_from=pr_page',
02721                 'pr_cascade' => 1
02722             );
02723         }
02724 
02725         if ( $getPages ) {
02726             $cols = array( 'pr_page', 'page_namespace', 'page_title',
02727                 'pr_expiry', 'pr_type', 'pr_level' );
02728             $where_clauses[] = 'page_id=pr_page';
02729             $tables[] = 'page';
02730         } else {
02731             $cols = array( 'pr_expiry' );
02732         }
02733 
02734         $res = $dbr->select( $tables, $cols, $where_clauses, __METHOD__ );
02735 
02736         $sources = $getPages ? array() : false;
02737         $now = wfTimestampNow();
02738         $purgeExpired = false;
02739 
02740         foreach ( $res as $row ) {
02741             $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02742             if ( $expiry > $now ) {
02743                 if ( $getPages ) {
02744                     $page_id = $row->pr_page;
02745                     $page_ns = $row->page_namespace;
02746                     $page_title = $row->page_title;
02747                     $sources[$page_id] = Title::makeTitle( $page_ns, $page_title );
02748                     # Add groups needed for each restriction type if its not already there
02749                     # Make sure this restriction type still exists
02750 
02751                     if ( !isset( $pagerestrictions[$row->pr_type] ) ) {
02752                         $pagerestrictions[$row->pr_type] = array();
02753                     }
02754 
02755                     if (
02756                         isset( $pagerestrictions[$row->pr_type] )
02757                         && !in_array( $row->pr_level, $pagerestrictions[$row->pr_type] )
02758                     ) {
02759                         $pagerestrictions[$row->pr_type][] = $row->pr_level;
02760                     }
02761                 } else {
02762                     $sources = true;
02763                 }
02764             } else {
02765                 // Trigger lazy purge of expired restrictions from the db
02766                 $purgeExpired = true;
02767             }
02768         }
02769         if ( $purgeExpired ) {
02770             Title::purgeExpiredRestrictions();
02771         }
02772 
02773         if ( $getPages ) {
02774             $this->mCascadeSources = $sources;
02775             $this->mCascadingRestrictions = $pagerestrictions;
02776         } else {
02777             $this->mHasCascadingRestrictions = $sources;
02778         }
02779 
02780         wfProfileOut( __METHOD__ );
02781         return array( $sources, $pagerestrictions );
02782     }
02783 
02791     public function areRestrictionsLoaded() {
02792         return $this->mRestrictionsLoaded;
02793     }
02794 
02802     public function getRestrictions( $action ) {
02803         if ( !$this->mRestrictionsLoaded ) {
02804             $this->loadRestrictions();
02805         }
02806         return isset( $this->mRestrictions[$action] )
02807                 ? $this->mRestrictions[$action]
02808                 : array();
02809     }
02810 
02818     public function getAllRestrictions() {
02819         if ( !$this->mRestrictionsLoaded ) {
02820             $this->loadRestrictions();
02821         }
02822         return $this->mRestrictions;
02823     }
02824 
02832     public function getRestrictionExpiry( $action ) {
02833         if ( !$this->mRestrictionsLoaded ) {
02834             $this->loadRestrictions();
02835         }
02836         return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
02837     }
02838 
02844     function areRestrictionsCascading() {
02845         if ( !$this->mRestrictionsLoaded ) {
02846             $this->loadRestrictions();
02847         }
02848 
02849         return $this->mCascadeRestriction;
02850     }
02851 
02859     private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
02860         $rows = array();
02861 
02862         foreach ( $res as $row ) {
02863             $rows[] = $row;
02864         }
02865 
02866         $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
02867     }
02868 
02878     public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
02879         global $wgContLang;
02880         $dbr = wfGetDB( DB_SLAVE );
02881 
02882         $restrictionTypes = $this->getRestrictionTypes();
02883 
02884         foreach ( $restrictionTypes as $type ) {
02885             $this->mRestrictions[$type] = array();
02886             $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
02887         }
02888 
02889         $this->mCascadeRestriction = false;
02890 
02891         # Backwards-compatibility: also load the restrictions from the page record (old format).
02892 
02893         if ( $oldFashionedRestrictions === null ) {
02894             $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
02895                 array( 'page_id' => $this->getArticleID() ), __METHOD__ );
02896         }
02897 
02898         if ( $oldFashionedRestrictions != '' ) {
02899 
02900             foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
02901                 $temp = explode( '=', trim( $restrict ) );
02902                 if ( count( $temp ) == 1 ) {
02903                     // old old format should be treated as edit/move restriction
02904                     $this->mRestrictions['edit'] = explode( ',', trim( $temp[0] ) );
02905                     $this->mRestrictions['move'] = explode( ',', trim( $temp[0] ) );
02906                 } else {
02907                     $restriction = trim( $temp[1] );
02908                     if ( $restriction != '' ) { //some old entries are empty
02909                         $this->mRestrictions[$temp[0]] = explode( ',', $restriction );
02910                     }
02911                 }
02912             }
02913 
02914             $this->mOldRestrictions = true;
02915 
02916         }
02917 
02918         if ( count( $rows ) ) {
02919             # Current system - load second to make them override.
02920             $now = wfTimestampNow();
02921             $purgeExpired = false;
02922 
02923             # Cycle through all the restrictions.
02924             foreach ( $rows as $row ) {
02925 
02926                 // Don't take care of restrictions types that aren't allowed
02927                 if ( !in_array( $row->pr_type, $restrictionTypes ) ) {
02928                     continue;
02929                 }
02930 
02931                 // This code should be refactored, now that it's being used more generally,
02932                 // But I don't really see any harm in leaving it in Block for now -werdna
02933                 $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
02934 
02935                 // Only apply the restrictions if they haven't expired!
02936                 if ( !$expiry || $expiry > $now ) {
02937                     $this->mRestrictionsExpiry[$row->pr_type] = $expiry;
02938                     $this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
02939 
02940                     $this->mCascadeRestriction |= $row->pr_cascade;
02941                 } else {
02942                     // Trigger a lazy purge of expired restrictions
02943                     $purgeExpired = true;
02944                 }
02945             }
02946 
02947             if ( $purgeExpired ) {
02948                 Title::purgeExpiredRestrictions();
02949             }
02950         }
02951 
02952         $this->mRestrictionsLoaded = true;
02953     }
02954 
02961     public function loadRestrictions( $oldFashionedRestrictions = null ) {
02962         global $wgContLang;
02963         if ( !$this->mRestrictionsLoaded ) {
02964             if ( $this->exists() ) {
02965                 $dbr = wfGetDB( DB_SLAVE );
02966 
02967                 $res = $dbr->select(
02968                     'page_restrictions',
02969                     array( 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ),
02970                     array( 'pr_page' => $this->getArticleID() ),
02971                     __METHOD__
02972                 );
02973 
02974                 $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
02975             } else {
02976                 $title_protection = $this->getTitleProtection();
02977 
02978                 if ( $title_protection ) {
02979                     $now = wfTimestampNow();
02980                     $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
02981 
02982                     if ( !$expiry || $expiry > $now ) {
02983                         // Apply the restrictions
02984                         $this->mRestrictionsExpiry['create'] = $expiry;
02985                         $this->mRestrictions['create'] = explode( ',', trim( $title_protection['pt_create_perm'] ) );
02986                     } else { // Get rid of the old restrictions
02987                         Title::purgeExpiredRestrictions();
02988                         $this->mTitleProtection = false;
02989                     }
02990                 } else {
02991                     $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
02992                 }
02993                 $this->mRestrictionsLoaded = true;
02994             }
02995         }
02996     }
02997 
03002     public function flushRestrictions() {
03003         $this->mRestrictionsLoaded = false;
03004         $this->mTitleProtection = null;
03005     }
03006 
03010     static function purgeExpiredRestrictions() {
03011         if ( wfReadOnly() ) {
03012             return;
03013         }
03014 
03015         $method = __METHOD__;
03016         $dbw = wfGetDB( DB_MASTER );
03017         $dbw->onTransactionIdle( function () use ( $dbw, $method ) {
03018             $dbw->delete(
03019                 'page_restrictions',
03020                 array( 'pr_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
03021                 $method
03022             );
03023             $dbw->delete(
03024                 'protected_titles',
03025                 array( 'pt_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ),
03026                 $method
03027             );
03028         } );
03029     }
03030 
03036     public function hasSubpages() {
03037         if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
03038             # Duh
03039             return false;
03040         }
03041 
03042         # We dynamically add a member variable for the purpose of this method
03043         # alone to cache the result.  There's no point in having it hanging
03044         # around uninitialized in every Title object; therefore we only add it
03045         # if needed and don't declare it statically.
03046         if ( $this->mHasSubpages === null ) {
03047             $this->mHasSubpages = false;
03048             $subpages = $this->getSubpages( 1 );
03049             if ( $subpages instanceof TitleArray ) {
03050                 $this->mHasSubpages = (bool)$subpages->count();
03051             }
03052         }
03053 
03054         return $this->mHasSubpages;
03055     }
03056 
03064     public function getSubpages( $limit = -1 ) {
03065         if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03066             return array();
03067         }
03068 
03069         $dbr = wfGetDB( DB_SLAVE );
03070         $conds['page_namespace'] = $this->getNamespace();
03071         $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
03072         $options = array();
03073         if ( $limit > -1 ) {
03074             $options['LIMIT'] = $limit;
03075         }
03076         $this->mSubpages = TitleArray::newFromResult(
03077             $dbr->select( 'page',
03078                 array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
03079                 $conds,
03080                 __METHOD__,
03081                 $options
03082             )
03083         );
03084         return $this->mSubpages;
03085     }
03086 
03092     public function isDeleted() {
03093         if ( $this->getNamespace() < 0 ) {
03094             $n = 0;
03095         } else {
03096             $dbr = wfGetDB( DB_SLAVE );
03097 
03098             $n = $dbr->selectField( 'archive', 'COUNT(*)',
03099                 array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
03100                 __METHOD__
03101             );
03102             if ( $this->getNamespace() == NS_FILE ) {
03103                 $n += $dbr->selectField( 'filearchive', 'COUNT(*)',
03104                     array( 'fa_name' => $this->getDBkey() ),
03105                     __METHOD__
03106                 );
03107             }
03108         }
03109         return (int)$n;
03110     }
03111 
03117     public function isDeletedQuick() {
03118         if ( $this->getNamespace() < 0 ) {
03119             return false;
03120         }
03121         $dbr = wfGetDB( DB_SLAVE );
03122         $deleted = (bool)$dbr->selectField( 'archive', '1',
03123             array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
03124             __METHOD__
03125         );
03126         if ( !$deleted && $this->getNamespace() == NS_FILE ) {
03127             $deleted = (bool)$dbr->selectField( 'filearchive', '1',
03128                 array( 'fa_name' => $this->getDBkey() ),
03129                 __METHOD__
03130             );
03131         }
03132         return $deleted;
03133     }
03134 
03143     public function getArticleID( $flags = 0 ) {
03144         if ( $this->getNamespace() < 0 ) {
03145             $this->mArticleID = 0;
03146             return $this->mArticleID;
03147         }
03148         $linkCache = LinkCache::singleton();
03149         if ( $flags & self::GAID_FOR_UPDATE ) {
03150             $oldUpdate = $linkCache->forUpdate( true );
03151             $linkCache->clearLink( $this );
03152             $this->mArticleID = $linkCache->addLinkObj( $this );
03153             $linkCache->forUpdate( $oldUpdate );
03154         } else {
03155             if ( -1 == $this->mArticleID ) {
03156                 $this->mArticleID = $linkCache->addLinkObj( $this );
03157             }
03158         }
03159         return $this->mArticleID;
03160     }
03161 
03169     public function isRedirect( $flags = 0 ) {
03170         if ( !is_null( $this->mRedirect ) ) {
03171             return $this->mRedirect;
03172         }
03173         # Calling getArticleID() loads the field from cache as needed
03174         if ( !$this->getArticleID( $flags ) ) {
03175             $this->mRedirect = false;
03176             return $this->mRedirect;
03177         }
03178 
03179         $linkCache = LinkCache::singleton();
03180         $cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
03181         if ( $cached === null ) {
03182             # Trust LinkCache's state over our own
03183             # LinkCache is telling us that the page doesn't exist, despite there being cached
03184             # data relating to an existing page in $this->mArticleID. Updaters should clear
03185             # LinkCache as appropriate, or use $flags = Title::GAID_FOR_UPDATE. If that flag is
03186             # set, then LinkCache will definitely be up to date here, since getArticleID() forces
03187             # LinkCache to refresh its data from the master.
03188             $this->mRedirect = false;
03189             return $this->mRedirect;
03190         }
03191 
03192         $this->mRedirect = (bool)$cached;
03193 
03194         return $this->mRedirect;
03195     }
03196 
03204     public function getLength( $flags = 0 ) {
03205         if ( $this->mLength != -1 ) {
03206             return $this->mLength;
03207         }
03208         # Calling getArticleID() loads the field from cache as needed
03209         if ( !$this->getArticleID( $flags ) ) {
03210             $this->mLength = 0;
03211             return $this->mLength;
03212         }
03213         $linkCache = LinkCache::singleton();
03214         $cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
03215         if ( $cached === null ) {
03216             # Trust LinkCache's state over our own, as for isRedirect()
03217             $this->mLength = 0;
03218             return $this->mLength;
03219         }
03220 
03221         $this->mLength = intval( $cached );
03222 
03223         return $this->mLength;
03224     }
03225 
03232     public function getLatestRevID( $flags = 0 ) {
03233         if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
03234             return intval( $this->mLatestID );
03235         }
03236         # Calling getArticleID() loads the field from cache as needed
03237         if ( !$this->getArticleID( $flags ) ) {
03238             $this->mLatestID = 0;
03239             return $this->mLatestID;
03240         }
03241         $linkCache = LinkCache::singleton();
03242         $linkCache->addLinkObj( $this );
03243         $cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
03244         if ( $cached === null ) {
03245             # Trust LinkCache's state over our own, as for isRedirect()
03246             $this->mLatestID = 0;
03247             return $this->mLatestID;
03248         }
03249 
03250         $this->mLatestID = intval( $cached );
03251 
03252         return $this->mLatestID;
03253     }
03254 
03265     public function resetArticleID( $newid ) {
03266         $linkCache = LinkCache::singleton();
03267         $linkCache->clearLink( $this );
03268 
03269         if ( $newid === false ) {
03270             $this->mArticleID = -1;
03271         } else {
03272             $this->mArticleID = intval( $newid );
03273         }
03274         $this->mRestrictionsLoaded = false;
03275         $this->mRestrictions = array();
03276         $this->mRedirect = null;
03277         $this->mLength = -1;
03278         $this->mLatestID = false;
03279         $this->mContentModel = false;
03280         $this->mEstimateRevisions = null;
03281         $this->mPageLanguage = false;
03282         $this->mDbPageLanguage = null;
03283         $this->mIsBigDeletion = null;
03284     }
03285 
03293     public static function capitalize( $text, $ns = NS_MAIN ) {
03294         global $wgContLang;
03295 
03296         if ( MWNamespace::isCapitalized( $ns ) ) {
03297             return $wgContLang->ucfirst( $text );
03298         } else {
03299             return $text;
03300         }
03301     }
03302 
03314     private function secureAndSplit() {
03315         # Initialisation
03316         $this->mInterwiki = '';
03317         $this->mFragment = '';
03318         $this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
03319 
03320         $dbkey = $this->mDbkeyform;
03321 
03322         try {
03323             // @note: splitTitleString() is a temporary hack to allow MediaWikiTitleCodec to share
03324             //        the parsing code with Title, while avoiding massive refactoring.
03325             // @todo: get rid of secureAndSplit, refactor parsing code.
03326             $titleParser = self::getTitleParser();
03327             $parts = $titleParser->splitTitleString( $dbkey, $this->getDefaultNamespace() );
03328         } catch ( MalformedTitleException $ex ) {
03329             return false;
03330         }
03331 
03332         # Fill fields
03333         $this->setFragment( '#' . $parts['fragment'] );
03334         $this->mInterwiki = $parts['interwiki'];
03335         $this->mLocalInterwiki = $parts['local_interwiki'];
03336         $this->mNamespace = $parts['namespace'];
03337         $this->mUserCaseDBKey = $parts['user_case_dbkey'];
03338 
03339         $this->mDbkeyform = $parts['dbkey'];
03340         $this->mUrlform = wfUrlencode( $this->mDbkeyform );
03341         $this->mTextform = str_replace( '_', ' ', $this->mDbkeyform );
03342 
03343         # We already know that some pages won't be in the database!
03344         if ( $this->isExternal() || $this->mNamespace == NS_SPECIAL ) {
03345             $this->mArticleID = 0;
03346         }
03347 
03348         return true;
03349     }
03350 
03363     public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03364         if ( count( $options ) > 0 ) {
03365             $db = wfGetDB( DB_MASTER );
03366         } else {
03367             $db = wfGetDB( DB_SLAVE );
03368         }
03369 
03370         $res = $db->select(
03371             array( 'page', $table ),
03372             self::getSelectFields(),
03373             array(
03374                 "{$prefix}_from=page_id",
03375                 "{$prefix}_namespace" => $this->getNamespace(),
03376                 "{$prefix}_title" => $this->getDBkey() ),
03377             __METHOD__,
03378             $options
03379         );
03380 
03381         $retVal = array();
03382         if ( $res->numRows() ) {
03383             $linkCache = LinkCache::singleton();
03384             foreach ( $res as $row ) {
03385                 $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
03386                 if ( $titleObj ) {
03387                     $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03388                     $retVal[] = $titleObj;
03389                 }
03390             }
03391         }
03392         return $retVal;
03393     }
03394 
03405     public function getTemplateLinksTo( $options = array() ) {
03406         return $this->getLinksTo( $options, 'templatelinks', 'tl' );
03407     }
03408 
03421     public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
03422         global $wgContentHandlerUseDB;
03423 
03424         $id = $this->getArticleID();
03425 
03426         # If the page doesn't exist; there can't be any link from this page
03427         if ( !$id ) {
03428             return array();
03429         }
03430 
03431         if ( count( $options ) > 0 ) {
03432             $db = wfGetDB( DB_MASTER );
03433         } else {
03434             $db = wfGetDB( DB_SLAVE );
03435         }
03436 
03437         $namespaceFiled = "{$prefix}_namespace";
03438         $titleField = "{$prefix}_title";
03439 
03440         $fields = array(
03441             $namespaceFiled,
03442             $titleField,
03443             'page_id',
03444             'page_len',
03445             'page_is_redirect',
03446             'page_latest'
03447         );
03448 
03449         if ( $wgContentHandlerUseDB ) {
03450             $fields[] = 'page_content_model';
03451         }
03452 
03453         $res = $db->select(
03454             array( $table, 'page' ),
03455             $fields,
03456             array( "{$prefix}_from" => $id ),
03457             __METHOD__,
03458             $options,
03459             array( 'page' => array(
03460                 'LEFT JOIN',
03461                 array( "page_namespace=$namespaceFiled", "page_title=$titleField" )
03462             ) )
03463         );
03464 
03465         $retVal = array();
03466         if ( $res->numRows() ) {
03467             $linkCache = LinkCache::singleton();
03468             foreach ( $res as $row ) {
03469                 $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
03470                 if ( $titleObj ) {
03471                     if ( $row->page_id ) {
03472                         $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
03473                     } else {
03474                         $linkCache->addBadLinkObj( $titleObj );
03475                     }
03476                     $retVal[] = $titleObj;
03477                 }
03478             }
03479         }
03480         return $retVal;
03481     }
03482 
03493     public function getTemplateLinksFrom( $options = array() ) {
03494         return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
03495     }
03496 
03505     public function getBrokenLinksFrom() {
03506         if ( $this->getArticleID() == 0 ) {
03507             # All links from article ID 0 are false positives
03508             return array();
03509         }
03510 
03511         $dbr = wfGetDB( DB_SLAVE );
03512         $res = $dbr->select(
03513             array( 'page', 'pagelinks' ),
03514             array( 'pl_namespace', 'pl_title' ),
03515             array(
03516                 'pl_from' => $this->getArticleID(),
03517                 'page_namespace IS NULL'
03518             ),
03519             __METHOD__, array(),
03520             array(
03521                 'page' => array(
03522                     'LEFT JOIN',
03523                     array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
03524                 )
03525             )
03526         );
03527 
03528         $retVal = array();
03529         foreach ( $res as $row ) {
03530             $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
03531         }
03532         return $retVal;
03533     }
03534 
03541     public function getSquidURLs() {
03542         $urls = array(
03543             $this->getInternalURL(),
03544             $this->getInternalURL( 'action=history' )
03545         );
03546 
03547         $pageLang = $this->getPageLanguage();
03548         if ( $pageLang->hasVariants() ) {
03549             $variants = $pageLang->getVariants();
03550             foreach ( $variants as $vCode ) {
03551                 $urls[] = $this->getInternalURL( '', $vCode );
03552             }
03553         }
03554 
03555         // If we are looking at a css/js user subpage, purge the action=raw.
03556         if ( $this->isJsSubpage() ) {
03557             $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/javascript' );
03558         } elseif ( $this->isCssSubpage() ) {
03559             $urls[] = $this->getInternalUrl( 'action=raw&ctype=text/css' );
03560         }
03561 
03562         wfRunHooks( 'TitleSquidURLs', array( $this, &$urls ) );
03563         return $urls;
03564     }
03565 
03569     public function purgeSquid() {
03570         global $wgUseSquid;
03571         if ( $wgUseSquid ) {
03572             $urls = $this->getSquidURLs();
03573             $u = new SquidUpdate( $urls );
03574             $u->doUpdate();
03575         }
03576     }
03577 
03584     public function moveNoAuth( &$nt ) {
03585         return $this->moveTo( $nt, false );
03586     }
03587 
03599     public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
03600         global $wgUser, $wgContentHandlerUseDB;
03601 
03602         $errors = array();
03603         if ( !$nt ) {
03604             // Normally we'd add this to $errors, but we'll get
03605             // lots of syntax errors if $nt is not an object
03606             return array( array( 'badtitletext' ) );
03607         }
03608         if ( $this->equals( $nt ) ) {
03609             $errors[] = array( 'selfmove' );
03610         }
03611         if ( !$this->isMovable() ) {
03612             $errors[] = array( 'immobile-source-namespace', $this->getNsText() );
03613         }
03614         if ( $nt->isExternal() ) {
03615             $errors[] = array( 'immobile-target-namespace-iw' );
03616         }
03617         if ( !$nt->isMovable() ) {
03618             $errors[] = array( 'immobile-target-namespace', $nt->getNsText() );
03619         }
03620 
03621         $oldid = $this->getArticleID();
03622         $newid = $nt->getArticleID();
03623 
03624         if ( strlen( $nt->getDBkey() ) < 1 ) {
03625             $errors[] = array( 'articleexists' );
03626         }
03627         if (
03628             ( $this->getDBkey() == '' ) ||
03629             ( !$oldid ) ||
03630             ( $nt->getDBkey() == '' )
03631         ) {
03632             $errors[] = array( 'badarticleerror' );
03633         }
03634 
03635         // Content model checks
03636         if ( !$wgContentHandlerUseDB &&
03637                 $this->getContentModel() !== $nt->getContentModel() ) {
03638             // can't move a page if that would change the page's content model
03639             $errors[] = array(
03640                 'bad-target-model',
03641                 ContentHandler::getLocalizedName( $this->getContentModel() ),
03642                 ContentHandler::getLocalizedName( $nt->getContentModel() )
03643             );
03644         }
03645 
03646         // Image-specific checks
03647         if ( $this->getNamespace() == NS_FILE ) {
03648             $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
03649         }
03650 
03651         if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
03652             $errors[] = array( 'nonfile-cannot-move-to-file' );
03653         }
03654 
03655         if ( $auth ) {
03656             $errors = wfMergeErrorArrays( $errors,
03657                 $this->getUserPermissionsErrors( 'move', $wgUser ),
03658                 $this->getUserPermissionsErrors( 'edit', $wgUser ),
03659                 $nt->getUserPermissionsErrors( 'move-target', $wgUser ),
03660                 $nt->getUserPermissionsErrors( 'edit', $wgUser ) );
03661         }
03662 
03663         $match = EditPage::matchSummarySpamRegex( $reason );
03664         if ( $match !== false ) {
03665             // This is kind of lame, won't display nice
03666             $errors[] = array( 'spamprotectiontext' );
03667         }
03668 
03669         $err = null;
03670         if ( !wfRunHooks( 'AbortMove', array( $this, $nt, $wgUser, &$err, $reason ) ) ) {
03671             $errors[] = array( 'hookaborted', $err );
03672         }
03673 
03674         # The move is allowed only if (1) the target doesn't exist, or
03675         # (2) the target is a redirect to the source, and has no history
03676         # (so we can undo bad moves right after they're done).
03677 
03678         if ( 0 != $newid ) { # Target exists; check for validity
03679             if ( !$this->isValidMoveTarget( $nt ) ) {
03680                 $errors[] = array( 'articleexists' );
03681             }
03682         } else {
03683             $tp = $nt->getTitleProtection();
03684             $right = $tp['pt_create_perm'];
03685             if ( $right == 'sysop' ) {
03686                 $right = 'editprotected'; // B/C
03687             }
03688             if ( $right == 'autoconfirmed' ) {
03689                 $right = 'editsemiprotected'; // B/C
03690             }
03691             if ( $tp and !$wgUser->isAllowed( $right ) ) {
03692                 $errors[] = array( 'cantmove-titleprotected' );
03693             }
03694         }
03695         if ( empty( $errors ) ) {
03696             return true;
03697         }
03698         return $errors;
03699     }
03700 
03706     protected function validateFileMoveOperation( $nt ) {
03707         global $wgUser;
03708 
03709         $errors = array();
03710 
03711         // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
03712 
03713         $file = wfLocalFile( $this );
03714         if ( $file->exists() ) {
03715             if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
03716                 $errors[] = array( 'imageinvalidfilename' );
03717             }
03718             if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
03719                 $errors[] = array( 'imagetypemismatch' );
03720             }
03721         }
03722 
03723         if ( $nt->getNamespace() != NS_FILE ) {
03724             $errors[] = array( 'imagenocrossnamespace' );
03725             // From here we want to do checks on a file object, so if we can't
03726             // create one, we must return.
03727             return $errors;
03728         }
03729 
03730         // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
03731 
03732         $destFile = wfLocalFile( $nt );
03733         if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
03734             $errors[] = array( 'file-exists-sharedrepo' );
03735         }
03736 
03737         return $errors;
03738     }
03739 
03752     public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
03753         global $wgUser;
03754         $err = $this->isValidMoveOperation( $nt, $auth, $reason );
03755         if ( is_array( $err ) ) {
03756             // Auto-block user's IP if the account was "hard" blocked
03757             $wgUser->spreadAnyEditBlock();
03758             return $err;
03759         }
03760         // Check suppressredirect permission
03761         if ( $auth && !$wgUser->isAllowed( 'suppressredirect' ) ) {
03762             $createRedirect = true;
03763         }
03764 
03765         wfRunHooks( 'TitleMove', array( $this, $nt, $wgUser ) );
03766 
03767         $mp = new MovePage( $this, $nt );
03768         $status = $mp->move( $wgUser, $reason, $createRedirect );
03769         if ( $status->isOK() ) {
03770             return true;
03771         } else {
03772             return $status->getErrorsArray();
03773         }
03774     }
03775 
03788     public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
03789         global $wgMaximumMovedPages;
03790         // Check permissions
03791         if ( !$this->userCan( 'move-subpages' ) ) {
03792             return array( 'cant-move-subpages' );
03793         }
03794         // Do the source and target namespaces support subpages?
03795         if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
03796             return array( 'namespace-nosubpages',
03797                 MWNamespace::getCanonicalName( $this->getNamespace() ) );
03798         }
03799         if ( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
03800             return array( 'namespace-nosubpages',
03801                 MWNamespace::getCanonicalName( $nt->getNamespace() ) );
03802         }
03803 
03804         $subpages = $this->getSubpages( $wgMaximumMovedPages + 1 );
03805         $retval = array();
03806         $count = 0;
03807         foreach ( $subpages as $oldSubpage ) {
03808             $count++;
03809             if ( $count > $wgMaximumMovedPages ) {
03810                 $retval[$oldSubpage->getPrefixedText()] =
03811                         array( 'movepage-max-pages',
03812                             $wgMaximumMovedPages );
03813                 break;
03814             }
03815 
03816             // We don't know whether this function was called before
03817             // or after moving the root page, so check both
03818             // $this and $nt
03819             if ( $oldSubpage->getArticleID() == $this->getArticleID()
03820                 || $oldSubpage->getArticleID() == $nt->getArticleID()
03821             ) {
03822                 // When moving a page to a subpage of itself,
03823                 // don't move it twice
03824                 continue;
03825             }
03826             $newPageName = preg_replace(
03827                     '#^' . preg_quote( $this->getDBkey(), '#' ) . '#',
03828                     StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
03829                     $oldSubpage->getDBkey() );
03830             if ( $oldSubpage->isTalkPage() ) {
03831                 $newNs = $nt->getTalkPage()->getNamespace();
03832             } else {
03833                 $newNs = $nt->getSubjectPage()->getNamespace();
03834             }
03835             # Bug 14385: we need makeTitleSafe because the new page names may
03836             # be longer than 255 characters.
03837             $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
03838 
03839             $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
03840             if ( $success === true ) {
03841                 $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
03842             } else {
03843                 $retval[$oldSubpage->getPrefixedText()] = $success;
03844             }
03845         }
03846         return $retval;
03847     }
03848 
03855     public function isSingleRevRedirect() {
03856         global $wgContentHandlerUseDB;
03857 
03858         $dbw = wfGetDB( DB_MASTER );
03859 
03860         # Is it a redirect?
03861         $fields = array( 'page_is_redirect', 'page_latest', 'page_id' );
03862         if ( $wgContentHandlerUseDB ) {
03863             $fields[] = 'page_content_model';
03864         }
03865 
03866         $row = $dbw->selectRow( 'page',
03867             $fields,
03868             $this->pageCond(),
03869             __METHOD__,
03870             array( 'FOR UPDATE' )
03871         );
03872         # Cache some fields we may want
03873         $this->mArticleID = $row ? intval( $row->page_id ) : 0;
03874         $this->mRedirect = $row ? (bool)$row->page_is_redirect : false;
03875         $this->mLatestID = $row ? intval( $row->page_latest ) : false;
03876         $this->mContentModel = $row && isset( $row->page_content_model )
03877             ? strval( $row->page_content_model )
03878             : false;
03879 
03880         if ( !$this->mRedirect ) {
03881             return false;
03882         }
03883         # Does the article have a history?
03884         $row = $dbw->selectField( array( 'page', 'revision' ),
03885             'rev_id',
03886             array( 'page_namespace' => $this->getNamespace(),
03887                 'page_title' => $this->getDBkey(),
03888                 'page_id=rev_page',
03889                 'page_latest != rev_id'
03890             ),
03891             __METHOD__,
03892             array( 'FOR UPDATE' )
03893         );
03894         # Return true if there was no history
03895         return ( $row === false );
03896     }
03897 
03905     public function isValidMoveTarget( $nt ) {
03906         # Is it an existing file?
03907         if ( $nt->getNamespace() == NS_FILE ) {
03908             $file = wfLocalFile( $nt );
03909             if ( $file->exists() ) {
03910                 wfDebug( __METHOD__ . ": file exists\n" );
03911                 return false;
03912             }
03913         }
03914         # Is it a redirect with no history?
03915         if ( !$nt->isSingleRevRedirect() ) {
03916             wfDebug( __METHOD__ . ": not a one-rev redirect\n" );
03917             return false;
03918         }
03919         # Get the article text
03920         $rev = Revision::newFromTitle( $nt, false, Revision::READ_LATEST );
03921         if ( !is_object( $rev ) ) {
03922             return false;
03923         }
03924         $content = $rev->getContent();
03925         # Does the redirect point to the source?
03926         # Or is it a broken self-redirect, usually caused by namespace collisions?
03927         $redirTitle = $content ? $content->getRedirectTarget() : null;
03928 
03929         if ( $redirTitle ) {
03930             if ( $redirTitle->getPrefixedDBkey() != $this->getPrefixedDBkey() &&
03931                 $redirTitle->getPrefixedDBkey() != $nt->getPrefixedDBkey() ) {
03932                 wfDebug( __METHOD__ . ": redirect points to other page\n" );
03933                 return false;
03934             } else {
03935                 return true;
03936             }
03937         } else {
03938             # Fail safe (not a redirect after all. strange.)
03939             wfDebug( __METHOD__ . ": failsafe: database sais " . $nt->getPrefixedDBkey() .
03940                         " is a redirect, but it doesn't contain a valid redirect.\n" );
03941             return false;
03942         }
03943     }
03944 
03952     public function getParentCategories() {
03953         global $wgContLang;
03954 
03955         $data = array();
03956 
03957         $titleKey = $this->getArticleID();
03958 
03959         if ( $titleKey === 0 ) {
03960             return $data;
03961         }
03962 
03963         $dbr = wfGetDB( DB_SLAVE );
03964 
03965         $res = $dbr->select(
03966             'categorylinks',
03967             'cl_to',
03968             array( 'cl_from' => $titleKey ),
03969             __METHOD__
03970         );
03971 
03972         if ( $res->numRows() > 0 ) {
03973             foreach ( $res as $row ) {
03974                 // $data[] = Title::newFromText($wgContLang->getNsText ( NS_CATEGORY ).':'.$row->cl_to);
03975                 $data[$wgContLang->getNsText( NS_CATEGORY ) . ':' . $row->cl_to] = $this->getFullText();
03976             }
03977         }
03978         return $data;
03979     }
03980 
03987     public function getParentCategoryTree( $children = array() ) {
03988         $stack = array();
03989         $parents = $this->getParentCategories();
03990 
03991         if ( $parents ) {
03992             foreach ( $parents as $parent => $current ) {
03993                 if ( array_key_exists( $parent, $children ) ) {
03994                     # Circular reference
03995                     $stack[$parent] = array();
03996                 } else {
03997                     $nt = Title::newFromText( $parent );
03998                     if ( $nt ) {
03999                         $stack[$parent] = $nt->getParentCategoryTree( $children + array( $parent => 1 ) );
04000                     }
04001                 }
04002             }
04003         }
04004 
04005         return $stack;
04006     }
04007 
04014     public function pageCond() {
04015         if ( $this->mArticleID > 0 ) {
04016             // PK avoids secondary lookups in InnoDB, shouldn't hurt other DBs
04017             return array( 'page_id' => $this->mArticleID );
04018         } else {
04019             return array( 'page_namespace' => $this->mNamespace, 'page_title' => $this->mDbkeyform );
04020         }
04021     }
04022 
04030     public function getPreviousRevisionID( $revId, $flags = 0 ) {
04031         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04032         $revId = $db->selectField( 'revision', 'rev_id',
04033             array(
04034                 'rev_page' => $this->getArticleID( $flags ),
04035                 'rev_id < ' . intval( $revId )
04036             ),
04037             __METHOD__,
04038             array( 'ORDER BY' => 'rev_id DESC' )
04039         );
04040 
04041         if ( $revId === false ) {
04042             return false;
04043         } else {
04044             return intval( $revId );
04045         }
04046     }
04047 
04055     public function getNextRevisionID( $revId, $flags = 0 ) {
04056         $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04057         $revId = $db->selectField( 'revision', 'rev_id',
04058             array(
04059                 'rev_page' => $this->getArticleID( $flags ),
04060                 'rev_id > ' . intval( $revId )
04061             ),
04062             __METHOD__,
04063             array( 'ORDER BY' => 'rev_id' )
04064         );
04065 
04066         if ( $revId === false ) {
04067             return false;
04068         } else {
04069             return intval( $revId );
04070         }
04071     }
04072 
04079     public function getFirstRevision( $flags = 0 ) {
04080         $pageId = $this->getArticleID( $flags );
04081         if ( $pageId ) {
04082             $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
04083             $row = $db->selectRow( 'revision', Revision::selectFields(),
04084                 array( 'rev_page' => $pageId ),
04085                 __METHOD__,
04086                 array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
04087             );
04088             if ( $row ) {
04089                 return new Revision( $row );
04090             }
04091         }
04092         return null;
04093     }
04094 
04101     public function getEarliestRevTime( $flags = 0 ) {
04102         $rev = $this->getFirstRevision( $flags );
04103         return $rev ? $rev->getTimestamp() : null;
04104     }
04105 
04111     public function isNewPage() {
04112         $dbr = wfGetDB( DB_SLAVE );
04113         return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
04114     }
04115 
04121     public function isBigDeletion() {
04122         global $wgDeleteRevisionsLimit;
04123 
04124         if ( !$wgDeleteRevisionsLimit ) {
04125             return false;
04126         }
04127 
04128         if ( $this->mIsBigDeletion === null ) {
04129             $dbr = wfGetDB( DB_SLAVE );
04130 
04131             $innerQuery = $dbr->selectSQLText(
04132                 'revision',
04133                 '1',
04134                 array( 'rev_page' => $this->getArticleID() ),
04135                 __METHOD__,
04136                 array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
04137             );
04138 
04139             $revCount = $dbr->query(
04140                 'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
04141                 __METHOD__
04142             );
04143             $revCount = $revCount->fetchRow();
04144             $revCount = $revCount['COUNT(*)'];
04145 
04146             $this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
04147         }
04148 
04149         return $this->mIsBigDeletion;
04150     }
04151 
04157     public function estimateRevisionCount() {
04158         if ( !$this->exists() ) {
04159             return 0;
04160         }
04161 
04162         if ( $this->mEstimateRevisions === null ) {
04163             $dbr = wfGetDB( DB_SLAVE );
04164             $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
04165                 array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
04166         }
04167 
04168         return $this->mEstimateRevisions;
04169     }
04170 
04180     public function countRevisionsBetween( $old, $new, $max = null ) {
04181         if ( !( $old instanceof Revision ) ) {
04182             $old = Revision::newFromTitle( $this, (int)$old );
04183         }
04184         if ( !( $new instanceof Revision ) ) {
04185             $new = Revision::newFromTitle( $this, (int)$new );
04186         }
04187         if ( !$old || !$new ) {
04188             return 0; // nothing to compare
04189         }
04190         $dbr = wfGetDB( DB_SLAVE );
04191         $conds = array(
04192             'rev_page' => $this->getArticleID(),
04193             'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04194             'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04195         );
04196         if ( $max !== null ) {
04197             $res = $dbr->select( 'revision', '1',
04198                 $conds,
04199                 __METHOD__,
04200                 array( 'LIMIT' => $max + 1 ) // extra to detect truncation
04201             );
04202             return $res->numRows();
04203         } else {
04204             return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
04205         }
04206     }
04207 
04224     public function getAuthorsBetween( $old, $new, $limit, $options = array() ) {
04225         if ( !( $old instanceof Revision ) ) {
04226             $old = Revision::newFromTitle( $this, (int)$old );
04227         }
04228         if ( !( $new instanceof Revision ) ) {
04229             $new = Revision::newFromTitle( $this, (int)$new );
04230         }
04231         // XXX: what if Revision objects are passed in, but they don't refer to this title?
04232         // Add $old->getPage() != $new->getPage() || $old->getPage() != $this->getArticleID()
04233         // in the sanity check below?
04234         if ( !$old || !$new ) {
04235             return null; // nothing to compare
04236         }
04237         $authors = array();
04238         $old_cmp = '>';
04239         $new_cmp = '<';
04240         $options = (array)$options;
04241         if ( in_array( 'include_old', $options ) ) {
04242             $old_cmp = '>=';
04243         }
04244         if ( in_array( 'include_new', $options ) ) {
04245             $new_cmp = '<=';
04246         }
04247         if ( in_array( 'include_both', $options ) ) {
04248             $old_cmp = '>=';
04249             $new_cmp = '<=';
04250         }
04251         // No DB query needed if $old and $new are the same or successive revisions:
04252         if ( $old->getId() === $new->getId() ) {
04253             return ( $old_cmp === '>' && $new_cmp === '<' ) ? array() : array( $old->getRawUserText() );
04254         } elseif ( $old->getId() === $new->getParentId() ) {
04255             if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
04256                 $authors[] = $old->getRawUserText();
04257                 if ( $old->getRawUserText() != $new->getRawUserText() ) {
04258                     $authors[] = $new->getRawUserText();
04259                 }
04260             } elseif ( $old_cmp === '>=' ) {
04261                 $authors[] = $old->getRawUserText();
04262             } elseif ( $new_cmp === '<=' ) {
04263                 $authors[] = $new->getRawUserText();
04264             }
04265             return $authors;
04266         }
04267         $dbr = wfGetDB( DB_SLAVE );
04268         $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
04269             array(
04270                 'rev_page' => $this->getArticleID(),
04271                 "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
04272                 "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
04273             ), __METHOD__,
04274             array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
04275         );
04276         foreach ( $res as $row ) {
04277             $authors[] = $row->rev_user_text;
04278         }
04279         return $authors;
04280     }
04281 
04296     public function countAuthorsBetween( $old, $new, $limit, $options = array() ) {
04297         $authors = $this->getAuthorsBetween( $old, $new, $limit, $options );
04298         return $authors ? count( $authors ) : 0;
04299     }
04300 
04307     public function equals( Title $title ) {
04308         // Note: === is necessary for proper matching of number-like titles.
04309         return $this->getInterwiki() === $title->getInterwiki()
04310             && $this->getNamespace() == $title->getNamespace()
04311             && $this->getDBkey() === $title->getDBkey();
04312     }
04313 
04320     public function isSubpageOf( Title $title ) {
04321         return $this->getInterwiki() === $title->getInterwiki()
04322             && $this->getNamespace() == $title->getNamespace()
04323             && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
04324     }
04325 
04335     public function exists() {
04336         $exists = $this->getArticleID() != 0;
04337         wfRunHooks( 'TitleExists', array( $this, &$exists ) );
04338         return $exists;
04339     }
04340 
04357     public function isAlwaysKnown() {
04358         $isKnown = null;
04359 
04370         wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
04371 
04372         if ( !is_null( $isKnown ) ) {
04373             return $isKnown;
04374         }
04375 
04376         if ( $this->isExternal() ) {
04377             return true;  // any interwiki link might be viewable, for all we know
04378         }
04379 
04380         switch ( $this->mNamespace ) {
04381             case NS_MEDIA:
04382             case NS_FILE:
04383                 // file exists, possibly in a foreign repo
04384                 return (bool)wfFindFile( $this );
04385             case NS_SPECIAL:
04386                 // valid special page
04387                 return SpecialPageFactory::exists( $this->getDBkey() );
04388             case NS_MAIN:
04389                 // selflink, possibly with fragment
04390                 return $this->mDbkeyform == '';
04391             case NS_MEDIAWIKI:
04392                 // known system message
04393                 return $this->hasSourceText() !== false;
04394             default:
04395                 return false;
04396         }
04397     }
04398 
04410     public function isKnown() {
04411         return $this->isAlwaysKnown() || $this->exists();
04412     }
04413 
04419     public function hasSourceText() {
04420         if ( $this->exists() ) {
04421             return true;
04422         }
04423 
04424         if ( $this->mNamespace == NS_MEDIAWIKI ) {
04425             // If the page doesn't exist but is a known system message, default
04426             // message content will be displayed, same for language subpages-
04427             // Use always content language to avoid loading hundreds of languages
04428             // to get the link color.
04429             global $wgContLang;
04430             list( $name, ) = MessageCache::singleton()->figureMessage(
04431                 $wgContLang->lcfirst( $this->getText() )
04432             );
04433             $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
04434             return $message->exists();
04435         }
04436 
04437         return false;
04438     }
04439 
04445     public function getDefaultMessageText() {
04446         global $wgContLang;
04447 
04448         if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
04449             return false;
04450         }
04451 
04452         list( $name, $lang ) = MessageCache::singleton()->figureMessage(
04453             $wgContLang->lcfirst( $this->getText() )
04454         );
04455         $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
04456 
04457         if ( $message->exists() ) {
04458             return $message->plain();
04459         } else {
04460             return false;
04461         }
04462     }
04463 
04469     public function invalidateCache() {
04470         if ( wfReadOnly() ) {
04471             return false;
04472         }
04473 
04474         if ( $this->mArticleID === 0 ) {
04475             return true; // avoid gap locking if we know it's not there
04476         }
04477 
04478         $method = __METHOD__;
04479         $dbw = wfGetDB( DB_MASTER );
04480         $conds = $this->pageCond();
04481         $dbw->onTransactionIdle( function () use ( $dbw, $conds, $method ) {
04482             $dbw->update(
04483                 'page',
04484                 array( 'page_touched' => $dbw->timestamp() ),
04485                 $conds,
04486                 $method
04487             );
04488         } );
04489 
04490         return true;
04491     }
04492 
04498     public function touchLinks() {
04499         $u = new HTMLCacheUpdate( $this, 'pagelinks' );
04500         $u->doUpdate();
04501 
04502         if ( $this->getNamespace() == NS_CATEGORY ) {
04503             $u = new HTMLCacheUpdate( $this, 'categorylinks' );
04504             $u->doUpdate();
04505         }
04506     }
04507 
04514     public function getTouched( $db = null ) {
04515         if ( $db === null ) {
04516             $db = wfGetDB( DB_SLAVE );
04517         }
04518         $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
04519         return $touched;
04520     }
04521 
04528     public function getNotificationTimestamp( $user = null ) {
04529         global $wgUser, $wgShowUpdatedMarker;
04530         // Assume current user if none given
04531         if ( !$user ) {
04532             $user = $wgUser;
04533         }
04534         // Check cache first
04535         $uid = $user->getId();
04536         // avoid isset here, as it'll return false for null entries
04537         if ( array_key_exists( $uid, $this->mNotificationTimestamp ) ) {
04538             return $this->mNotificationTimestamp[$uid];
04539         }
04540         if ( !$uid || !$wgShowUpdatedMarker || !$user->isAllowed( 'viewmywatchlist' ) ) {
04541             $this->mNotificationTimestamp[$uid] = false;
04542             return $this->mNotificationTimestamp[$uid];
04543         }
04544         // Don't cache too much!
04545         if ( count( $this->mNotificationTimestamp ) >= self::CACHE_MAX ) {
04546             $this->mNotificationTimestamp = array();
04547         }
04548         $dbr = wfGetDB( DB_SLAVE );
04549         $this->mNotificationTimestamp[$uid] = $dbr->selectField( 'watchlist',
04550             'wl_notificationtimestamp',
04551             array(
04552                 'wl_user' => $user->getId(),
04553                 'wl_namespace' => $this->getNamespace(),
04554                 'wl_title' => $this->getDBkey(),
04555             ),
04556             __METHOD__
04557         );
04558         return $this->mNotificationTimestamp[$uid];
04559     }
04560 
04567     public function getNamespaceKey( $prepend = 'nstab-' ) {
04568         global $wgContLang;
04569         // Gets the subject namespace if this title
04570         $namespace = MWNamespace::getSubject( $this->getNamespace() );
04571         // Checks if canonical namespace name exists for namespace
04572         if ( MWNamespace::exists( $this->getNamespace() ) ) {
04573             // Uses canonical namespace name
04574             $namespaceKey = MWNamespace::getCanonicalName( $namespace );
04575         } else {
04576             // Uses text of namespace
04577             $namespaceKey = $this->getSubjectNsText();
04578         }
04579         // Makes namespace key lowercase
04580         $namespaceKey = $wgContLang->lc( $namespaceKey );
04581         // Uses main
04582         if ( $namespaceKey == '' ) {
04583             $namespaceKey = 'main';
04584         }
04585         // Changes file to image for backwards compatibility
04586         if ( $namespaceKey == 'file' ) {
04587             $namespaceKey = 'image';
04588         }
04589         return $prepend . $namespaceKey;
04590     }
04591 
04598     public function getRedirectsHere( $ns = null ) {
04599         $redirs = array();
04600 
04601         $dbr = wfGetDB( DB_SLAVE );
04602         $where = array(
04603             'rd_namespace' => $this->getNamespace(),
04604             'rd_title' => $this->getDBkey(),
04605             'rd_from = page_id'
04606         );
04607         if ( $this->isExternal() ) {
04608             $where['rd_interwiki'] = $this->getInterwiki();
04609         } else {
04610             $where[] = 'rd_interwiki = ' . $dbr->addQuotes( '' ) . ' OR rd_interwiki IS NULL';
04611         }
04612         if ( !is_null( $ns ) ) {
04613             $where['page_namespace'] = $ns;
04614         }
04615 
04616         $res = $dbr->select(
04617             array( 'redirect', 'page' ),
04618             array( 'page_namespace', 'page_title' ),
04619             $where,
04620             __METHOD__
04621         );
04622 
04623         foreach ( $res as $row ) {
04624             $redirs[] = self::newFromRow( $row );
04625         }
04626         return $redirs;
04627     }
04628 
04634     public function isValidRedirectTarget() {
04635         global $wgInvalidRedirectTargets;
04636 
04637         // invalid redirect targets are stored in a global array, but explicitly disallow Userlogout here
04638         if ( $this->isSpecial( 'Userlogout' ) ) {
04639             return false;
04640         }
04641 
04642         foreach ( $wgInvalidRedirectTargets as $target ) {
04643             if ( $this->isSpecial( $target ) ) {
04644                 return false;
04645             }
04646         }
04647 
04648         return true;
04649     }
04650 
04656     public function getBacklinkCache() {
04657         return BacklinkCache::get( $this );
04658     }
04659 
04665     public function canUseNoindex() {
04666         global $wgContentNamespaces, $wgExemptFromUserRobotsControl;
04667 
04668         $bannedNamespaces = is_null( $wgExemptFromUserRobotsControl )
04669             ? $wgContentNamespaces
04670             : $wgExemptFromUserRobotsControl;
04671 
04672         return !in_array( $this->mNamespace, $bannedNamespaces );
04673 
04674     }
04675 
04686     public function getCategorySortkey( $prefix = '' ) {
04687         $unprefixed = $this->getText();
04688 
04689         // Anything that uses this hook should only depend
04690         // on the Title object passed in, and should probably
04691         // tell the users to run updateCollations.php --force
04692         // in order to re-sort existing category relations.
04693         wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
04694         if ( $prefix !== '' ) {
04695             # Separate with a line feed, so the unprefixed part is only used as
04696             # a tiebreaker when two pages have the exact same prefix.
04697             # In UCA, tab is the only character that can sort above LF
04698             # so we strip both of them from the original prefix.
04699             $prefix = strtr( $prefix, "\n\t", '  ' );
04700             return "$prefix\n$unprefixed";
04701         }
04702         return $unprefixed;
04703     }
04704 
04713     public function getPageLanguage() {
04714         global $wgLang, $wgLanguageCode;
04715         wfProfileIn( __METHOD__ );
04716         if ( $this->isSpecialPage() ) {
04717             // special pages are in the user language
04718             wfProfileOut( __METHOD__ );
04719             return $wgLang;
04720         }
04721 
04722         // Checking if DB language is set
04723         if ( $this->mDbPageLanguage ) {
04724             wfProfileOut( __METHOD__ );
04725             return wfGetLangObj( $this->mDbPageLanguage );
04726         }
04727 
04728         if ( !$this->mPageLanguage || $this->mPageLanguage[1] !== $wgLanguageCode ) {
04729             // Note that this may depend on user settings, so the cache should
04730             // be only per-request.
04731             // NOTE: ContentHandler::getPageLanguage() may need to load the
04732             // content to determine the page language!
04733             // Checking $wgLanguageCode hasn't changed for the benefit of unit
04734             // tests.
04735             $contentHandler = ContentHandler::getForTitle( $this );
04736             $langObj = wfGetLangObj( $contentHandler->getPageLanguage( $this ) );
04737             $this->mPageLanguage = array( $langObj->getCode(), $wgLanguageCode );
04738         } else {
04739             $langObj = wfGetLangObj( $this->mPageLanguage[0] );
04740         }
04741 
04742         wfProfileOut( __METHOD__ );
04743         return $langObj;
04744     }
04745 
04754     public function getPageViewLanguage() {
04755         global $wgLang;
04756 
04757         if ( $this->isSpecialPage() ) {
04758             // If the user chooses a variant, the content is actually
04759             // in a language whose code is the variant code.
04760             $variant = $wgLang->getPreferredVariant();
04761             if ( $wgLang->getCode() !== $variant ) {
04762                 return Language::factory( $variant );
04763             }
04764 
04765             return $wgLang;
04766         }
04767 
04768         // @note Can't be cached persistently, depends on user settings.
04769         // @note ContentHandler::getPageViewLanguage() may need to load the
04770         //   content to determine the page language!
04771         $contentHandler = ContentHandler::getForTitle( $this );
04772         $pageLang = $contentHandler->getPageViewLanguage( $this );
04773         return $pageLang;
04774     }
04775 
04786     public function getEditNotices( $oldid = 0 ) {
04787         $notices = array();
04788 
04789         # Optional notices on a per-namespace and per-page basis
04790         $editnotice_ns = 'editnotice-' . $this->getNamespace();
04791         $editnotice_ns_message = wfMessage( $editnotice_ns );
04792         if ( $editnotice_ns_message->exists() ) {
04793             $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
04794         }
04795         if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
04796             $parts = explode( '/', $this->getDBkey() );
04797             $editnotice_base = $editnotice_ns;
04798             while ( count( $parts ) > 0 ) {
04799                 $editnotice_base .= '-' . array_shift( $parts );
04800                 $editnotice_base_msg = wfMessage( $editnotice_base );
04801                 if ( $editnotice_base_msg->exists() ) {
04802                     $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
04803                 }
04804             }
04805         } else {
04806             # Even if there are no subpages in namespace, we still don't want / in MW ns.
04807             $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
04808             $editnoticeMsg = wfMessage( $editnoticeText );
04809             if ( $editnoticeMsg->exists() ) {
04810                 $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
04811             }
04812         }
04813 
04814         wfRunHooks( 'TitleGetEditNotices', array( $this, $oldid, &$notices ) );
04815         return $notices;
04816     }
04817 }