Change calls from Xml::namespaceSelector() to Html::namespaceSelector() since the...
[lhc/web/wiklou.git] / includes / Title.php
index 1e312d4..769adb9 100644 (file)
@@ -63,7 +63,7 @@ class Title {
        var $mFragment;                   // /< Title fragment (i.e. the bit after the #)
        var $mArticleID = -1;             // /< Article ID, fetched from the link cache on demand
        var $mLatestID = false;           // /< ID of most recent revision
-       var $mCounter = -1;               // /< Number of times this page has been viewed (-1 means "not loaded")
+       private $mEstimateRevisions;      // /< Estimated number of revisions; null of not loaded
        var $mRestrictions = array();     // /< Array of groups allowed to edit this article
        var $mOldRestrictions = false;
        var $mCascadeRestriction;         ///< Cascade restrictions on this page to included templates and images?
@@ -260,8 +260,7 @@ 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
-        * @return void
+        * @param $row Object|bool database row
         */
        public function loadFromRow( $row ) {
                if ( $row ) { // page found
@@ -273,14 +272,11 @@ class Title {
                                $this->mRedirect = (bool)$row->page_is_redirect;
                        if ( isset( $row->page_latest ) )
                                $this->mLatestID = (int)$row->page_latest;
-                       if ( isset( $row->page_counter ) )
-                               $this->mCounter = (int)$row->page_counter;
                } else { // page not found
                        $this->mArticleID = 0;
                        $this->mLength = 0;
                        $this->mRedirect = false;
                        $this->mLatestID = 0;
-                       $this->mCounter = 0;
                }
        }
 
@@ -764,7 +760,7 @@ class Title {
         * @internal note -- uses hardcoded namespace index instead of constants
         */
        public function canExist() {
-               return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
+               return $this->mNamespace >= NS_MAIN;
        }
 
        /**
@@ -866,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 );
@@ -1222,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 );
                }
@@ -1277,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.
@@ -1285,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 ) {
@@ -1468,6 +1479,7 @@ class Title {
         *
         * @see self::getLocalURL
         * @since 1.18
+        * @return string
         */
        public function escapeCanonicalURL( $query = '', $query2 = false ) {
                wfDeprecated( __METHOD__, '1.19' );
@@ -1843,6 +1855,8 @@ class Title {
         * @return Array list of errors
         */
        private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+               global $wgDeleteRevisionsLimit, $wgLang;
+
                if ( $action == 'protect' ) {
                        if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
                                // If they can't edit, they shouldn't protect.
@@ -1875,6 +1889,12 @@ class Title {
                        } elseif ( !$this->isMovable() ) {
                                $errors[] = array( 'immobile-target-page' );
                        }
+               } elseif ( $action == 'delete' ) {
+                       if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
+                               && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
+                       {
+                               $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
+                       }
                }
                return $errors;
        }
@@ -1907,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
@@ -1927,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 );
@@ -1956,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
@@ -1982,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;
        }
 
@@ -2230,7 +2245,7 @@ class Title {
 
                global $wgUser;
 
-               $imit = array( 'create' => $create_perm );
+               $limit = array( 'create' => $create_perm );
                $expiry = array( 'create' => $expiry );
 
                $page = WikiPage::factory( $this );
@@ -2523,7 +2538,7 @@ class Title {
 
                if ( $oldFashionedRestrictions === null ) {
                        $oldFashionedRestrictions = $dbr->selectField( 'page', 'page_restrictions',
-                               array( 'page_id' => $this->getArticleId() ), __METHOD__ );
+                               array( 'page_id' => $this->getArticleID() ), __METHOD__ );
                }
 
                if ( $oldFashionedRestrictions != '' ) {
@@ -2594,7 +2609,7 @@ class Title {
                                $res = $dbr->select(
                                        'page_restrictions',
                                        '*',
-                                       array( 'pr_page' => $this->getArticleId() ),
+                                       array( 'pr_page' => $this->getArticleID() ),
                                        __METHOD__
                                );
 
@@ -2752,37 +2767,6 @@ class Title {
                return $deleted;
        }
 
-       /**
-        * Get the number of views of this page
-        *
-        * @return int The view count for the page
-        */
-       public function getCount() {
-               if ( $this->mCounter == -1 ) {
-                       if ( $this->exists() ) {
-                               $dbr = wfGetDB( DB_SLAVE );
-                               $this->mCounter = $dbr->selectField( 'page',
-                                       'page_counter',
-                                       array( 'page_id' => $this->getArticleID() ),
-                                       __METHOD__
-                               );
-                       } else {
-                               $this->mCounter = 0;
-                       }
-               }
-
-               return $this->mCounter;
-       }
-
-       /**
-        * Returns a bool to say whether the Article ID for this title has already been loaded
-        *
-        * @return bool
-        */
-       public function isArticleIDLoaded() {
-               return $this->mArticleID != -1;
-       }
-
        /**
         * Get the article ID for this Title from the link cache,
         * adding it if necessary
@@ -2801,8 +2785,10 @@ class Title {
                        $linkCache->clearLink( $this );
                        $this->mArticleID = $linkCache->addLinkObj( $this );
                        $linkCache->forUpdate( $oldUpdate );
-               } else if ( -1 == $this->mArticleID ) {
-                       $this->mArticleID = $linkCache->addLinkObj( $this );
+               } else {
+                       if ( -1 == $this->mArticleID ) {
+                               $this->mArticleID = $linkCache->addLinkObj( $this );
+                       }
                }
                return $this->mArticleID;
        }
@@ -2873,9 +2859,9 @@ class Title {
         * This clears some fields in this object, and clears any associated
         * keys in the "bad links" section of the link cache.
         *
-        * - This is called from Article::doEdit() and Article::insertOn() to allow
+        * - This is called from WikiPage::doEdit() and WikiPage::insertOn() to allow
         * loading of the new page_id. It's also called from
-        * Article::doDeleteArticle()
+        * WikiPage::doDeleteArticle()
         *
         * @param $newid Int the new Article ID
         */
@@ -2893,7 +2879,7 @@ class Title {
                $this->mRedirect = null;
                $this->mLength = -1;
                $this->mLatestID = false;
-               $this->mCounter = -1;
+               $this->mEstimateRevisions = null;
        }
 
        /**
@@ -3123,8 +3109,6 @@ class Title {
         * @return Array of Title objects linking here
         */
        public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
-               $linkCache = LinkCache::singleton();
-
                if ( count( $options ) > 0 ) {
                        $db = wfGetDB( DB_MASTER );
                } else {
@@ -3143,7 +3127,8 @@ class Title {
                );
 
                $retVal = array();
-               if ( $db->numRows( $res ) ) {
+               if ( $res->numRows() ) {
+                       $linkCache = LinkCache::singleton();
                        foreach ( $res as $row ) {
                                $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
                                if ( $titleObj ) {
@@ -3169,6 +3154,76 @@ class Title {
                return $this->getLinksTo( $options, 'templatelinks', 'tl' );
        }
 
+       /**
+        * Get an array of Title objects linked from this Title
+        * Also stores the IDs in the link cache.
+        *
+        * WARNING: do not use this function on arbitrary user-supplied titles!
+        * On heavily-used templates it will max out the memory.
+        *
+        * @param $options Array: may be FOR UPDATE
+        * @param $table String: table name
+        * @param $prefix String: fields prefix
+        * @return Array of Title objects linking here
+        */
+       public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+               $id = $this->getArticleID();
+
+               # If the page doesn't exist; there can't be any link from this page
+               if ( !$id ) {
+                       return array();
+               }
+
+               if ( count( $options ) > 0 ) {
+                       $db = wfGetDB( DB_MASTER );
+               } else {
+                       $db = wfGetDB( DB_SLAVE );
+               }
+
+               $namespaceFiled = "{$prefix}_namespace";
+               $titleField = "{$prefix}_title";
+
+               $res = $db->select(
+                       array( $table, 'page' ),
+                       array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+                       array( "{$prefix}_from" => $id ),
+                       __METHOD__,
+                       $options,
+                       array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
+               );
+
+               $retVal = array();
+               if ( $res->numRows() ) {
+                       $linkCache = LinkCache::singleton();
+                       foreach ( $res as $row ) {
+                               $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
+                               if ( $titleObj ) {
+                                       if ( $row->page_id ) {
+                                               $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
+                                       } else {
+                                               $linkCache->addBadLinkObj( $titleObj );
+                                       }
+                                       $retVal[] = $titleObj;
+                               }
+                       }
+               }
+               return $retVal;
+       }
+
+       /**
+        * Get an array of Title objects used on this Title as a template
+        * Also stores the IDs in the link cache.
+        *
+        * WARNING: do not use this function on arbitrary user-supplied titles!
+        * On heavily-used templates it will max out the memory.
+        *
+        * @param $options Array: may be FOR UPDATE
+        * @return Array of Title the Title objects used here
+        */
+       public function getTemplateLinksFrom( $options = array() ) {
+               return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
+       }
+
        /**
         * Get an array of Title objects referring to non-existent articles linked from this page
         *
@@ -3176,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();
                }
@@ -3186,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(),
@@ -3416,26 +3471,23 @@ class Title {
                                        return $status->getErrorsArray();
                                }
                        }
+                       // Clear RepoGroup process cache
+                       RepoGroup::singleton()->clearCache( $this );
+                       RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
                }
-               // Clear RepoGroup process cache
-               RepoGroup::singleton()->clearCache( $this );
-               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();
-               $pageCountChange = ( $createRedirect ? 1 : 0 ) - ( $nt->exists() ? 1 : 0 );
 
                // Do the actual move
                $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;
                }
 
-               $redirid = $this->getArticleID();
-
                // Refresh the sortkey for this row.  Be careful to avoid resetting
                // cl_timestamp, which may disturb time-based lists on some sites.
                $prefixes = $dbw->select(
@@ -3459,6 +3511,8 @@ class Title {
                        );
                }
 
+               $redirid = $this->getArticleID();
+
                if ( $protected ) {
                        # Protect the redirect title as the title used to be...
                        $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
@@ -3494,49 +3548,7 @@ class Title {
                        WatchedItem::duplicateEntries( $this, $nt );
                }
 
-               # Update search engine
-               $u = new SearchUpdate( $pageid, $nt->getPrefixedDBkey() );
-               $u->doUpdate();
-               $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
-               $u->doUpdate();
-
-               $dbw->commit();
-
-               # Update site_stats
-               if ( $this->isContentPage() && !$nt->isContentPage() ) {
-                       # No longer a content page
-                       # Not viewed, edited, removing
-                       $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
-               } elseif ( !$this->isContentPage() && $nt->isContentPage() ) {
-                       # Now a content page
-                       # Not viewed, edited, adding
-                       $u = new SiteStatsUpdate( 0, 1, + 1, $pageCountChange );
-               } elseif ( $pageCountChange ) {
-                       # Redirect added
-                       $u = new SiteStatsUpdate( 0, 0, 0, 1 );
-               } else {
-                       # Nothing special
-                       $u = false;
-               }
-               if ( $u ) {
-                       $u->doUpdate();
-               }
-
-               # Update message cache for interface messages
-               if ( $this->getNamespace() == NS_MEDIAWIKI ) {
-                       # @bug 17860: old article can be deleted, if this the case,
-                       # delete it from message cache
-                       if ( $this->getArticleID() === 0 ) {
-                               MessageCache::singleton()->replace( $this->getDBkey(), false );
-                       } else {
-                               $rev = Revision::newFromTitle( $this );
-                               MessageCache::singleton()->replace( $this->getDBkey(), $rev->getText() );
-                       }
-               }
-               if ( $nt->getNamespace() == NS_MEDIAWIKI ) {
-                       $rev = Revision::newFromTitle( $nt );
-                       MessageCache::singleton()->replace( $nt->getDBkey(), $rev->getText() );
-               }
+               $dbw->commit( __METHOD__ );
 
                wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
                return true;
@@ -3583,40 +3595,21 @@ class Title {
                $comment = $wgContLang->truncate( $comment, 255 );
 
                $oldid = $this->getArticleID();
-               $latest = $this->getLatestRevID();
 
                $dbw = wfGetDB( DB_MASTER );
 
-               if ( $moveOverRedirect ) {
-                       $rcts = $dbw->timestamp( $nt->getEarliestRevTime() );
+               $newpage = WikiPage::factory( $nt );
 
+               if ( $moveOverRedirect ) {
                        $newid = $nt->getArticleID();
-                       $newns = $nt->getNamespace();
-                       $newdbk = $nt->getDBkey();
 
                        # Delete the old redirect. We don't save it to history since
                        # by definition if we've got here it's rather uninteresting.
                        # We have to remove it so that the next step doesn't trigger
                        # a conflict on the unique namespace+title index...
                        $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
-                       if ( !$dbw->cascadingDeletes() ) {
-                               $dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
-
-                               $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'imagelinks', array( 'il_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'categorylinks', array( 'cl_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'templatelinks', array( 'tl_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'externallinks', array( 'el_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'langlinks', array( 'll_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'iwlinks', array( 'iwl_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'redirect', array( 'rd_from' => $newid ), __METHOD__ );
-                               $dbw->delete( 'page_props', array( 'pp_page' => $newid ), __METHOD__ );
-                       }
-                       // If the target page was recently created, it may have an entry in recentchanges still
-                       $dbw->delete( 'recentchanges',
-                               array( 'rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1 ),
-                               __METHOD__
-                       );
+
+                       $newpage->doDeleteUpdates( $newid );
                }
 
                # Save a null revision in the page's history notifying of the move
@@ -3626,27 +3619,34 @@ class Title {
                }
                $nullRevId = $nullRevision->insertOn( $dbw );
 
-               $now = wfTimestampNow();
                # Change the name of the target page:
                $dbw->update( 'page',
                        /* SET */ array(
-                               'page_touched'   => $dbw->timestamp( $now ),
                                'page_namespace' => $nt->getNamespace(),
                                'page_title'     => $nt->getDBkey(),
-                               'page_latest'    => $nullRevId,
                        ),
                        /* WHERE */ array( 'page_id' => $oldid ),
                        __METHOD__
                );
+
+               $this->resetArticleID( 0 );
                $nt->resetArticleID( $oldid );
 
-               $article = WikiPage::factory( $nt );
+               $newpage->updateRevisionOn( $dbw, $nullRevision );
+
                wfRunHooks( 'NewRevisionFromEditComplete',
-                       array( $article, $nullRevision, $latest, $wgUser ) );
-               $article->setCachedLastEditTime( $now );
+                       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 ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
+               if ( $redirectSuppressed ) {
+                       WikiPage::onArticleDelete( $this );
+               } else {
                        $mwRedir = MagicWord::get( 'redirect' );
                        $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
                        $redirectArticle = WikiPage::factory( $this );
@@ -3662,33 +3662,13 @@ class Title {
                                wfRunHooks( 'NewRevisionFromEditComplete',
                                        array( $redirectArticle, $redirectRevision, false, $wgUser ) );
 
-                               # Now, we record the link from the redirect to the new title.
-                               # It should have no other outgoing links...
-                               $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
-                               $dbw->insert( 'pagelinks',
-                                       array(
-                                               'pl_from'      => $newid,
-                                               'pl_namespace' => $nt->getNamespace(),
-                                               'pl_title'     => $nt->getDBkey() ),
-                                       __METHOD__ );
+                               $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
                        }
-               } else {
-                       $this->resetArticleID( 0 );
                }
 
                # Log the move
                $logid = $logEntry->insert();
                $logEntry->publish( $logid );
-
-               # Purge caches for old and new titles
-               if ( $moveOverRedirect ) {
-                       # A simple purge is enough when moving over a redirect
-                       $nt->purgeSquid();
-               } else {
-                       # Purge caches as per article creation, including any pages that link to this title
-                       Article::onArticleCreate( $nt );
-               }
-               $this->purgeSquid();
        }
 
        /**
@@ -3734,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
@@ -3859,7 +3839,7 @@ class Title {
 
                $data = array();
 
-               $titleKey = $this->getArticleId();
+               $titleKey = $this->getArticleID();
 
                if ( $titleKey === 0 ) {
                        return $data;
@@ -3937,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__,
@@ -3956,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__,
@@ -3971,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', '*',
@@ -4007,6 +3987,41 @@ class Title {
                return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
        }
 
+       /**
+        * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+        *
+        * @return bool
+        */
+       public function isBigDeletion() {
+               global $wgDeleteRevisionsLimit;
+
+               if ( !$wgDeleteRevisionsLimit ) {
+                       return false;
+               }
+
+               $revCount = $this->estimateRevisionCount();
+               return $revCount > $wgDeleteRevisionsLimit;
+       }
+
+       /**
+        * Get the  approximate revision count of this page.
+        *
+        * @return int
+        */
+       public function estimateRevisionCount() {
+               if ( !$this->exists() ) {
+                       return 0;
+               }
+
+               if ( $this->mEstimateRevisions === null ) {
+                       $dbr = wfGetDB( DB_SLAVE );
+                       $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
+                               array( 'rev_page' => $this->getArticleID() ), __METHOD__ );
+               }
+
+               return $this->mEstimateRevisions;
+       }
+
        /**
         * Get the number of revisions between the given revision.
         * Used for diffs and other things that really need it.
@@ -4028,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() ) )
                        ),
@@ -4102,7 +4117,7 @@ class Title {
         * @return Bool
         */
        public function exists() {
-               return $this->getArticleId() != 0;
+               return $this->getArticleID() != 0;
        }
 
        /**
@@ -4122,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:
@@ -4149,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
         */
@@ -4441,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 ) {