From 0a2294a9bbc6c1926c5fca057dff1a92727a7822 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 27 Aug 2012 17:06:53 +0200 Subject: [PATCH] Check prepareSave() before undeleting. 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 | 13 +++-- includes/specials/SpecialUndelete.php | 76 +++++++++++++++++++++------ languages/messages/MessagesDe.php | 3 +- languages/messages/MessagesEn.php | 4 +- languages/messages/MessagesQqq.php | 2 + 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/includes/Content.php b/includes/Content.php index 54d8110cdb..6758407a60 100644 --- a/includes/Content.php +++ b/includes/Content.php @@ -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 diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index b51879ce65..3385234bf5 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -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( '
' . $status->getWikiText( 'cannotundelete', 'cannotundelete' ) . '
' ); + } + + // Show file undeletion warnings and errors $status = $archive->getFileStatus(); if( $status && !$status->isGood() ) { $out->addWikiText( '
' . $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) . '
' ); diff --git a/languages/messages/MessagesDe.php b/languages/messages/MessagesDe.php index 4e766f9071..f314f87665 100644 --- a/languages/messages/MessagesDe.php +++ b/languages/messages/MessagesDe.php @@ -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.", diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 75b1d35640..1cee1ba7a2 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -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.", diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index 3e6ea6f4f6..14bb37fa5f 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -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. +* $1 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', -- 2.20.1