Check prepareSave() before undeleting.
authordaniel <daniel.kinzler@wikimedia.de>
Mon, 27 Aug 2012 15:06:53 +0000 (17:06 +0200)
committerdaniel <daniel.kinzler@wikimedia.de>
Mon, 27 Aug 2012 15:17:49 +0000 (17:17 +0200)
This calls prepareSave() before undeleting a page, to make sure the restored
revision does not violate any constraints imposed by the ContentHandler.

To achieve this, several improvements are made to the error handling in
SpecialUndelete.

Change-Id: I41bab9892de7c604be6aa7f6db9dee47b3f0d27c

includes/Content.php
includes/specials/SpecialUndelete.php
languages/messages/MessagesDe.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php

index 54d8110..6758407 100644 (file)
@@ -394,13 +394,18 @@ interface Content {
        public function preloadTransform( Title $title, ParserOptions $popts );
 
        /**
-        * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent().
-        * This may be used to store additional information in the database, or check the content's
-        * consistency with global state.
+        * Prepare Content for saving. Called before Content is saved by WikiPage::doEditContent() and in
+        * similar places.
         *
-        * Note that this method will be called inside the same transaction bracket that will be used
+        * This may be used to check the content's consistency with global state. This function should
+        * NOT write any information to the database.
+        *
+        * Note that this method will usually be called inside the same transaction bracket that will be used
         * to save the new revision.
         *
+        * Note that this method is called before any update to the page table is performed. This means that
+        * $page may not yet know a page ID.
+        *
         * @param WikiPage $page The page to be saved.
         * @param int      $flags bitfield for use with EDIT_XXX constants, see WikiPage::doEditContent()
         * @param int      $baseRevId the ID of the current revision
index b51879c..3385234 100644 (file)
@@ -32,7 +32,16 @@ class PageArchive {
         * @var Title
         */
        protected $title;
-       var $fileStatus;
+
+       /**
+        * @var Status
+        */
+       protected $fileStatus;
+
+       /**
+        * @var Status
+        */
+       protected $revisionStatus;
 
        function __construct( $title ) {
                if( is_null( $title ) ) {
@@ -359,7 +368,7 @@ class PageArchive {
                if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
                        $img = wfLocalFile( $this->title );
                        $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
-                       if ( !$this->fileStatus->isOk() ) {
+                       if ( !$this->fileStatus->isOK() ) {
                                return false;
                        }
                        $filesRestored = $this->fileStatus->successCount;
@@ -368,10 +377,12 @@ class PageArchive {
                }
 
                if( $restoreText ) {
-                       $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
-                       if( $textRestored === false ) { // It must be one of UNDELETE_*
+                       $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
+                       if( !$this->revisionStatus->isOK() ) {
                                return false;
                        }
+
+                       $textRestored = $this->revisionStatus->getValue();
                } else {
                        $textRestored = 0;
                }
@@ -419,13 +430,13 @@ class PageArchive {
         * @param $comment String
         * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs
         *
-        * @return Mixed: number of revisions restored or false on failure
+        * @return Status, containing the number of revisions restored on success
         */
        private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
                global $wgContentHandlerNoDB;
 
                if ( wfReadOnly() ) {
-                       return false;
+                       throw new ReadOnlyError();
                }
                $restoreAll = empty( $timestamps );
 
@@ -455,9 +466,14 @@ class PageArchive {
                        $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
                                array( 'rev_id' => $previousRevId ),
                                __METHOD__ );
+
                        if( $previousTimestamp === false ) {
                                wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
-                               return 0;
+
+                               $status = Status::newGood( 0 );
+                               $status->warning( 'undeleterevision-missing' );
+
+                               return $status;
                        }
                } else {
                        # Have to create a new article...
@@ -513,17 +529,38 @@ class PageArchive {
                $rev_count = $dbw->numRows( $result );
                if( !$rev_count ) {
                        wfDebug( __METHOD__ . ": no revisions to restore\n" );
-                       return false; // ???
+
+                       $status = Status::newGood( 0 );
+                       $status->warning( "undelete-no-results" );
+                       return $status;
                }
 
                $ret->seek( $rev_count - 1 ); // move to last
                $row = $ret->fetchObject(); // get newest archived rev
                $ret->seek( 0 ); // move back
 
+               // 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
+                       ) );
+
+               $m = $revision->getContentModel();
+
+               $user = User::newFromName( $revision->getRawUserText(), 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() ) {
+                       return $status;
+               }
+
                if( $makepage ) {
                        // Check the state of the newest to-be version...
                        if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
-                               return false; // we can't leave the current revision like this!
+                               return Status::newFatal( "undeleterevdel" );
                        }
                        // Safe to insert now...
                        $newid  = $article->insertOn( $dbw );
@@ -533,7 +570,7 @@ class PageArchive {
                        if( $row->ar_timestamp > $previousTimestamp ) {
                                // Check the state of the newest to-be version...
                                if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
-                                       return false; // we can't leave the current revision like this!
+                                       return Status::newFatal( "undeleterevdel" );
                                }
                        }
                }
@@ -573,7 +610,7 @@ class PageArchive {
 
                // Was anything restored at all?
                if ( $restored == 0 ) {
-                       return 0;
+                       return Status::newGood( 0 );
                }
 
                $created = (bool)$newid;
@@ -593,13 +630,18 @@ class PageArchive {
                        $update->doUpdate();
                }
 
-               return $restored;
+               return Status::newGood( $restored );
        }
 
        /**
         * @return Status
         */
        function getFileStatus() { return $this->fileStatus; }
+
+       /**
+        * @return Status
+        */
+       function getRevisionStatus() { return $this->revisionStatus; }
 }
 
 /**
@@ -1448,11 +1490,15 @@ class SpecialUndelete extends SpecialPage {
                        $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
                } else {
                        $out->setPageTitle( $this->msg( 'undelete-error' ) );
-                       $out->addWikiMsg( 'cannotundelete' );
-                       $out->addWikiMsg( 'undeleterevdel' );
                }
 
-               // Show file deletion warnings and errors
+               // Show revision undeletion warnings and errors
+               $status = $archive->getRevisionStatus();
+               if( $status && !$status->isGood() ) {
+                       $out->addWikiText( '<div class="error">' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '</div>' );
+               }
+
+               // Show file undeletion warnings and errors
                $status = $archive->getFileStatus();
                if( $status && !$status->isGood() ) {
                        $out->addWikiText( '<div class="error">' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '</div>' );
index 4e766f9..f314f87 100644 (file)
@@ -2592,7 +2592,8 @@ Der aktuelle Text der gelöschten Seite ist nur Administratoren zugänglich.',
 'undeletedrevisions' => '{{PLURAL:$1|1 Version wurde|$1 Versionen wurden}} wiederhergestellt',
 'undeletedrevisions-files' => '{{PLURAL:$1|1 Version|$1 Versionen}} und {{PLURAL:$2|1 Datei|$2 Dateien}} wurden wiederhergestellt',
 'undeletedfiles' => '{{PLURAL:$1|1 Datei wurde|$1 Dateien wurden}} wiederhergestellt',
-'cannotundelete' => 'Wiederherstellung fehlgeschlagen; jemand anderes hat die Seite bereits wiederhergestellt.',
+'cannotundelete' => 'Wiederherstellung fehlgeschlagen:
+$1',
 'undeletedpage' => "'''„$1“''' wurde wiederhergestellt.
 
 Im [[Special:Log/delete|Lösch-Logbuch]] findest du eine Übersicht der gelöschten und wiederhergestellten Seiten.",
index 75b1d35..1cee1ba 100644 (file)
@@ -3067,8 +3067,8 @@ You may have a bad link, or the revision may have been restored or removed from
 'undeletedrevisions'           => '{{PLURAL:$1|1 revision|$1 revisions}} restored',
 'undeletedrevisions-files'     => '{{PLURAL:$1|1 revision|$1 revisions}} and {{PLURAL:$2|1 file|$2 files}} restored',
 'undeletedfiles'               => '{{PLURAL:$1|1 file|$1 files}} restored',
-'cannotundelete'               => 'Undelete failed;
-someone else may have undeleted the page first.',
+'cannotundelete'               => 'Undelete failed:
+$1',
 'undeletedpage'                => "'''$1 has been restored'''
 
 Consult the [[Special:Log/delete|deletion log]] for a record of recent deletions and restorations.",
index 3e6ea6f..14bb37f 100644 (file)
@@ -2872,6 +2872,8 @@ Options for the duration of the page protection. Example: See e.g. [[MediaWiki:P
 {{Identical|Reset}}',
 'undeleteinvert' => '{{Identical|Invert selection}}',
 'undeletecomment' => '{{Identical|Reason}}',
+'cannotundelete' => 'Message shown when undeletion failed for some reason.
+* <code>$1</code> is the combined wikitext of messages for all errors that caused the failure.',
 'undelete-search-title' => 'Page title when showing the search form in Special:Undelete',
 'undelete-search-submit' => '{{Identical|Search}}',
 'undelete-error' => 'Page title when a page could not be undeleted',