Rename domain => localDomain in LBFactory for consistency
[lhc/web/wiklou.git] / includes / specials / SpecialUndelete.php
index f488d3c..91753a9 100644 (file)
@@ -63,7 +63,7 @@ class PageArchive {
         * @return ResultWrapper
         */
        public static function listAllPages() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                return self::listPages( $dbr, '' );
        }
@@ -77,7 +77,7 @@ class PageArchive {
         * @return ResultWrapper
         */
        public static function listPagesByPrefix( $prefix ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                $title = Title::newFromText( $prefix );
                if ( $title ) {
@@ -89,10 +89,10 @@ class PageArchive {
                        $ns = 0;
                }
 
-               $conds = array(
+               $conds = [
                        'ar_namespace' => $ns,
                        'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
-               );
+               ];
 
                return self::listPages( $dbr, $conds );
        }
@@ -104,19 +104,19 @@ class PageArchive {
         */
        protected static function listPages( $dbr, $condition ) {
                return $dbr->select(
-                       array( 'archive' ),
-                       array(
+                       [ 'archive' ],
+                       [
                                'ar_namespace',
                                'ar_title',
                                'count' => 'COUNT(*)'
-                       ),
+                       ],
                        $condition,
                        __METHOD__,
-                       array(
-                               'GROUP BY' => array( 'ar_namespace', 'ar_title' ),
-                               'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
+                       [
+                               'GROUP BY' => [ 'ar_namespace', 'ar_title' ],
+                               'ORDER BY' => [ 'ar_namespace', 'ar_title' ],
                                'LIMIT' => 100,
-                       )
+                       ]
                );
        }
 
@@ -127,26 +127,26 @@ class PageArchive {
         * @return ResultWrapper
         */
        function listRevisions() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
-               $tables = array( 'archive' );
+               $tables = [ 'archive' ];
 
-               $fields = array(
+               $fields = [
                        'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
                        'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
-               );
+               ];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
                        $fields[] = 'ar_content_model';
                }
 
-               $conds = array( 'ar_namespace' => $this->title->getNamespace(),
-                       'ar_title' => $this->title->getDBkey() );
+               $conds = [ 'ar_namespace' => $this->title->getNamespace(),
+                       'ar_title' => $this->title->getDBkey() ];
 
-               $options = array( 'ORDER BY' => 'ar_timestamp DESC' );
+               $options = [ 'ORDER BY' => 'ar_timestamp DESC' ];
 
-               $join_conds = array();
+               $join_conds = [];
 
                ChangeTags::modifyDisplayQuery(
                        $tables,
@@ -179,13 +179,13 @@ class PageArchive {
                        return null;
                }
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                return $dbr->select(
                        'filearchive',
                        ArchivedFile::selectFields(),
-                       array( 'fa_name' => $this->title->getDBkey() ),
+                       [ 'fa_name' => $this->title->getDBkey() ],
                        __METHOD__,
-                       array( 'ORDER BY' => 'fa_timestamp DESC' )
+                       [ 'ORDER BY' => 'fa_timestamp DESC' ]
                );
        }
 
@@ -197,9 +197,9 @@ class PageArchive {
         * @return Revision|null
         */
        function getRevision( $timestamp ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
-               $fields = array(
+               $fields = [
                        'ar_rev_id',
                        'ar_text',
                        'ar_comment',
@@ -212,7 +212,7 @@ class PageArchive {
                        'ar_deleted',
                        'ar_len',
                        'ar_sha1',
-               );
+               ];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
@@ -221,13 +221,13 @@ class PageArchive {
 
                $row = $dbr->selectRow( 'archive',
                        $fields,
-                       array( 'ar_namespace' => $this->title->getNamespace(),
+                       [ 'ar_namespace' => $this->title->getNamespace(),
                                'ar_title' => $this->title->getDBkey(),
-                               'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
+                               'ar_timestamp' => $dbr->timestamp( $timestamp ) ],
                        __METHOD__ );
 
                if ( $row ) {
-                       return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
+                       return Revision::newFromArchiveRow( $row, [ 'title' => $this->title ] );
                }
 
                return null;
@@ -244,33 +244,33 @@ class PageArchive {
         * @return Revision|null Null when there is no previous revision
         */
        function getPreviousRevision( $timestamp ) {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
 
                // Check the previous deleted revision...
                $row = $dbr->selectRow( 'archive',
                        'ar_timestamp',
-                       array( 'ar_namespace' => $this->title->getNamespace(),
+                       [ 'ar_namespace' => $this->title->getNamespace(),
                                'ar_title' => $this->title->getDBkey(),
                                'ar_timestamp < ' .
-                                       $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
+                                       $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
                        __METHOD__,
-                       array(
+                       [
                                'ORDER BY' => 'ar_timestamp DESC',
-                               'LIMIT' => 1 ) );
+                               'LIMIT' => 1 ] );
                $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
 
-               $row = $dbr->selectRow( array( 'page', 'revision' ),
-                       array( 'rev_id', 'rev_timestamp' ),
-                       array(
+               $row = $dbr->selectRow( [ 'page', 'revision' ],
+                       [ 'rev_id', 'rev_timestamp' ],
+                       [
                                'page_namespace' => $this->title->getNamespace(),
                                'page_title' => $this->title->getDBkey(),
                                'page_id = rev_page',
                                'rev_timestamp < ' .
-                                       $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
+                                       $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ],
                        __METHOD__,
-                       array(
+                       [
                                'ORDER BY' => 'rev_timestamp DESC',
-                               'LIMIT' => 1 ) );
+                               'LIMIT' => 1 ] );
                $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
                $prevLiveId = $row ? intval( $row->rev_id ) : null;
 
@@ -300,10 +300,10 @@ class PageArchive {
                }
 
                // New-style: keyed to the text storage backend.
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $text = $dbr->selectRow( 'text',
-                       array( 'old_text', 'old_flags' ),
-                       array( 'old_id' => $row->ar_text_id ),
+                       [ 'old_text', 'old_flags' ],
+                       [ 'old_id' => $row->ar_text_id ],
                        __METHOD__ );
 
                return Revision::getRevisionText( $text );
@@ -318,13 +318,13 @@ class PageArchive {
         * @return string|null
         */
        function getLastRevisionText() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $row = $dbr->selectRow( 'archive',
-                       array( 'ar_text', 'ar_flags', 'ar_text_id' ),
-                       array( 'ar_namespace' => $this->title->getNamespace(),
-                               'ar_title' => $this->title->getDBkey() ),
+                       [ 'ar_text', 'ar_flags', 'ar_text_id' ],
+                       [ 'ar_namespace' => $this->title->getNamespace(),
+                               'ar_title' => $this->title->getDBkey() ],
                        __METHOD__,
-                       array( 'ORDER BY' => 'ar_timestamp DESC' ) );
+                       [ 'ORDER BY' => 'ar_timestamp DESC' ] );
 
                if ( $row ) {
                        return $this->getTextFromRow( $row );
@@ -339,10 +339,10 @@ class PageArchive {
         * @return bool
         */
        function isDeleted() {
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_REPLICA );
                $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
-                       array( 'ar_namespace' => $this->title->getNamespace(),
-                               'ar_title' => $this->title->getDBkey() ),
+                       [ 'ar_namespace' => $this->title->getNamespace(),
+                               'ar_title' => $this->title->getDBkey() ],
                        __METHOD__
                );
 
@@ -354,17 +354,22 @@ class PageArchive {
         * Once restored, the items will be removed from the archive tables.
         * The deletion log will be updated with an undeletion notice.
         *
+        * This also sets Status objects, $this->fileStatus and $this->revisionStatus
+        * (depending what operations are attempted).
+        *
         * @param array $timestamps Pass an empty array to restore all revisions,
         *   otherwise list the ones to undelete.
         * @param string $comment
         * @param array $fileVersions
         * @param bool $unsuppress
         * @param User $user User performing the action, or null to use $wgUser
+        * @param string|string[] $tags Change tags to add to log entry
+        *   ($user should be able to add the specified tags before this is called)
         * @return array(number of file revisions restored, number of image revisions
         *   restored, log message) on success, false on failure.
         */
-       function undelete( $timestamps, $comment = '', $fileVersions = array(),
-               $unsuppress = false, User $user = null
+       function undelete( $timestamps, $comment = '', $fileVersions = [],
+               $unsuppress = false, User $user = null, $tags = null
        ) {
                // If both the set of text revisions and file revisions are empty,
                // restore everything. Otherwise, just restore the requested items.
@@ -426,19 +431,19 @@ class PageArchive {
                $logEntry->setPerformer( $user );
                $logEntry->setTarget( $this->title );
                $logEntry->setComment( $reason );
+               $logEntry->setTags( $tags );
 
-               Hooks::run( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) );
+               Hooks::run( 'ArticleUndeleteLogEntry', [ $this, &$logEntry, $user ] );
 
                $logid = $logEntry->insert();
                $logEntry->publish( $logid );
 
-               return array( $textRestored, $filesRestored, $reason );
+               return [ $textRestored, $filesRestored, $reason ];
        }
 
        /**
-        * This is the meaty bit -- restores archived revisions of the given page
-        * to the cur/old tables. If the page currently exists, all revisions will
-        * be stuffed into old, otherwise the most recent will go into cur.
+        * This is the meaty bit -- It restores archived revisions of the given page
+        * to the revision table.
         *
         * @param array $timestamps Pass an empty array to restore all revisions,
         *   otherwise list the ones to undelete.
@@ -452,8 +457,10 @@ class PageArchive {
                        throw new ReadOnlyError();
                }
 
-               $restoreAll = empty( $timestamps );
                $dbw = wfGetDB( DB_MASTER );
+               $dbw->startAtomic( __METHOD__ );
+
+               $restoreAll = empty( $timestamps );
 
                # Does this page already exist? We'll have to update it...
                $article = WikiPage::factory( $this->title );
@@ -462,11 +469,11 @@ class PageArchive {
                $oldcountable = $article->isCountable();
 
                $page = $dbw->selectRow( 'page',
-                       array( 'page_id', 'page_latest' ),
-                       array( 'page_namespace' => $this->title->getNamespace(),
-                               'page_title' => $this->title->getDBkey() ),
+                       [ 'page_id', 'page_latest' ],
+                       [ 'page_namespace' => $this->title->getNamespace(),
+                               'page_title' => $this->title->getDBkey() ],
                        __METHOD__,
-                       array( 'FOR UPDATE' ) // lock page
+                       [ 'FOR UPDATE' ] // lock page
                );
 
                if ( $page ) {
@@ -474,11 +481,9 @@ class PageArchive {
                        # Page already exists. Import the history, and if necessary
                        # we'll update the latest revision field in the record.
 
-                       $previousRevId = $page->page_latest;
-
                        # Get the time span of this page
                        $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
-                               array( 'rev_id' => $previousRevId ),
+                               [ 'rev_id' => $page->page_latest ],
                                __METHOD__ );
 
                        if ( $previousTimestamp === false ) {
@@ -486,26 +491,28 @@ class PageArchive {
 
                                $status = Status::newGood( 0 );
                                $status->warning( 'undeleterevision-missing' );
+                               $dbw->endAtomic( __METHOD__ );
 
                                return $status;
                        }
                } else {
                        # Have to create a new article...
                        $makepage = true;
-                       $previousRevId = 0;
                        $previousTimestamp = 0;
                }
 
-               $oldWhere = array(
+               $oldWhere = [
                        'ar_namespace' => $this->title->getNamespace(),
                        'ar_title' => $this->title->getDBkey(),
-               );
+               ];
                if ( !$restoreAll ) {
-                       $oldWhere['ar_timestamp'] = array_map( array( &$dbw, 'timestamp' ), $timestamps );
+                       $oldWhere['ar_timestamp'] = array_map( [ &$dbw, 'timestamp' ], $timestamps );
                }
 
-               $fields = array(
+               $fields = [
+                       'ar_id',
                        'ar_rev_id',
+                       'rev_id',
                        'ar_text',
                        'ar_comment',
                        'ar_user',
@@ -518,7 +525,7 @@ class PageArchive {
                        'ar_page_id',
                        'ar_len',
                        'ar_sha1'
-               );
+               ];
 
                if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
                        $fields[] = 'ar_content_format';
@@ -528,11 +535,14 @@ class PageArchive {
                /**
                 * Select each archived revision...
                 */
-               $result = $dbw->select( 'archive',
+               $result = $dbw->select(
+                       [ 'archive', 'revision' ],
                        $fields,
                        $oldWhere,
                        __METHOD__,
-                       /* options */ array( 'ORDER BY' => 'ar_timestamp' )
+                       /* options */
+                       [ 'ORDER BY' => 'ar_timestamp' ],
+                       [ 'revision' => [ 'LEFT JOIN', 'ar_rev_id=rev_id' ] ]
                );
 
                $rev_count = $result->numRows();
@@ -541,116 +551,182 @@ class PageArchive {
 
                        $status = Status::newGood( 0 );
                        $status->warning( "undelete-no-results" );
+                       $dbw->endAtomic( __METHOD__ );
 
                        return $status;
                }
 
-               $result->seek( $rev_count - 1 ); // move to last
-               $row = $result->fetchObject(); // get newest archived rev
-               $oldPageId = (int)$row->ar_page_id; // pass this to ArticleUndelete hook
-               $result->seek( 0 ); // move back
+               // We use ar_id because there can be duplicate ar_rev_id even for the same
+               // page.  In this case, we may be able to restore the first one.
+               $restoreFailedArIds = [];
 
-               // grab the content to check consistency with global state before restoring the page.
-               $revision = Revision::newFromArchiveRow( $row,
-                       array(
-                               'title' => $article->getTitle(), // used to derive default content model
-                       )
-               );
-               $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
-               $content = $revision->getContent( Revision::RAW );
+               // Map rev_id to the ar_id that is allowed to use it.  When checking later,
+               // if it doesn't match, the current ar_id can not be restored.
 
-               // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
-               $status = $content->prepareSave( $article, 0, -1, $user );
+               // Value can be an ar_id or -1 (-1 means no ar_id can use it, since the
+               // rev_id is taken before we even start the restore).
+               $allowedRevIdToArIdMap = [];
 
-               if ( !$status->isOK() ) {
-                       return $status;
-               }
+               $latestRestorableRow = null;
 
-               if ( $makepage ) {
-                       // Check the state of the newest to-be version...
-                       if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
-                               return Status::newFatal( "undeleterevdel" );
+               foreach ( $result as $row ) {
+                       if ( $row->ar_rev_id ) {
+                               // rev_id is taken even before we start restoring.
+                               if ( $row->ar_rev_id === $row->rev_id ) {
+                                       $restoreFailedArIds[] = $row->ar_id;
+                                       $allowedRevIdToArIdMap[$row->ar_rev_id] = -1;
+                               } else {
+                                       // rev_id is not taken yet in the DB, but it might be taken
+                                       // by a prior revision in the same restore operation. If
+                                       // not, we need to reserve it.
+                                       if ( isset( $allowedRevIdToArIdMap[$row->ar_rev_id] ) ) {
+                                               $restoreFailedArIds[] = $row->ar_id;
+                                       } else {
+                                               $allowedRevIdToArIdMap[$row->ar_rev_id] = $row->ar_id;
+                                               $latestRestorableRow = $row;
+                                       }
+                               }
+                       } else {
+                               // If ar_rev_id is null, there can't be a collision, and a
+                               // rev_id will be chosen automatically.
+                               $latestRestorableRow = $row;
                        }
-                       // Safe to insert now...
-                       $newid = $article->insertOn( $dbw, $row->ar_page_id );
-                       if ( $newid === false ) {
-                               // The old ID is reserved; let's pick another
-                               $newid = $article->insertOn( $dbw );
+               }
+
+               $result->seek( 0 ); // move back
+
+               $oldPageId = 0;
+               if ( $latestRestorableRow !== null ) {
+                       $oldPageId = (int)$latestRestorableRow->ar_page_id; // pass this to ArticleUndelete hook
+
+                       // grab the content to check consistency with global state before restoring the page.
+                       $revision = Revision::newFromArchiveRow( $latestRestorableRow,
+                               [
+                                       'title' => $article->getTitle(), // used to derive default content model
+                               ]
+                       );
+                       $user = User::newFromName( $revision->getUserText( Revision::RAW ), false );
+                       $content = $revision->getContent( Revision::RAW );
+
+                       // NOTE: article ID may not be known yet. prepareSave() should not modify the database.
+                       $status = $content->prepareSave( $article, 0, -1, $user );
+                       if ( !$status->isOK() ) {
+                               $dbw->endAtomic( __METHOD__ );
+
+                               return $status;
                        }
-                       $pageId = $newid;
+               }
+
+               $newid = false; // newly created page ID
+               $restored = 0; // number of revisions restored
+               /** @var Revision $revision */
+               $revision = null;
+
+               // If there are no restorable revisions, we can skip most of the steps.
+               if ( $latestRestorableRow === null ) {
+                       $failedRevisionCount = $rev_count;
                } else {
-                       // Check if a deleted revision will become the current revision...
-                       if ( $row->ar_timestamp > $previousTimestamp ) {
+                       if ( $makepage ) {
                                // Check the state of the newest to-be version...
-                               if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
+                               if ( !$unsuppress
+                                       && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
+                               ) {
+                                       $dbw->endAtomic( __METHOD__ );
+
                                        return Status::newFatal( "undeleterevdel" );
                                }
-                       }
-
-                       $newid = false;
-                       $pageId = $article->getId();
-               }
+                               // Safe to insert now...
+                               $newid = $article->insertOn( $dbw, $latestRestorableRow->ar_page_id );
+                               if ( $newid === false ) {
+                                       // The old ID is reserved; let's pick another
+                                       $newid = $article->insertOn( $dbw );
+                               }
+                               $pageId = $newid;
+                       } else {
+                               // Check if a deleted revision will become the current revision...
+                               if ( $latestRestorableRow->ar_timestamp > $previousTimestamp ) {
+                                       // Check the state of the newest to-be version...
+                                       if ( !$unsuppress
+                                               && ( $latestRestorableRow->ar_deleted & Revision::DELETED_TEXT )
+                                       ) {
+                                               $dbw->endAtomic( __METHOD__ );
+
+                                               return Status::newFatal( "undeleterevdel" );
+                                       }
+                               }
 
-               $revision = null;
-               $restored = 0;
+                               $newid = false;
+                               $pageId = $article->getId();
+                       }
 
-               foreach ( $result as $row ) {
-                       // Check for key dupes due to needed archive integrity.
-                       if ( $row->ar_rev_id ) {
-                               $exists = $dbw->selectField( 'revision', '1',
-                                       array( 'rev_id' => $row->ar_rev_id ), __METHOD__ );
-                               if ( $exists ) {
-                                       continue; // don't throw DB errors
+                       foreach ( $result as $row ) {
+                               // Check for key dupes due to needed archive integrity.
+                               if ( $row->ar_rev_id && $allowedRevIdToArIdMap[$row->ar_rev_id] !== $row->ar_id ) {
+                                       continue;
                                }
+                               // Insert one revision at a time...maintaining deletion status
+                               // unless we are specifically removing all restrictions...
+                               $revision = Revision::newFromArchiveRow( $row,
+                                       [
+                                               'page' => $pageId,
+                                               'title' => $this->title,
+                                               'deleted' => $unsuppress ? 0 : $row->ar_deleted
+                                       ] );
+
+                               $revision->insertOn( $dbw );
+                               $restored++;
+
+                               Hooks::run( 'ArticleRevisionUndeleted',
+                                       [ &$this->title, $revision, $row->ar_page_id ] );
                        }
-                       // Insert one revision at a time...maintaining deletion status
-                       // unless we are specifically removing all restrictions...
-                       $revision = Revision::newFromArchiveRow( $row,
-                               array(
-                                       'page' => $pageId,
-                                       'title' => $this->title,
-                                       'deleted' => $unsuppress ? 0 : $row->ar_deleted
-                               ) );
-
-                       $revision->insertOn( $dbw );
-                       $restored++;
-
-                       Hooks::run( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
-               }
-               # Now that it's safely stored, take it out of the archive
-               $dbw->delete( 'archive',
-                       $oldWhere,
-                       __METHOD__ );
 
-               // Was anything restored at all?
-               if ( $restored == 0 ) {
-                       return Status::newGood( 0 );
-               }
-
-               $created = (bool)$newid;
-
-               // Attach the latest revision to the page...
-               $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
-               if ( $created || $wasnew ) {
-                       // Update site stats, link tables, etc
-                       $article->doEditUpdates(
-                               $revision,
-                               User::newFromName( $revision->getUserText( Revision::RAW ), false ),
-                               array(
-                                       'created' => $created,
-                                       'oldcountable' => $oldcountable,
-                                       'restored' => true
-                               )
-                       );
+                       // Now that it's safely stored, take it out of the archive
+                       // Don't delete rows that we failed to restore
+                       $toDeleteConds = $oldWhere;
+                       $failedRevisionCount = count( $restoreFailedArIds );
+                       if ( $failedRevisionCount > 0 ) {
+                               $toDeleteConds[] = 'ar_id NOT IN ( ' . $dbw->makeList( $restoreFailedArIds ) . ' )';
+                       }
+
+                       $dbw->delete( 'archive',
+                               $toDeleteConds,
+                               __METHOD__ );
                }
 
-               Hooks::run( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
+               $status = Status::newGood( $restored );
 
-               if ( $this->title->getNamespace() == NS_FILE ) {
-                       DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->title, 'imagelinks' ) );
+               if ( $failedRevisionCount > 0 ) {
+                       $status->warning(
+                               wfMessage( 'undeleterevision-duplicate-revid', $failedRevisionCount ) );
                }
 
-               return Status::newGood( $restored );
+               // Was anything restored at all?
+               if ( $restored ) {
+                       $created = (bool)$newid;
+                       // Attach the latest revision to the page...
+                       $wasnew = $article->updateIfNewerOn( $dbw, $revision );
+                       if ( $created || $wasnew ) {
+                               // Update site stats, link tables, etc
+                               $article->doEditUpdates(
+                                       $revision,
+                                       User::newFromName( $revision->getUserText( Revision::RAW ), false ),
+                                       [
+                                               'created' => $created,
+                                               'oldcountable' => $oldcountable,
+                                               'restored' => true
+                                       ]
+                               );
+                       }
+
+                       Hooks::run( 'ArticleUndelete', [ &$this->title, $created, $comment, $oldPageId ] );
+                       if ( $this->title->getNamespace() == NS_FILE ) {
+                               DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->title, 'imagelinks' ) );
+                       }
+               }
+
+               $dbw->endAtomic( __METHOD__ );
+
+               return $status;
        }
 
        /**
@@ -695,6 +771,10 @@ class SpecialUndelete extends SpecialPage {
                parent::__construct( 'Undelete', 'deletedhistory' );
        }
 
+       public function doesWrites() {
+               return true;
+       }
+
        function loadRequest( $par ) {
                $request = $this->getRequest();
                $user = $this->getUser();
@@ -744,10 +824,10 @@ class SpecialUndelete extends SpecialPage {
                }
 
                if ( $this->mRestore || $this->mInvert ) {
-                       $timestamps = array();
-                       $this->mFileVersions = array();
+                       $timestamps = [];
+                       $this->mFileVersions = [];
                        foreach ( $request->getValues() as $key => $val ) {
-                               $matches = array();
+                               $matches = [];
                                if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
                                        array_push( $timestamps, $matches[1] );
                                }
@@ -852,20 +932,20 @@ class SpecialUndelete extends SpecialPage {
        private function redirectToRevDel() {
                $archive = new PageArchive( $this->mTargetObj );
 
-               $revisions = array();
+               $revisions = [];
 
                foreach ( $this->getRequest()->getValues() as $key => $val ) {
-                       $matches = array();
+                       $matches = [];
                        if ( preg_match( "/^ts(\d{14})$/", $key, $matches ) ) {
                                $revisions[ $archive->getRevision( $matches[1] )->getId() ] = 1;
                        }
                }
-               $query = array(
+               $query = [
                        "type" => "revision",
                        "ids" => $revisions,
-                       "target" => wfUrlencode( $this->mTargetObj->getPrefixedText() )
-               );
-               $url = SpecialPage::getTitleFor( "RevisionDelete" )->getFullURL( $query );
+                       "target" => $this->mTargetObj->getPrefixedText()
+               ];
+               $url = SpecialPage::getTitleFor( 'Revisiondelete' )->getFullURL( $query );
                $this->getOutput()->redirect( $url );
        }
 
@@ -873,19 +953,19 @@ class SpecialUndelete extends SpecialPage {
                $out = $this->getOutput();
                $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
                $out->addHTML(
-                       Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
+                       Xml::openElement( 'form', [ 'method' => 'get', 'action' => wfScript() ] ) .
                                Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
                                Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
                                Html::rawElement(
                                        'label',
-                                       array( 'for' => 'prefix' ),
+                                       [ 'for' => 'prefix' ],
                                        $this->msg( 'undelete-search-prefix' )->parse()
                                ) .
                                Xml::input(
                                        'prefix',
                                        20,
                                        $this->mSearchPrefix,
-                                       array( 'id' => 'prefix', 'autofocus' => '' )
+                                       [ 'id' => 'prefix', 'autofocus' => '' ]
                                ) . ' ' .
                                Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
                                Xml::closeElement( 'fieldset' ) .
@@ -924,14 +1004,14 @@ class SpecialUndelete extends SpecialPage {
                                $item = Linker::linkKnown(
                                        $undelete,
                                        htmlspecialchars( $title->getPrefixedText() ),
-                                       array(),
-                                       array( 'target' => $title->getPrefixedText() )
+                                       [],
+                                       [ 'target' => $title->getPrefixedText() ]
                                );
                        } else {
                                // The title is no longer valid, show as text
                                $item = Html::element(
                                        'span',
-                                       array( 'class' => 'mw-invalidtitle' ),
+                                       [ 'class' => 'mw-invalidtitle' ],
                                        Linker::getInvalidTitleDescription(
                                                $this->getContext(),
                                                $row->ar_namespace,
@@ -954,7 +1034,7 @@ class SpecialUndelete extends SpecialPage {
                }
 
                $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
-               if ( !Hooks::run( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
+               if ( !Hooks::run( 'UndeleteForm::showRevision', [ &$archive, $this->mTargetObj ] ) ) {
                        return;
                }
                $rev = $archive->getRevision( $timestamp );
@@ -1038,7 +1118,7 @@ class SpecialUndelete extends SpecialPage {
                $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
                        $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
 
-               if ( !Hooks::run( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
+               if ( !Hooks::run( 'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) {
                        return;
                }
 
@@ -1057,48 +1137,48 @@ class SpecialUndelete extends SpecialPage {
                        // source view for textual content
                        $sourceView = Xml::element(
                                'textarea',
-                               array(
+                               [
                                        'readonly' => 'readonly',
                                        'cols' => $user->getIntOption( 'cols' ),
                                        'rows' => $user->getIntOption( 'rows' )
-                               ),
+                               ],
                                $content->getNativeData() . "\n"
                        );
 
-                       $previewButton = Xml::element( 'input', array(
+                       $previewButton = Xml::element( 'input', [
                                'type' => 'submit',
                                'name' => 'preview',
                                'value' => $this->msg( 'showpreview' )->text()
-                       ) );
+                       ] );
                } else {
                        $sourceView = '';
                        $previewButton = '';
                }
 
-               $diffButton = Xml::element( 'input', array(
+               $diffButton = Xml::element( 'input', [
                        'name' => 'diff',
                        'type' => 'submit',
-                       'value' => $this->msg( 'showdiff' )->text() ) );
+                       'value' => $this->msg( 'showdiff' )->text() ] );
 
                $out->addHTML(
                        $sourceView .
-                               Xml::openElement( 'div', array(
-                                       'style' => 'clear: both' ) ) .
-                               Xml::openElement( 'form', array(
+                               Xml::openElement( 'div', [
+                                       'style' => 'clear: both' ] ) .
+                               Xml::openElement( 'form', [
                                        'method' => 'post',
-                                       'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
-                               Xml::element( 'input', array(
+                                       'action' => $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] ) ] ) .
+                               Xml::element( 'input', [
                                        'type' => 'hidden',
                                        'name' => 'target',
-                                       'value' => $this->mTargetObj->getPrefixedDBkey() ) ) .
-                               Xml::element( 'input', array(
+                                       'value' => $this->mTargetObj->getPrefixedDBkey() ] ) .
+                               Xml::element( 'input', [
                                        'type' => 'hidden',
                                        'name' => 'timestamp',
-                                       'value' => $timestamp ) ) .
-                               Xml::element( 'input', array(
+                                       'value' => $timestamp ] ) .
+                               Xml::element( 'input', [
                                        'type' => 'hidden',
                                        'name' => 'wpEditToken',
-                                       'value' => $user->getEditToken() ) ) .
+                                       'value' => $user->getEditToken() ] ) .
                                $previewButton .
                                $diffButton .
                                Xml::closeElement( 'form' ) .
@@ -1146,14 +1226,14 @@ class SpecialUndelete extends SpecialPage {
                if ( $isDeleted ) {
                        /// @todo FIXME: $rev->getTitle() is null for deleted revs...?
                        $targetPage = $this->getPageTitle();
-                       $targetQuery = array(
+                       $targetQuery = [
                                'target' => $this->mTargetObj->getPrefixedText(),
                                'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
-                       );
+                       ];
                } else {
                        /// @todo FIXME: getId() may return non-zero for deleted revs...
                        $targetPage = $rev->getTitle();
-                       $targetQuery = array( 'oldid' => $rev->getId() );
+                       $targetQuery = [ 'oldid' => $rev->getId() ];
                }
 
                // Add show/hide deletion links if available
@@ -1167,10 +1247,10 @@ class SpecialUndelete extends SpecialPage {
 
                $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
 
-               $tags = wfGetDB( DB_SLAVE )->selectField(
+               $tags = wfGetDB( DB_REPLICA )->selectField(
                        'tag_summary',
                        'ts_tags',
-                       array( 'ts_rev_id' => $rev->getId() ),
+                       [ 'ts_rev_id' => $rev->getId() ],
                        __METHOD__
                );
                $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff', $this->getContext() );
@@ -1186,7 +1266,7 @@ class SpecialUndelete extends SpecialPage {
                                        $lang->userDate( $rev->getTimestamp(), $user ),
                                        $lang->userTime( $rev->getTimestamp(), $user )
                                )->escaped(),
-                               array(),
+                               [],
                                $targetQuery
                        ) .
                        '</strong></div>' .
@@ -1215,14 +1295,14 @@ class SpecialUndelete extends SpecialPage {
                        $lang->userDate( $file->getTimestamp(), $user ),
                        $lang->userTime( $file->getTimestamp(), $user ) );
                $out->addHTML(
-                       Xml::openElement( 'form', array(
+                       Xml::openElement( 'form', [
                                        'method' => 'POST',
-                                       'action' => $this->getPageTitle()->getLocalURL( array(
+                                       'action' => $this->getPageTitle()->getLocalURL( [
                                                'target' => $this->mTarget,
                                                'file' => $key,
                                                'token' => $user->getEditToken( $key ),
-                                       ) ),
-                               )
+                                       ] ),
+                               ]
                        ) .
                                Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
                                '</form>'
@@ -1259,11 +1339,11 @@ class SpecialUndelete extends SpecialPage {
                }
                $out->wrapWikiMsg(
                        "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
-                       array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) )
+                       [ 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ]
                );
 
                $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
-               Hooks::run( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
+               Hooks::run( 'UndeleteForm::showHistory', [ &$archive, $this->mTargetObj ] );
                /*
                $text = $archive->getLastRevisionText();
                if( is_null( $text ) ) {
@@ -1308,11 +1388,11 @@ class SpecialUndelete extends SpecialPage {
                }
 
                if ( $this->mAllowed ) {
-                       $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
+                       $action = $this->getPageTitle()->getLocalURL( [ 'action' => 'submit' ] );
                        # Start the form here
                        $top = Xml::openElement(
                                'form',
-                               array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' )
+                               [ 'method' => 'post', 'action' => $action, 'id' => 'undelete' ]
                        );
                        $out->addHTML( $top );
                }
@@ -1345,7 +1425,7 @@ class SpecialUndelete extends SpecialPage {
                        }
 
                        $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
-                               Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
+                               Xml::openElement( 'table', [ 'id' => 'mw-undelete-table' ] ) .
                                "<tr>
                                        <td colspan='2' class='mw-undelete-extrahelp'>" .
                                $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
@@ -1360,7 +1440,7 @@ class SpecialUndelete extends SpecialPage {
                                        'wpComment',
                                        50,
                                        $this->mComment,
-                                       array( 'id' => 'wpComment', 'autofocus' => '' )
+                                       [ 'id' => 'wpComment', 'autofocus' => '' ]
                                ) .
                                "</td>
                        </tr>
@@ -1369,11 +1449,11 @@ class SpecialUndelete extends SpecialPage {
                                <td class='mw-submit'>" .
                                Xml::submitButton(
                                        $this->msg( 'undeletebtn' )->text(),
-                                       array( 'name' => 'restore', 'id' => 'mw-undelete-submit' )
+                                       [ 'name' => 'restore', 'id' => 'mw-undelete-submit' ]
                                ) . ' ' .
                                Xml::submitButton(
                                        $this->msg( 'undeleteinvert' )->text(),
-                                       array( 'name' => 'invert', 'id' => 'mw-undelete-invert' )
+                                       [ 'name' => 'invert', 'id' => 'mw-undelete-invert' ]
                                ) .
                                "</td>
                        </tr>" .
@@ -1392,11 +1472,11 @@ class SpecialUndelete extends SpecialPage {
                        if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
                                $out->addHTML( Html::element(
                                        'button',
-                                       array(
+                                       [
                                                'name' => 'revdel',
                                                'type' => 'submit',
                                                'class' => 'deleterevision-log-submit mw-log-deleterevision-button'
-                                       ),
+                                       ],
                                        $this->msg( 'showhideselectedversions' )->text()
                                ) . "\n" );
                        }
@@ -1438,9 +1518,9 @@ class SpecialUndelete extends SpecialPage {
 
        protected function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
                $rev = Revision::newFromArchiveRow( $row,
-                       array(
+                       [
                                'title' => $this->mTargetObj
-                       ) );
+                       ] );
 
                $revTextSize = '';
                $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
@@ -1472,12 +1552,12 @@ class SpecialUndelete extends SpecialPage {
                                $last = Linker::linkKnown(
                                        $titleObj,
                                        $this->msg( 'diff' )->escaped(),
-                                       array(),
-                                       array(
+                                       [],
+                                       [
                                                'target' => $this->mTargetObj->getPrefixedText(),
                                                'timestamp' => $ts,
                                                'diff' => 'prev'
-                                       )
+                                       ]
                                );
                        } else {
                                $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
@@ -1504,7 +1584,7 @@ class SpecialUndelete extends SpecialPage {
                $comment = Linker::revComment( $rev );
 
                // Tags
-               $attribs = array();
+               $attribs = [];
                list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
                        $row->ts_tags,
                        'deletedhistory',
@@ -1560,11 +1640,11 @@ class SpecialUndelete extends SpecialPage {
                                // Revision was hidden from sysops
                                $revdlink = Linker::revDeleteLinkDisabled( $canHide );
                        } else {
-                               $query = array(
+                               $query = [
                                        'type' => 'filearchive',
                                        'target' => $this->mTargetObj->getPrefixedDBkey(),
                                        'ids' => $row->fa_id
-                               );
+                               ];
                                $revdlink = Linker::revDeleteLink( $query,
                                        $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
                        }
@@ -1594,11 +1674,11 @@ class SpecialUndelete extends SpecialPage {
                $link = Linker::linkKnown(
                        $titleObj,
                        htmlspecialchars( $time ),
-                       array(),
-                       array(
+                       [],
+                       [
                                'target' => $this->mTargetObj->getPrefixedText(),
                                'timestamp' => $ts
-                       )
+                       ]
                );
 
                if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
@@ -1629,12 +1709,12 @@ class SpecialUndelete extends SpecialPage {
                $link = Linker::linkKnown(
                        $titleObj,
                        htmlspecialchars( $time ),
-                       array(),
-                       array(
+                       [],
+                       [
                                'target' => $this->mTargetObj->getPrefixedText(),
                                'file' => $key,
                                'token' => $user->getEditToken( $key )
-                       )
+                       ]
                );
 
                if ( $file->isDeleted( File::DELETED_FILE ) ) {
@@ -1699,7 +1779,7 @@ class SpecialUndelete extends SpecialPage {
 
                $out = $this->getOutput();
                $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
-               Hooks::run( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
+               Hooks::run( 'UndeleteForm::undelete', [ &$archive, $this->mTargetObj ] );
                $ok = $archive->undelete(
                        $this->mTargetTimestamp,
                        $this->mComment,
@@ -1710,9 +1790,9 @@ class SpecialUndelete extends SpecialPage {
 
                if ( is_array( $ok ) ) {
                        if ( $ok[1] ) { // Undeleted file count
-                               Hooks::run( 'FileUndeleteComplete', array(
+                               Hooks::run( 'FileUndeleteComplete', [
                                        $this->mTargetObj, $this->mFileVersions,
-                                       $this->getUser(), $this->mComment ) );
+                                       $this->getUser(), $this->mComment ] );
                        }
 
                        $link = Linker::linkKnown( $this->mTargetObj );