Change calls from Xml::namespaceSelector() to Html::namespaceSelector() since the...
[lhc/web/wiklou.git] / includes / Title.php
index a165a40..769adb9 100644 (file)
@@ -43,26 +43,10 @@ class Title {
 
        /**
         * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends
-        * to SELECT FOR UPDATE
+        * to use the master DB
         */
        const GAID_FOR_UPDATE = 1;
 
-       /**
-        * Used with getArticleID() and friends to load the object from the master
-        * database
-        */
-       const GAID_USE_MASTER = 2;
-
-       /**
-        * For use in load(). Field is available in LinkCache.
-        */
-       const FIELD_IN_LINKCACHE = 1;
-
-       /**
-        * For use in load(). Field is not available in LinkCache.
-        */
-       const FIELD_NOT_IN_LINKCACHE = 2;
-
        /**
         * @name Private member variables
         * Please use the accessor functions instead.
@@ -77,16 +61,11 @@ class Title {
        var $mNamespace = NS_MAIN;        // /< Namespace index, i.e. one of the NS_xxxx constants
        var $mInterwiki = '';             // /< Interwiki prefix (or null string)
        var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
-       private $mLoadedLevel = 0;        // /<
-       private $mLoadedFromMaster = false;
        var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
        var $mLatestID = false;           // /< ID of most recent revision
-       private $mCounter = false;        // /< Number of times this page has been viewed (-1 means "not loaded")
-       private $mTouched;                // /< Timestamp of the last time this page was touched
-       private $mIsNew;                  // /< Whether this is a "new page" (i.e. it has only one revision)
        private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
        var $mRestrictions = array();     // /< Array of groups allowed to edit this article
-       var $mOldRestrictions;
+       var $mOldRestrictions = false;
        var $mCascadeRestriction;         ///< Cascade restrictions on this page to included templates and images?
        var $mCascadingRestrictions;      // Caching the results of getCascadeProtectionSources
        var $mRestrictionsExpiry = array(); ///< When do the restrictions on this page expire?
@@ -222,11 +201,11 @@ class Title {
         * Create a new Title from an article ID
         *
         * @param $id Int the page_id corresponding to the Title to create
-        * @param $flags Int use Title::GAID_USE_MASTER to use master
+        * @param $flags Int use Title::GAID_FOR_UPDATE to use master
         * @return Title the new object, or NULL on an error
         */
        public static function newFromID( $id, $flags = 0 ) {
-               $db = $flags ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+               $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                $row = $db->selectRow( 'page', '*', array( 'page_id' => $id ), __METHOD__ );
                if ( $row !== false ) {
                        $title = Title::newFromRow( $row );
@@ -248,8 +227,15 @@ class Title {
                }
                $dbr = wfGetDB( DB_SLAVE );
 
-               $res = $dbr->select( 'page', self::selectFields(),
-                       array( 'page_id' => $ids ), __METHOD__ );
+               $res = $dbr->select(
+                       'page',
+                       array(
+                               'page_namespace', 'page_title', 'page_id',
+                               'page_len', 'page_is_redirect', 'page_latest',
+                       ),
+                       array( 'page_id' => $ids ),
+                       __METHOD__
+               );
 
                $titles = array();
                foreach ( $res as $row ) {
@@ -274,81 +260,24 @@ class Title {
         * Load Title object fields from a DB row.
         * If false is given, the title will be treated as non-existing.
         *
-        * @param $row Object|false database row
-        * @param $wasFromMaster bool: whether the row was loaded from the master
-        *        database
-        * @return void
+        * @param $row Object|bool database row
         */
-       public function loadFromRow( $row, $wasFromMaster = false ) {
+       public function loadFromRow( $row ) {
                if ( $row ) { // page found
-                       $cacheLevel = self::FIELD_NOT_IN_LINKCACHE;
-
-                       # Items that cannot be stored in LinkCache
-                       # If one (or more) of these field is missing, the row can still
-                       # be stored in LinkCache
-                       if ( isset( $row->page_counter ) ) {
-                               $this->mCounter = (int)$row->page_counter;
-                       } else {
-                               $cacheLevel = self::FIELD_IN_LINKCACHE;
-                       }
-                       if ( isset( $row->page_touched ) ) {
-                               $this->mTouched = $row->page_touched;
-                       } else {
-                               $cacheLevel = self::FIELD_IN_LINKCACHE;
-                       }
-                       if ( isset( $row->page_is_new ) ) {
-                               $this->mIsNew = (bool)$row->page_is_new;
-                       } else {
-                               $cacheLevel = self::FIELD_IN_LINKCACHE;
-                       }
-                       if ( isset( $row->page_restrictions ) ) {
-                               $this->mOldRestrictions = $row->page_restrictions;
-                       } else {
-                               $cacheLevel = self::FIELD_IN_LINKCACHE;
-                       }
-
-                       # Items that can be stored in LinkCache
-                       # If one (or more) of these field is missing, the row cannot
-                       # be stored in LinkCache
-                       if ( isset( $row->page_id ) ) {
+                       if ( isset( $row->page_id ) )
                                $this->mArticleID = (int)$row->page_id;
-                       } else {
-                               $cacheLevel = 0;
-                       }
-                       if ( isset( $row->page_len ) ) {
+                       if ( isset( $row->page_len ) )
                                $this->mLength = (int)$row->page_len;
-                       } else {
-                               $cacheLevel = 0;
-                       }
-                       if ( isset( $row->page_is_redirect ) ) {
+                       if ( isset( $row->page_is_redirect ) )
                                $this->mRedirect = (bool)$row->page_is_redirect;
-                       } else {
-                               $cacheLevel = 0;
-                       }
-                       if ( isset( $row->page_latest ) ) {
+                       if ( isset( $row->page_latest ) )
                                $this->mLatestID = (int)$row->page_latest;
-                       } else {
-                               $cacheLevel = 0;
-                       }
-
-                       $this->mLoadedLevel = $cacheLevel;
-                       if ( $cacheLevel > 0 ) {
-                               # We have all fields required by LinkCache
-                               LinkCache::singleton()->addGoodLinkObjFromRow( $this, $row );
-                       }
                } else { // page not found
                        $this->mArticleID = 0;
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
-                       $this->mCounter = 0;
-                       $this->mTouched = '19700101000000';
-                       $this->mIsNew = false;
-                       $this->mOldRestrictions = false;
-                       $this->mLoadedLevel = 2;
-                       LinkCache::singleton()->addBadLinkObj( $this );
                }
-               $this->mLoadedFromMaster = $wasFromMaster;
        }
 
        /**
@@ -536,27 +465,6 @@ class Title {
                return $n;
        }
 
-       /**
-        * Get the fields of the `page` table that have to be select if you want
-        * to give a complete row to loadFromRow()
-        *
-        * @return array
-        */
-       public static function selectFields() {
-               return array(
-                       'page_namespace',
-                       'page_title',
-                       'page_id',
-                       'page_len',
-                       'page_is_redirect',
-                       'page_latest',
-                       'page_counter',
-                       'page_touched',
-                       'page_is_new',
-                       'page_restrictions',
-               );
-       }
-
        /**
         * Get a regex character class describing the legal characters in a link
         *
@@ -954,6 +862,7 @@ class Title {
         * This is MUCH simpler than individually testing for equivilance
         * against both NS_USER and NS_USER_TALK, and is also forward compatible.
         * @since 1.19
+        * @return bool
         */
        public function hasSubjectNamespace( $ns ) {
                return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
@@ -1310,8 +1219,19 @@ class Title {
 
        /**
         * Helper to fix up the get{Local,Full,Link,Canonical}URL args
+        * get{Canonical,Full,Link,Local}URL methods accepted an optional
+        * second argument named variant. This was deprecated in favor
+        * of passing an array of option with a "variant" key
+        * Once $query2 is removed for good, this helper can be dropped
+        * andthe wfArrayToCGI moved to getLocalURL();
+        *
+        * @since 1.19 (r105919)
+        * @return String
         */
-       private static function fixUrlQueryArgs( $query, $query2 ) {
+       private static function fixUrlQueryArgs( $query, $query2 = false ) {
+               if( $query2 !== false ) {
+                       wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" );
+               }
                if ( is_array( $query ) ) {
                        $query = wfArrayToCGI( $query );
                }
@@ -1365,7 +1285,7 @@ class Title {
         * with action=render, $wgServer is prepended.
         *
 
-        * @param $query \twotypes{\string,\array} an optional query string,
+        * @param $query string|array an optional query string,
         *   not used for interwiki     links. Can be specified as an associative array as well,
         *   e.g., array( 'action' => 'edit' ) (keys and values will be URL-escaped).
         *   Some query patterns will trigger various shorturl path replacements.
@@ -1373,6 +1293,9 @@ class Title {
         *   be an array. If a string is passed it will be interpreted as a deprecated
         *   variant argument and urlencoded into a variant= argument.
         *   This second query argument will be added to the $query
+        *   The second parameter is deprecated since 1.19. Pass it as a key,value
+        *   pair in the first parameter array instead.
+        *
         * @return String the URL
         */
        public function getLocalURL( $query = '', $query2 = false ) {
@@ -1556,6 +1479,7 @@ class Title {
         *
         * @see self::getLocalURL
         * @since 1.18
+        * @return string
         */
        public function escapeCanonicalURL( $query = '', $query2 = false ) {
                wfDeprecated( __METHOD__, '1.19' );
@@ -1577,82 +1501,6 @@ class Title {
                return $s;
        }
 
-       /**
-        * Load field from database into this object
-        *
-        * @param $level int, may be on of the following values:
-        *   - self::FIELD_IN_LINKCACHE: the field can be retrived from the LinkCache
-        *   - self::FIELD_NOT_IN_LINKCACHE: the field is not stored in LinkCache and
-        *     must be loaded from the database
-        * @param $flags int, may be on of the following values:
-        *   - 0: to use a slave connection
-        *   - self::GAID_USE_MASTER to use a master connection
-        *   - self::GAID_FOR_UPDATE to SELECT FROM UPDATE from a master connection
-        */
-       private function load( $level, $flags = 0 ) {
-               global $wgAntiLockFlags;
-
-               if ( !$this->canExist() ) {
-                       return;
-               }
-
-               // Check whether the wanted item is already loaded
-               // and from where it is requested.
-               // If $flags is self::GAID_FOR_UPDATE, it will always be reloaded.
-               if ( $level <= $this->mLoadedLevel && ( $flags === 0 ||
-                       ( $flags === self::GAID_USE_MASTER && $this->mLoadedFromMaster ) ) )
-               {
-                       return;
-               }
-
-               $linkCache = LinkCache::singleton();
-
-               # Only use the LinkCache if we can load from a slave database
-               if ( $flags === 0 ) {
-
-                       # If the LinkCache says the page doesn't exist; we can load all fields
-                       if ( $linkCache->isBadLink( $this->getPrefixedDBkey() ) ) {
-                               $this->loadFromRow( false );
-                               return;
-                       }
-
-                       # For existing pages we can only load some fields
-                       if ( $level === self::FIELD_IN_LINKCACHE ) {
-                               $id = $linkCache->getGoodLinkID( $this->getPrefixedDBkey() );
-                               if ( $id ) {
-                                       $this->mArticleID = $id;
-                                       $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
-                                       $this->mLength = (int)$linkCache->getGoodLinkFieldObj( $this, 'length' );
-                                       $this->mLatestID = (int)$linkCache->getGoodLinkFieldObj( $this, 'revision' );
-                                       $this->mLoadedLevel = 1;
-                                       $this->mLoadedFromMaster = false;
-                                       return;
-                               }
-                       }
-               }
-
-               # Just in case it's already loaded from a slave database
-               $linkCache->clearLink( $this );
-
-               # No success using LinkCache, we need to use the database
-               # In this case we load the complete row regardless of $level
-               $options = array();
-               if ( $flags === 0 ) {
-                       $db = wfGetDB( DB_SLAVE );
-                       $this->mLoadedFromMaster = false;
-               } else {
-                       $db = wfGetDB( DB_MASTER );
-                       if ( $flags == self::GAID_FOR_UPDATE && !( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) ) {
-                               $options[] = 'FOR UPDATE';
-                       }
-                       $this->mLoadedFromMaster = true;
-               }
-
-               $row = $db->selectRow( 'page', self::selectFields(),
-                       $this->pageCond(), __METHOD__, $options );
-               $this->loadFromRow( $row );
-       }
-
        /**
         * Is $wgUser watching this page?
         *
@@ -2079,7 +1927,7 @@ class Title {
                        // Don't block the user from editing their own talk page unless they've been
                        // explicitly blocked from that too.
                } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
-                       $block = $user->mBlock;
+                       $block = $user->getBlock();
 
                        // This is from OutputPage::blockedPage
                        // Copied at r23888 by werdna
@@ -2099,15 +1947,15 @@ class Title {
 
                        $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
                        $blockid = $block->getId();
-                       $blockExpiry = $user->mBlock->mExpiry;
-                       $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
+                       $blockExpiry = $block->getExpiry();
+                       $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true );
                        if ( $blockExpiry == 'infinity' ) {
                                $blockExpiry = wfMessage( 'infiniteblock' )->text();
                        } else {
                                $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
                        }
 
-                       $intended = strval( $user->mBlock->getTarget() );
+                       $intended = strval( $block->getTarget() );
 
                        $errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
                                $blockid, $blockExpiry, $intended, $blockTimestamp );
@@ -2128,11 +1976,11 @@ class Title {
         * @return Array list of errors
         */
        private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+               global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
                static $useShortcut = null;
 
                # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
                if ( is_null( $useShortcut ) ) {
-                       global $wgGroupPermissions, $wgRevokePermissions;
                        $useShortcut = true;
                        if ( empty( $wgGroupPermissions['*']['read'] ) ) {
                                # Not a public wiki, so no shortcut
@@ -2154,61 +2002,56 @@ class Title {
                        }
                }
 
-               # Shortcut for public wikis, allows skipping quite a bit of code
+               $whitelisted = false;
                if ( $useShortcut ) {
-                       return $errors;
-               }
-
-               # If the user is allowed to read pages, he is allowed to read all pages
-               if ( $user->isAllowed( 'read' ) ) {
-                       return $errors;
-               }
-
-               # Always grant access to the login page.
-               # Even anons need to be able to log in.
-               if ( $this->isSpecial( 'Userlogin' )
+                       # Shortcut for public wikis, allows skipping quite a bit of code
+                       $whitelisted = true;
+               } elseif ( $user->isAllowed( 'read' ) ) {
+                       # If the user is allowed to read pages, he is allowed to read all pages
+                       $whitelisted = true;
+               } elseif ( $this->isSpecial( 'Userlogin' )
                        || $this->isSpecial( 'ChangePassword' )
                        || $this->isSpecial( 'PasswordReset' )
                ) {
-                       return $errors;
-               }
-
-               # Time to check the whitelist
-               global $wgWhitelistRead;
-
-               # Only do these checks is there's something to check against
-               if ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
-                       # Check for explicit whitelisting
+                       # Always grant access to the login page.
+                       # Even anons need to be able to log in.
+                       $whitelisted = true;
+               } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
+                       # Time to check the whitelist
+                       # Only do these checks is there's something to check against
                        $name = $this->getPrefixedText();
                        $dbName = $this->getPrefixedDBKey();
 
-                       // Check with and without underscores
+                       // Check for explicit whitelisting with and without underscores
                        if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
-                               return $errors;
-                       }
-
-                       # Old settings might have the title prefixed with
-                       # a colon for main-namespace pages
-                       if ( $this->getNamespace() == NS_MAIN ) {
+                               $whitelisted = true;
+                       } elseif ( $this->getNamespace() == NS_MAIN ) {
+                               # Old settings might have the title prefixed with
+                               # a colon for main-namespace pages
                                if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
-                                       return $errors;
+                                       $whitelisted = true;
                                }
-                       }
-
-                       # If it's a special page, ditch the subpage bit and check again
-                       if ( $this->isSpecialPage() ) {
+                       } elseif ( $this->isSpecialPage() ) {
+                               # If it's a special page, ditch the subpage bit and check again
                                $name = $this->getDBkey();
                                list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
                                if ( $name !== false ) {
                                        $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
                                        if ( in_array( $pure, $wgWhitelistRead, true ) ) {
-                                               return $errors;
+                                               $whitelisted = true;
                                        }
                                }
                        }
                }
 
-               $errors[] = $this->missingPermissionError( $action, $short );
+               if ( !$whitelisted ) {
+                       # If the title is not whitelisted, give extensions a chance to do so...
+                       wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
+                       if ( !$whitelisted ) {
+                               $errors[] = $this->missingPermissionError( $action, $short );
+                       }
+               }
+
                return $errors;
        }
 
@@ -2656,15 +2499,17 @@ class Title {
         * Loads a string into mRestrictions array
         *
         * @param $res Resource restrictions as an SQL result.
+        * @param $oldFashionedRestrictions String comma-separated list of page
+        *        restrictions from page table (pre 1.10)
         */
-       private function loadRestrictionsFromResultWrapper( $res ) {
+       private function loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions = null ) {
                $rows = array();
 
                foreach ( $res as $row ) {
                        $rows[] = $row;
                }
 
-               $this->loadRestrictionsFromRows( $rows );
+               $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
        }
 
        /**
@@ -2673,8 +2518,10 @@ class Title {
         * Public for usage by LiquidThreads.
         *
         * @param $rows array of db result objects
+        * @param $oldFashionedRestrictions string comma-separated list of page
+        *        restrictions from page table (pre 1.10)
         */
-       public function loadRestrictionsFromRows( $rows ) {
+       public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
                global $wgContLang;
                $dbr = wfGetDB( DB_SLAVE );
 
@@ -2689,13 +2536,14 @@ class Title {
 
                # Backwards-compatibility: also load the restrictions from the page record (old format).
 
-               if ( $this->mOldRestrictions === null ) {
-                       $this->mOldRestrictions = $dbr->selectField( 'page', 'page_restrictions',
-                               array( 'page_id' => $this->getArticleId() ), __METHOD__ );
+               if ( $oldFashionedRestrictions === null ) {
+                       $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
+                               array( 'page_id' => $this->getArticleID() ), __METHOD__ );
                }
 
-               if ( $this->mOldRestrictions !== false && $this->mOldRestrictions !== '' ) {
-                       foreach ( explode( ':', trim( $this->mOldRestrictions ) ) as $restrict ) {
+               if ( $oldFashionedRestrictions != '' ) {
+
+                       foreach ( explode( ':', trim( $oldFashionedRestrictions ) ) as $restrict ) {
                                $temp = explode( '=', trim( $restrict ) );
                                if ( count( $temp ) == 1 ) {
                                        // old old format should be treated as edit/move restriction
@@ -2705,6 +2553,9 @@ class Title {
                                        $this->mRestrictions[$temp[0]] = explode( ',', trim( $temp[1] ) );
                                }
                        }
+
+                       $this->mOldRestrictions = true;
+
                }
 
                if ( count( $rows ) ) {
@@ -2745,8 +2596,11 @@ class Title {
 
        /**
         * Load restrictions from the page_restrictions table
+        *
+        * @param $oldFashionedRestrictions String comma-separated list of page
+        *        restrictions from page table (pre 1.10)
         */
-       public function loadRestrictions() {
+       public function loadRestrictions( $oldFashionedRestrictions = null ) {
                global $wgContLang;
                if ( !$this->mRestrictionsLoaded ) {
                        if ( $this->exists() ) {
@@ -2755,11 +2609,11 @@ class Title {
                                $res = $dbr->select(
                                        'page_restrictions',
                                        '*',
-                                       array( 'pr_page' => $this->getArticleId() ),
+                                       array( 'pr_page' => $this->getArticleID() ),
                                        __METHOD__
                                );
 
-                               $this->loadRestrictionsFromResultWrapper( $res );
+                               $this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
                        } else {
                                $title_protection = $this->getTitleProtection();
 
@@ -2914,88 +2768,69 @@ class Title {
        }
 
        /**
-        * Get the number of views of this page
-        *
-        * @return int The view count for the page
-        */
-       public function getCount() {
-               if ( $this->mCounter == false ) {
-                       $this->load( self::FIELD_NOT_IN_LINKCACHE );
-               }
-
-               return $this->mCounter;
-       }
-
-       /**
-        * Get the last touched timestamp
+        * Get the article ID for this Title from the link cache,
+        * adding it if necessary
         *
-        * @return String last-touched timestamp
-        */
-       public function getTouched() {
-               if ( $this->mTouched == null ) {
-                       $this->load( self::FIELD_NOT_IN_LINKCACHE );
-               }
-
-               return $this->mTouched;
-       }
-
-       /**
-        * Check if this is a new page (i.e. it has only one revision)
-        *
-        * @return bool
-        */
-       public function isNewPage() {
-               if ( $this->mIsNew === null ) {
-                       $this->load( self::FIELD_NOT_IN_LINKCACHE );
-               }
-
-               return $this->mIsNew;
-       }
-
-       /**
-        * Get the article ID for this page.
-        * Uses link cache, adding it if necessary.
-        *
-        * @param $flags Int a bit field; may be Title::GAID_USE_MASTER to select
-        *  from the master database or Title::GAID_FOR_UPDATE to select for update.
+        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select
+        *  for update
         * @return Int the ID
         */
        public function getArticleID( $flags = 0 ) {
-               if ( $this->mArticleID === -1 || $flags ) {
-                       $this->load( self::FIELD_IN_LINKCACHE, $flags );
+               if ( $this->getNamespace() < 0 ) {
+                       return $this->mArticleID = 0;
+               }
+               $linkCache = LinkCache::singleton();
+               if ( $flags & self::GAID_FOR_UPDATE ) {
+                       $oldUpdate = $linkCache->forUpdate( true );
+                       $linkCache->clearLink( $this );
+                       $this->mArticleID = $linkCache->addLinkObj( $this );
+                       $linkCache->forUpdate( $oldUpdate );
+               } else {
+                       if ( -1 == $this->mArticleID ) {
+                               $this->mArticleID = $linkCache->addLinkObj( $this );
+                       }
                }
-
                return $this->mArticleID;
        }
 
        /**
         * Is this an article that is a redirect page?
-        * Uses link cache, adding it if necessary.
+        * Uses link cache, adding it if necessary
         *
-        * @param $flags Int a bit field; may be Title::GAID_USE_MASTER to select
-        *  from the master database or Title::GAID_FOR_UPDATE to select for update.
+        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
         * @return Bool
         */
        public function isRedirect( $flags = 0 ) {
-               if ( $this->mRedirect === null || $flags ) {
-                       $this->load( self::FIELD_IN_LINKCACHE, $flags );
+               if ( !is_null( $this->mRedirect ) ) {
+                       return $this->mRedirect;
+               }
+               # Calling getArticleID() loads the field from cache as needed
+               if ( !$this->getArticleID( $flags ) ) {
+                       return $this->mRedirect = false;
                }
+               $linkCache = LinkCache::singleton();
+               $this->mRedirect = (bool)$linkCache->getGoodLinkFieldObj( $this, 'redirect' );
 
                return $this->mRedirect;
        }
 
        /**
         * What is the length of this page?
-        * Uses link cache, adding it if necessary.
+        * Uses link cache, adding it if necessary
         *
-        * @param $flags Int a bit field; may be Title::GAID_USE_MASTER to select
-        *  from the master database or Title::GAID_FOR_UPDATE to select for update.
+        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
         * @return Int
         */
        public function getLength( $flags = 0 ) {
-               if ( $this->mLength === -1 || $flags ) {
-                       $this->load( self::FIELD_IN_LINKCACHE, $flags );
+               if ( $this->mLength != -1 ) {
+                       return $this->mLength;
                }
+               # Calling getArticleID() loads the field from cache as needed
+               if ( !$this->getArticleID( $flags ) ) {
+                       return $this->mLength = 0;
+               }
+               $linkCache = LinkCache::singleton();
+               $this->mLength = intval( $linkCache->getGoodLinkFieldObj( $this, 'length' ) );
 
                return $this->mLength;
        }
@@ -3003,14 +2838,19 @@ class Title {
        /**
         * What is the page_latest field for this page?
         *
-        * @param $flags Int a bit field; may be Title::GAID_USE_MASTER to select
-        *  from the master database or Title::GAID_FOR_UPDATE to select for update.
+        * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
         * @return Int or 0 if the page doesn't exist
         */
        public function getLatestRevID( $flags = 0 ) {
-               if ( $this->mLatestID === false || $flags ) {
-                       $this->load( self::FIELD_IN_LINKCACHE, $flags );
+               if ( $this->mLatestID !== false ) {
+                       return intval( $this->mLatestID );
+               }
+               # Calling getArticleID() loads the field from cache as needed
+               if ( !$this->getArticleID( $flags ) ) {
+                       return $this->mLatestID = 0;
                }
+               $linkCache = LinkCache::singleton();
+               $this->mLatestID = intval( $linkCache->getGoodLinkFieldObj( $this, 'revision' ) );
 
                return $this->mLatestID;
        }
@@ -3026,29 +2866,20 @@ class Title {
         * @param $newid Int the new Article ID
         */
        public function resetArticleID( $newid ) {
-               LinkCache::singleton()->clearLink( $this );
+               $linkCache = LinkCache::singleton();
+               $linkCache->clearLink( $this );
 
-               if ( $newid === 0 ) {
-                       $this->loadFromRow( false );
+               if ( $newid === false ) {
+                       $this->mArticleID = -1;
                } else {
-                       if ( $newid === false ) {
-                               $this->mArticleID = -1;
-                       } else {
-                               $this->mArticleID = intval( $newid );
-                       }
-                       $this->mRestrictionsLoaded = false;
-                       $this->mRestrictions = array();
-                       $this->mOldRestrictions = null;
-                       $this->mRedirect = null;
-                       $this->mLength = -1;
-                       $this->mLatestID = false;
-                       $this->mCounter = false;
-                       $this->mTouched = '19700101000000';
-                       $this->mIsNew = null;
-                       $this->mEstimateRevisions = null;
-                       $this->mLoadedLevel = 0;
-                       $this->mLoadedFromMaster = false;
+                       $this->mArticleID = intval( $newid );
                }
+               $this->mRestrictionsLoaded = false;
+               $this->mRestrictions = array();
+               $this->mRedirect = null;
+               $this->mLength = -1;
+               $this->mLatestID = false;
+               $this->mEstimateRevisions = null;
        }
 
        /**
@@ -3336,7 +3167,7 @@ class Title {
         * @return Array of Title objects linking here
         */
        public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
-               $id = $this->getArticleId();
+               $id = $this->getArticleID();
 
                # If the page doesn't exist; there can't be any link from this page
                if ( !$id ) {
@@ -3400,7 +3231,7 @@ class Title {
         * @return Array of Title the Title objects
         */
        public function getBrokenLinksFrom() {
-               if ( $this->getArticleId() == 0 ) {
+               if ( $this->getArticleID() == 0 ) {
                        # All links from article ID 0 are false positives
                        return array();
                }
@@ -3410,7 +3241,7 @@ class Title {
                        array( 'page', 'pagelinks' ),
                        array( 'pl_namespace', 'pl_title' ),
                        array(
-                               'pl_from' => $this->getArticleId(),
+                               'pl_from' => $this->getArticleID(),
                                'page_namespace IS NULL'
                        ),
                        __METHOD__, array(),
@@ -3645,7 +3476,7 @@ class Title {
                        RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
                }
 
-               $dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
+               $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
                $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
                $protected = $this->isProtected();
 
@@ -3653,7 +3484,7 @@ class Title {
                $err = $this->moveToInternal( $nt, $reason, $createRedirect );
                if ( is_array( $err ) ) {
                        # @todo FIXME: What about the File we have already moved?
-                       $dbw->rollback();
+                       $dbw->rollback( __METHOD__ );
                        return $err;
                }
 
@@ -3717,7 +3548,7 @@ class Title {
                        WatchedItem::duplicateEntries( $this, $nt );
                }
 
-               $dbw->commit();
+               $dbw->commit( __METHOD__ );
 
                wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
                return true;
@@ -3764,7 +3595,6 @@ class Title {
                $comment = $wgContLang->truncate( $comment, 255 );
 
                $oldid = $this->getArticleID();
-               $latest = $this->getLatestRevID();
 
                $dbw = wfGetDB( DB_MASTER );
 
@@ -3805,10 +3635,14 @@ class Title {
                $newpage->updateRevisionOn( $dbw, $nullRevision );
 
                wfRunHooks( 'NewRevisionFromEditComplete',
-                       array( $newpage, $nullRevision, $latest, $wgUser ) );
+                       array( $newpage, $nullRevision, $nullRevision->getParentId(), $wgUser ) );
 
                $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
 
+               if ( !$moveOverRedirect ) {
+                       WikiPage::onArticleCreate( $nt );
+               }
+
                # Recreate the redirect, this time in the other direction.
                if ( $redirectSuppressed ) {
                        WikiPage::onArticleDelete( $this );
@@ -3880,8 +3714,8 @@ class Title {
                        // We don't know whether this function was called before
                        // or after moving the root page, so check both
                        // $this and $nt
-                       if ( $oldSubpage->getArticleId() == $this->getArticleId() ||
-                                       $oldSubpage->getArticleID() == $nt->getArticleId() )
+                       if ( $oldSubpage->getArticleID() == $this->getArticleID() ||
+                                       $oldSubpage->getArticleID() == $nt->getArticleID() )
                        {
                                // When moving a page to a subpage of itself,
                                // don't move it twice
@@ -4005,7 +3839,7 @@ class Title {
 
                $data = array();
 
-               $titleKey = $this->getArticleId();
+               $titleKey = $this->getArticleID();
 
                if ( $titleKey === 0 ) {
                        return $data;
@@ -4083,7 +3917,7 @@ class Title {
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                return $db->selectField( 'revision', 'rev_id',
                        array(
-                               'rev_page' => $this->getArticleId( $flags ),
+                               'rev_page' => $this->getArticleID( $flags ),
                                'rev_id < ' . intval( $revId )
                        ),
                        __METHOD__,
@@ -4102,7 +3936,7 @@ class Title {
                $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                return $db->selectField( 'revision', 'rev_id',
                        array(
-                               'rev_page' => $this->getArticleId( $flags ),
+                               'rev_page' => $this->getArticleID( $flags ),
                                'rev_id > ' . intval( $revId )
                        ),
                        __METHOD__,
@@ -4117,7 +3951,7 @@ class Title {
         * @return Revision|Null if page doesn't exist
         */
        public function getFirstRevision( $flags = 0 ) {
-               $pageId = $this->getArticleId( $flags );
+               $pageId = $this->getArticleID( $flags );
                if ( $pageId ) {
                        $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
                        $row = $db->selectRow( 'revision', '*',
@@ -4143,6 +3977,16 @@ class Title {
                return $rev ? $rev->getTimestamp() : null;
        }
 
+       /**
+        * Check if this is a new page
+        *
+        * @return bool
+        */
+       public function isNewPage() {
+               $dbr = wfGetDB( DB_SLAVE );
+               return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
+       }
+
        /**
         * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
         *
@@ -4172,7 +4016,7 @@ class Title {
                if ( $this->mEstimateRevisions === null ) {
                        $dbr = wfGetDB( DB_SLAVE );
                        $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
-                               array( 'rev_page' => $this->getArticleId() ), __METHOD__ );
+                               array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
                }
 
                return $this->mEstimateRevisions;
@@ -4199,7 +4043,7 @@ class Title {
                $dbr = wfGetDB( DB_SLAVE );
                return (int)$dbr->selectField( 'revision', 'count(*)',
                        array(
-                               'rev_page' => $this->getArticleId(),
+                               'rev_page' => $this->getArticleID(),
                                'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
                                'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                        ),
@@ -4273,7 +4117,7 @@ class Title {
         * @return Bool
         */
        public function exists() {
-               return $this->getArticleId() != 0;
+               return $this->getArticleID() != 0;
        }
 
        /**
@@ -4293,9 +4137,28 @@ class Title {
         * @return Bool
         */
        public function isAlwaysKnown() {
+               $isKnown = null;
+
+               /**
+                * Allows overriding default behaviour for determining if a page exists.
+                * If $isKnown is kept as null, regular checks happen. If it's
+                * a boolean, this value is returned by the isKnown method.
+                *
+                * @since 1.20
+                *
+                * @param Title $title
+                * @param boolean|null $isKnown
+                */
+               wfRunHooks( 'TitleIsAlwaysKnown', array( $this, &$isKnown ) );
+
+               if ( !is_null( $isKnown ) ) {
+                       return $isKnown;
+               }
+
                if ( $this->mInterwiki != '' ) {
                        return true;  // any interwiki link might be viewable, for all we know
                }
+
                switch( $this->mNamespace ) {
                        case NS_MEDIA:
                        case NS_FILE:
@@ -4320,6 +4183,9 @@ class Title {
         * viewed?  In particular, this function may be used to determine if
         * links to the title should be rendered as "bluelinks" (as opposed to
         * "redlinks" to non-existent pages).
+        * Adding something else to this function will cause inconsistency
+        * since LinkHolderArray calls isAlwaysKnown() and does its own
+        * page existence check.
         *
         * @return Bool
         */
@@ -4408,6 +4274,18 @@ class Title {
                }
        }
 
+       /**
+        * Get the last touched timestamp
+        *
+        * @param $db DatabaseBase: optional db
+        * @return String last-touched timestamp
+        */
+       public function getTouched( $db = null ) {
+               $db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
+               $touched = $db->selectField( 'page', 'page_touched', $this->pageCond(), __METHOD__ );
+               return $touched;
+       }
+
        /**
         * Get the timestamp when this page was updated since the user last saw it.
         *
@@ -4600,7 +4478,7 @@ class Title {
                if ( $this->isSpecialPage() ) {
                        // special pages are in the user language
                        return $wgLang;
-               } elseif ( $this->isCssOrJsPage() ) {
+               } elseif ( $this->isCssOrJsPage() || $this->isCssJsSubpage() ) {
                        // css/js should always be LTR and is, in fact, English
                        return wfGetLangObj( 'en' );
                } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {