From 65d74391fd69b34541d846ee867d3714d8a13bcd Mon Sep 17 00:00:00 2001 From: Rob Church Date: Fri, 17 Aug 2007 20:53:17 +0000 Subject: [PATCH] * Confirmation is now required when deleting old versions of files * Users can now enter comments when deleting old versions of files --- RELEASE-NOTES | 2 + includes/AutoLoader.php | 6 +- includes/FileDeleteForm.php | 202 ++++++++++++++++++++++++++++++ includes/ImagePage.php | 136 ++------------------ languages/messages/MessagesEn.php | 13 ++ maintenance/language/messages.inc | 16 ++- 6 files changed, 243 insertions(+), 132 deletions(-) create mode 100644 includes/FileDeleteForm.php diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 3ffb7f85bf..f7528426e0 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -177,6 +177,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * (bug 10937) Distinguish overwritten files in upload log * Introduce 'ArticleUpdateBeforeRedirect' hook; see docs/hooks.txt for more information +* Confirmation is now required when deleting old versions of files +* Users can now enter comments when deleting old versions of files == Bugfixes since 1.10 == diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 632903fc90..25c728cdd8 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -101,6 +101,7 @@ function __autoload($className) { 'ImagePage' => 'includes/ImagePage.php', 'ImageHistoryList' => 'includes/ImagePage.php', 'ImageRemote' => 'includes/ImageRemote.php', + 'FileDeleteForm' => 'includes/FileDeleteForm.php', 'FileRevertForm' => 'includes/FileRevertForm.php', 'Job' => 'includes/JobQueue.php', 'EmaillingJob' => 'includes/EmaillingJob.php', @@ -383,7 +384,4 @@ function wfLoadAllExtensions() { require( $file ); } } -} - - - +} \ No newline at end of file diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php new file mode 100644 index 0000000000..2d4d22a8d5 --- /dev/null +++ b/includes/FileDeleteForm.php @@ -0,0 +1,202 @@ + + */ +class FileDeleteForm { + + private $title = null; + private $file = null; + private $oldimage = ''; + + /** + * Constructor + * + * @param File $file File we're deleting + */ + public function __construct( $file ) { + $this->title = $file->getTitle(); + $this->file = $file; + } + + /** + * Fulfil the request; shows the form or deletes the file, + * pending authentication, confirmation, etc. + */ + public function execute() { + global $wgOut, $wgRequest, $wgUser, $wgLang, $wgServer; + $this->setHeaders(); + + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } elseif( !$wgUser->isLoggedIn() ) { + $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' ); + return; + } elseif( !$wgUser->isAllowed( 'delete' ) ) { + $wgOut->permissionError( 'delete' ); + return; + } elseif( $wgUser->isBlocked() ) { + $wgOut->blockedPage(); + return; + } + + $this->oldimage = $wgRequest->getText( 'oldimage', false ); + $token = $wgRequest->getText( 'wpEditToken' ); + if( $this->oldimage && !$this->isValidOldSpec() ) { + $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars( $this->oldimage ) ); + return; + } + + if( !$this->haveDeletableFile() ) { + $wgOut->addHtml( $this->prepareMessage( 'filedelete-nofile' ) ); + $wgOut->addReturnTo( $this->title ); + return; + } + + // Don't allow accidental deletion of a single file revision + // if this is, in fact, the current revision; things might break + if( $this->oldimage && $this->file->getTimestamp() == $this->getTimestamp() ) { + $wgOut->addHtml( wfMsgExt( 'filedelete-iscurrent', 'parse' ) ); + $wgOut->addReturnTo( $this->title ); + return; + } + + // Perform the deletion if appropriate + if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) { + $comment = $wgRequest->getText( 'wpComment' ); + if( $this->oldimage ) { + $status = $this->file->deleteOld( $this->oldimage, $comment ); + if( $status->ok ) { + // Need to do a log item + $log = new LogPage( 'delete' ); + $log->addEntry( 'delete', $this->title, wfMsg( 'deletedrevision' , $this->oldimage ) ); + } + } else { + $status = $this->file->delete( $comment ); + if( $status->ok ) { + // Need to delete the associated article + $article = new Article( $this->title ); + $article->doDeleteArticle( $comment ); + } + } + if( !$status->isGood() ) + $wgOut->addWikiText( $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) ); + if( $status->ok ) { + $wgOut->addHtml( $this->prepareMessage( 'filedelete-success' ) ); + // Return to the main page if we just deleted all versions of the + // file, otherwise go back to the description page + $wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() ); + } + return; + } + + // Show the form + $this->showForm(); + } + + /** + * Show the confirmation form + */ + private function showForm() { + global $wgOut, $wgUser, $wgRequest, $wgLang, $wgContLang, $wgServer; + + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getAction() ) ); + $form .= Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->oldimage ) ); + $form .= '
' . wfMsgHtml( 'filedelete-legend' ) . ''; + $form .= $this->prepareMessage( 'filedelete-intro' ); + + $form .= '

' . Xml::inputLabel( wfMsg( 'filedelete-comment' ), 'wpComment', 'wpComment', 60 ) . '

'; + $form .= '

' . Xml::submitButton( wfMsg( 'filedelete-submit' ) ) . '

'; + $form .= '
'; + $form .= ''; + + $wgOut->addHtml( $form ); + } + + /** + * Prepare a message referring to the file being deleted, + * showing an appropriate message depending upon whether + * it's a current file or an old version + * + * @param string $message Message base + * @return string + */ + private function prepareMessage( $message ) { + global $wgLang, $wgServer; + if( $this->oldimage ) { + return wfMsgExt( + "{$message}-old", + 'parse', + $this->title->getText(), + $wgLang->date( $this->getTimestamp() ), + $wgLang->time( $this->getTimestamp() ), + $wgServer . $this->file->getArchiveUrl( $this->oldimage ) + ); + } else { + return wfMsgExt( + $message, + 'parse', + $this->title->getText() + ); + } + } + + /** + * Set headers, titles and other bits + */ + private function setHeaders() { + global $wgOut; + $wgOut->setPageTitle( wfMsg( 'filedelete', $this->title->getText() ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + } + + /** + * Is the provided `oldimage` value valid? + * + * @return bool + */ + private function isValidOldSpec() { + return strlen( $this->oldimage ) >= 16 + && strpos( $this->oldimage, '/' ) === false + && strpos( $this->oldimage, '\\' ) === false; + } + + /** + * Could we delete the file specified? If an `oldimage` + * value was provided, does it correspond to an + * existing, local, old version of this file? + * + * @return bool + */ + private function haveDeletableFile() { + $file = wfFindFile( $this->title, $this->oldimage ); + return $file && $file->exists() && $file->isLocal(); + } + + /** + * Prepare the form action + * + * @return string + */ + private function getAction() { + $q = array(); + $q[] = 'action=delete'; + if( $this->oldimage ) + $q[] = 'oldimage=' . urlencode( $this->oldimage ); + return $this->title->getLocalUrl( implode( '&', $q ) ); + } + + /** + * Extract the timestamp of the old version + * + * @return string + */ + private function getTimestamp() { + return substr( $this->oldimage, 0, 14 ); + } + +} \ No newline at end of file diff --git a/includes/ImagePage.php b/includes/ImagePage.php index d64a41835c..6443a16730 100644 --- a/includes/ImagePage.php +++ b/includes/ImagePage.php @@ -480,141 +480,23 @@ EOT $wgOut->addHTML( "\n" ); } - function delete() - { - global $wgUser, $wgOut, $wgRequest; - - if ( !$this->img->exists() || !$this->img->isLocal() ) { - # Use standard article deletion - Article::delete(); - return; - } - - $confirm = $wgRequest->wasPosted(); - $reason = $wgRequest->getVal( 'wpReason' ); - $image = $wgRequest->getVal( 'image' ); - $oldimage = $wgRequest->getVal( 'oldimage' ); - - # Only sysops can delete images. Previously ordinary users could delete - # old revisions, but this is no longer the case. - if ( !$wgUser->isAllowed('delete') ) { - $wgOut->permissionRequired( 'delete' ); - return; - } - if ( $wgUser->isBlocked() ) { - $wgOut->blockedPage(); - return; - } - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - - # Better double-check that it hasn't been deleted yet! - $wgOut->setPagetitle( wfMsg( 'confirmdelete' ) ); - if ( ( !is_null( $image ) ) - && ( '' == trim( $image ) ) ) { - $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); - return; - } - - # Deleting old images doesn't require confirmation - if ( !is_null( $oldimage ) || $confirm ) { - if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) { - $this->doDeleteImage( $reason ); - } else { - $wgOut->showFatalError( wfMsg( 'sessionfailure' ) ); - } - return; - } - - if ( !is_null( $image ) ) { - $q = '&image=' . urlencode( $image ); - } else if ( !is_null( $oldimage ) ) { - $q = '&oldimage=' . urlencode( $oldimage ); - } else { - $q = ''; - } - return $this->confirmDelete( $q, $wgRequest->getText( 'wpReason' ) ); - } - - /* - * Delete an image. - * Called doDeleteImage() not doDelete() so that Article::delete() doesn't - * call back to here. - * - * @param $reason User provided reason for deletion. - */ - function doDeleteImage( $reason ) { - global $wgOut, $wgRequest; - - $oldimage = $wgRequest->getVal( 'oldimage' ); - - if ( !is_null( $oldimage ) ) { - if ( strlen( $oldimage ) < 16 ) { - $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars($oldimage) ); - return; - } - if( strpos( $oldimage, '/' ) !== false || strpos( $oldimage, '\\' ) !== false ) { - $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars($oldimage) ); - return; - } - $status = $this->doDeleteOldImage( $oldimage ); - $deleted = $oldimage; - } else { - $status = $this->img->delete( $reason ); - if ( !$status->isGood() ) { - // Warning or error - $wgOut->addWikiText( $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) ); - } - if ( $status->ok ) { - # Image itself is now gone, and database is cleaned. - # Now we remove the image description page. - $article = new Article( $this->mTitle ); - $article->doDeleteArticle( $reason ); # ignore errors - $deleted = $this->img->getName(); - } - } - - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - - if ( !$status->ok ) { - // Fatal error flagged - $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); - $wgOut->returnToMain( false, $this->mTitle->getPrefixedText() ); - } else { - // Operation completed - $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); - $loglink = '[[Special:Log/delete|' . wfMsg( 'deletionlog' ) . ']]'; - $text = wfMsg( 'deletedtext', $deleted, $loglink ); - $wgOut->addWikiText( $text ); - $wgOut->returnToMain( false, $this->mTitle->getPrefixedText() ); - } - } - /** - * Delete an old revision of an image, - * @return FileRepoStatus + * Delete the file, or an earlier version of it */ - function doDeleteOldImage( $oldimage ) { - global $wgOut; - - $status = $this->img->deleteOld( $oldimage, '' ); - if( !$status->isGood() ) { - $wgOut->addWikiText( $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) ); - } - if ( $status->ok ) { - # Log the deletion - $log = new LogPage( 'delete' ); - $log->addEntry( 'delete', $this->mTitle, wfMsg('deletedrevision',$oldimage) ); + public function delete() { + if( !$this->img->exists() || !$this->img->isLocal() ) { + // Standard article deletion + Article::delete(); + return; } - return $status; + $deleter = new FileDeleteForm( $this->img ); + $deleter->execute(); } /** * Revert the file to an earlier version */ - function revert() { + public function revert() { $reverter = new FileRevertForm( $this->img ); $reverter->execute(); } diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index fa215ffbe5..7abfe69d53 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -1502,6 +1502,19 @@ If you have this image in full resolution upload this one, otherwise change the 'filerevert-success' => "'''[[Media:$1|$1]]''' has been reverted to the [$4 version as of $2, $3].", 'filerevert-badversion' => 'There is no previous local version of this file with the provided timestamp.', +# File deletion +'filedelete' => 'Delete $1', +'filedelete-legend' => 'Delete file', +'filedelete-intro' => "You are deleting '''[[Media:$1|$1]]'''.", +'filedelete-intro-old' => "You are deleting the version of '''[[Media:$1|$1]]''' as of [$4 $2, $3].", +'filedelete-comment' => 'Comment:', +'filedelete-submit' => 'Delete', +'filedelete-success' => "'''$1''' has been deleted.", +'filedelete-success-old' => "The version of '''[[Media:$1|$1]]''' as of $2, $3 has been deleted.", +'filedelete-nofile' => "'''$1''' does not exist on this site.", +'filedelete-nofile-old' => "There is no version of '''$1''' dated $2, $3.", +'filedelete-iscurrent' => 'You are attempting to delete the most recent version of this file. Please revert to an older version first.', + # MIME search 'mimesearch' => 'MIME search', 'mimesearch-summary' => 'This page enables the filtering of files for its MIME-type. Input: contenttype/subtype, e.g. image/jpeg.', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index c6d55ae447..9475513bb8 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -898,6 +898,19 @@ $wgMessageStructure = array( 'filerevert-success', 'filerevert-badversion', ), + 'filedelete' => array( + 'filedelete', + 'filedelete-legend', + 'filedelete-intro', + 'filedelete-intro-old', + 'filedelete-comment', + 'filedelete-submit', + 'filedelete-success', + 'filedelete-success-old', + 'filedelete-nofile', + 'filedelete-nofile-old', + 'filedelete-iscurrent', + ), 'mimesearch' => array( 'mimesearch', 'mimesearch-summary', @@ -2241,6 +2254,7 @@ XHTML id names.", 'licenses' => '', 'imagelist' => 'Image list', 'filerevert' => 'File reversion', + 'filedelete' => 'File deletion', 'mimesearch' => 'MIME search', 'unwatchedpages' => 'Unwatched pages', 'listredirects' => 'List redirects', @@ -2383,4 +2397,4 @@ $wgMessagseWithDollarSigns = array( 'enotif_subject', 'enotif_body', 'allmessagesnotsupportedDB', -); +); \ No newline at end of file -- 2.20.1