X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fspecials%2FSpecialUndelete.php;h=f5015e9dd36f7b15b101142b029de670e8f94713;hb=6826aed67097ded0c9a2366920009505f10a528c;hp=6c89d166c0c87e5a4ade4daeb697015a45a27dd4;hpb=fdbb1752ab37538fb8965996baa2a40f75bf8834;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 6c89d166c0..f5015e9dd3 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -1,34 +1,42 @@ execute(); -} - /** * Used to show archived pages and eventually restore them. + * * @ingroup SpecialPage */ class PageArchive { + + /** + * @var Title + */ protected $title; var $fileStatus; function __construct( $title ) { if( is_null( $title ) ) { - throw new MWException( 'Archiver() given a null title.'); + throw new MWException( __METHOD__ . ' given a null title.' ); } $this->title = $title; } @@ -50,6 +58,7 @@ class PageArchive { * given title prefix. * Returns result wrapper with (ar_namespace, ar_title, count) fields. * + * @param $prefix String: title prefix * @return ResultWrapper */ public static function listPagesByPrefix( $prefix ) { @@ -58,16 +67,15 @@ class PageArchive { $title = Title::newFromText( $prefix ); if( $title ) { $ns = $title->getNamespace(); - $encPrefix = $dbr->escapeLike( $title->getDBkey() ); + $prefix = $title->getDBkey(); } else { // Prolly won't work too good // @todo handle bare namespace names cleanly? $ns = 0; - $encPrefix = $dbr->escapeLike( $prefix ); } $conds = array( 'ar_namespace' => $ns, - "ar_title LIKE '$encPrefix%'", + 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), ); return self::listPages( $dbr, $conds ); } @@ -103,7 +111,7 @@ class PageArchive { $res = $dbr->select( 'archive', array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ), array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), + 'ar_title' => $this->title->getDBkey() ), 'PageArchive::listRevisions', array( 'ORDER BY' => 'ar_timestamp DESC' ) ); $ret = $dbr->resultObject( $res ); @@ -154,7 +162,8 @@ class PageArchive { * Fetch (and decompress if necessary) the stored text for the deleted * revision of the page with the given timestamp. * - * @return string + * @param $timestamp String + * @return String * @deprecated Use getRevision() for more flexible information */ function getRevisionText( $timestamp ) { @@ -165,7 +174,8 @@ class PageArchive { /** * Return a Revision object containing data for the deleted revision. * Note that the result *may* or *may not* have a null page ID. - * @param string $timestamp + * + * @param $timestamp String * @return Revision */ function getRevision( $timestamp ) { @@ -184,24 +194,11 @@ class PageArchive { 'ar_deleted', 'ar_len' ), array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), __METHOD__ ); if( $row ) { - return new Revision( array( - 'page' => $this->title->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => ($row->ar_text_id - ? null - : Revision::getRevisionText( $row, 'ar_' ) ), - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); + return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) ); } else { return null; } @@ -214,7 +211,7 @@ class PageArchive { * May produce unexpected results in case of history merges or other * unusual time issues. * - * @param string $timestamp + * @param $timestamp String * @return Revision or null */ function getPreviousRevision( $timestamp ) { @@ -224,8 +221,8 @@ class PageArchive { $row = $dbr->selectRow( 'archive', 'ar_timestamp', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey(), - 'ar_timestamp < ' . + 'ar_title' => $this->title->getDBkey(), + 'ar_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), __METHOD__, array( @@ -262,6 +259,9 @@ class PageArchive { /** * Get the text from an archive row containing ar_text, ar_flags and ar_text_id + * + * @param $row Object: database row + * @return Revision */ function getTextFromRow( $row ) { if( is_null( $row->ar_text_id ) ) { @@ -286,32 +286,33 @@ class PageArchive { * * If there are no archived revisions for the page, returns NULL. * - * @return string + * @return String */ function getLastRevisionText() { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'archive', array( 'ar_text', 'ar_flags', 'ar_text_id' ), array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::getLastRevisionText', + 'ar_title' => $this->title->getDBkey() ), + __METHOD__, array( 'ORDER BY' => 'ar_timestamp DESC' ) ); if( $row ) { return $this->getTextFromRow( $row ); } else { - return NULL; + return null; } } /** * Quick check if any archived revisions are present for the page. - * @return bool + * + * @return Boolean */ function isDeleted() { $dbr = wfGetDB( DB_SLAVE ); $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', array( 'ar_namespace' => $this->title->getNamespace(), - 'ar_title' => $this->title->getDBkey() ) ); + 'ar_title' => $this->title->getDBkey() ) ); return ($n > 0); } @@ -320,10 +321,10 @@ class PageArchive { * Once restored, the items will be removed from the archive tables. * The deletion log will be updated with an undeletion notice. * - * @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 $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param $comment String + * @param $fileVersions Array + * @param $unsuppress Boolean * * @return array(number of file revisions restored, number of image revisions restored, log message) * on success, false on failure @@ -339,15 +340,19 @@ class PageArchive { if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { $img = wfLocalFile( $this->title ); $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); + if ( !$this->fileStatus->isOk() ) { + return false; + } $filesRestored = $this->fileStatus->successCount; } else { $filesRestored = 0; } if( $restoreText ) { - $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress ); - if($textRestored === false) // It must be one of UNDELETE_* + $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); + if( $textRestored === false ) { // It must be one of UNDELETE_* return false; + } } else { $textRestored = 0; } @@ -372,7 +377,7 @@ class PageArchive { } if( trim( $comment ) != '' ) - $reason .= ": {$comment}"; + $reason .= wfMsgForContent( 'colon-separator' ) . $comment; $log->addEntry( 'restore', $this->title, $reason ); return array($textRestored, $filesRestored, $reason); @@ -383,14 +388,13 @@ class PageArchive { * 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. * - * @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, remove all ar_deleted/fa_deleted restrictions of seletected revs + * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @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 Mixed: number of revisions restored or false on failure */ - private function undeleteRevisions( $timestamps, $unsuppress = false ) { + private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { if ( wfReadOnly() ) return false; $restoreAll = empty( $timestamps ); @@ -399,13 +403,14 @@ class PageArchive { # Does this page already exist? We'll have to update it... $article = new Article( $this->title ); - $options = 'FOR UPDATE'; + $options = 'FOR UPDATE'; // lock page $page = $dbw->selectRow( 'page', array( 'page_id', 'page_latest' ), array( 'page_namespace' => $this->title->getNamespace(), - 'page_title' => $this->title->getDBkey() ), + 'page_title' => $this->title->getDBkey() ), __METHOD__, - $options ); + $options + ); if( $page ) { $makepage = false; # Page already exists. Import the history, and if necessary @@ -462,50 +467,53 @@ class PageArchive { $oldones ), __METHOD__, /* options */ array( 'ORDER BY' => 'ar_timestamp' ) - ); + ); $ret = $dbw->resultObject( $result ); $rev_count = $dbw->numRows( $result ); + if( !$rev_count ) { + wfDebug( __METHOD__.": no revisions to restore\n" ); + return false; // ??? + } + + $ret->seek( $rev_count - 1 ); // move to last + $row = $ret->fetchObject(); // get newest archived rev + $ret->seek( 0 ); // move back 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! + } + // Safe to insert now... $newid = $article->insertOn( $dbw ); $pageId = $newid; + } else { + // Check if a deleted revision will become the current revision... + 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! + } + } } $revision = null; $restored = 0; - while( $row = $ret->fetchObject() ) { - if( $row->ar_text_id ) { - // Revision was deleted in 1.5+; text is in - // the regular text table, use the reference. - // Specify null here so the so the text is - // dereferenced for page length info if needed. - $revText = null; - } else { - // Revision was deleted in 1.4 or earlier. - // Text is squashed into the archive row, and - // a new text table entry will be created for it. - $revText = Revision::getRevisionText( $row, 'ar_' ); - } + foreach ( $ret as $row ) { // Check for key dupes due to shitty 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 } - - $revision = new Revision( array( - 'page' => $pageId, - 'id' => $row->ar_rev_id, - 'text' => $revText, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $unsuppress ? 0 : $row->ar_deleted, - 'len' => $row->ar_len + // Insert one revision at a time...maintaining deletion status + // unless we are specifically removing all restrictions... + $revision = Revision::newFromArchiveRow( $row, + array( + 'page' => $pageId, + 'deleted' => $unsuppress ? 0 : $row->ar_deleted ) ); + $revision->insertOn( $dbw ); $restored++; @@ -518,7 +526,7 @@ class PageArchive { 'ar_title' => $this->title->getDBkey(), $oldones ), __METHOD__ ); - + // Was anything restored at all? if( $restored == 0 ) return 0; @@ -529,21 +537,13 @@ class PageArchive { if( $newid || $wasnew ) { // Update site stats, link tables, etc $article->createUpdates( $revision ); - // We don't handle well with top revision deleted - if( $revision->getVisibility() ) { - $dbw->update( 'revision', - array( 'rev_deleted' => 0 ), - array( 'rev_id' => $revision->getId() ), - __METHOD__ - ); - } } if( $newid ) { - wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) ); + wfRunHooks( 'ArticleUndelete', array( &$this->title, true, $comment ) ); Article::onArticleCreate( $this->title ); } else { - wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) ); + wfRunHooks( 'ArticleUndelete', array( &$this->title, false, $comment ) ); Article::onArticleEdit( $this->title ); } @@ -553,58 +553,72 @@ class PageArchive { } } else { // Revision couldn't be created. This is very weird - return self::UNDELETE_UNKNOWNERR; + wfDebug( "Undelete: unknown error...\n" ); + return false; } return $restored; } + /** + * @return Status + */ function getFileStatus() { return $this->fileStatus; } } /** - * The HTML form for Special:Undelete, which allows users with the appropriate - * permissions to view and restore deleted content. + * Special page allowing users with the appropriate permissions to view + * and restore deleted content. + * * @ingroup SpecialPage */ -class UndeleteForm { +class SpecialUndelete extends SpecialPage { var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mComment, $mToken; + var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken, $mRequest; + + function __construct( $request = null ) { + parent::__construct( 'Undelete', 'deletedhistory' ); - function UndeleteForm( $request, $par = "" ) { + if ( $request === null ) { + global $wgRequest; + $this->mRequest = $wgRequest; + } else { + $this->mRequest = $request; + } + } + + function loadRequest() { global $wgUser; - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mSearchPrefix = $request->getText( 'prefix' ); - $time = $request->getVal( 'timestamp' ); + $this->mAction = $this->mRequest->getVal( 'action' ); + $this->mTarget = $this->mRequest->getVal( 'target' ); + $this->mSearchPrefix = $this->mRequest->getText( 'prefix' ); + $time = $this->mRequest->getVal( 'timestamp' ); $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; - $this->mFile = $request->getVal( 'file' ); - - $posted = $request->wasPosted() && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - $this->mRestore = $request->getCheck( 'restore' ) && $posted; - $this->mInvert = $request->getCheck( 'invert' ) && $posted; - $this->mPreview = $request->getCheck( 'preview' ) && $posted; - $this->mDiff = $request->getCheck( 'diff' ); - $this->mComment = $request->getText( 'wpComment' ); - $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); - $this->mToken = $request->getVal( 'token' ); - - if( $par != "" ) { - $this->mTarget = $par; - } + $this->mFile = $this->mRequest->getVal( 'file' ); + + $posted = $this->mRequest->wasPosted() && + $wgUser->matchEditToken( $this->mRequest->getVal( 'wpEditToken' ) ); + $this->mRestore = $this->mRequest->getCheck( 'restore' ) && $posted; + $this->mInvert = $this->mRequest->getCheck( 'invert' ) && $posted; + $this->mPreview = $this->mRequest->getCheck( 'preview' ) && $posted; + $this->mDiff = $this->mRequest->getCheck( 'diff' ); + $this->mComment = $this->mRequest->getText( 'wpComment' ); + $this->mUnsuppress = $this->mRequest->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); + $this->mToken = $this->mRequest->getVal( 'token' ); + if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { - $this->mAllowed = true; - } else { + $this->mAllowed = true; // user can restore + $this->mCanView = true; // user can view content + } elseif ( $wgUser->isAllowed( 'deletedtext' ) ) { + $this->mAllowed = false; // user cannot restore + $this->mCanView = true; // user can view content + } else { // user can only view the list of revisions $this->mAllowed = false; + $this->mCanView = false; $this->mTimestamp = ''; $this->mRestore = false; } - if ( $this->mTarget !== "" ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - } else { - $this->mTargetObj = NULL; - } + if( $this->mRestore || $this->mInvert ) { $timestamps = array(); $this->mFileVersions = array(); @@ -623,14 +637,34 @@ class UndeleteForm { } } - function execute() { + function execute( $par ) { global $wgOut, $wgUser; + + $this->setHeaders(); + if ( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + $this->outputHeader(); + + $this->loadRequest(); + if ( $this->mAllowed ) { $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); } else { $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); } + if( $par != '' ) { + $this->mTarget = $par; + } + if ( $this->mTarget !== '' ) { + $this->mTargetObj = Title::newFromURL( $this->mTarget ); + $wgUser->getSkin()->setRelevantTitle( $this->mTargetObj ); + } else { + $this->mTargetObj = null; + } + if( is_null( $this->mTargetObj ) ) { # Not all users can just browse every deleted page from the list if( $wgUser->isAllowed( 'browsearchive' ) ) { @@ -652,8 +686,15 @@ class UndeleteForm { if( $this->mFile !== null ) { $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile ); // Check if user is allowed to see this file - if( !$file->userCan( File::DELETED_FILE ) ) { - $wgOut->permissionRequired( 'suppressrevision' ); + if ( !$file->exists() ) { + $wgOut->addWikiMsg( 'filedelete-nofile', $this->mFile ); + return; + } else if( !$file->userCan( File::DELETED_FILE ) ) { + if( $file->isDeleted( File::DELETED_RESTRICTED ) ) { + $wgOut->permissionRequired( 'suppressrevision' ); + } else { + $wgOut->permissionRequired( 'deletedtext' ); + } return false; } elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) { $this->showFileConfirmationForm( $this->mFile ); @@ -663,6 +704,11 @@ class UndeleteForm { } } if( $this->mRestore && $this->mAction == "submit" ) { + global $wgUploadMaintenance; + if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) { + $wgOut->wrapWikiMsg( "
\n$1\n
\n", array( 'filedelete-maintenance' ) ); + return; + } return $this->undelete(); } if( $this->mInvert && $this->mAction == "submit" ) { @@ -680,8 +726,8 @@ class UndeleteForm { 'method' => 'get', 'action' => $wgScript ) ) . Xml::fieldset( wfMsg( 'undelete-search-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . + Html::hidden( 'title', + $this->getTitle()->getPrefixedDbKey() ) . Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), 'prefix', 'prefix', 20, $this->mSearchPrefix ) . ' ' . @@ -693,7 +739,7 @@ class UndeleteForm { // Generic list of deleted pages private function showList( $result ) { - global $wgLang, $wgContLang, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; if( $result->numRows() == 0 ) { $wgOut->addWikiMsg( 'undelete-no-results' ); @@ -703,12 +749,16 @@ class UndeleteForm { $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) ); $sk = $wgUser->getSkin(); - $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $undelete = $this->getTitle(); $wgOut->addHTML( "