* Confirmation is now required when deleting old versions of files
authorRob Church <robchurch@users.mediawiki.org>
Fri, 17 Aug 2007 20:53:17 +0000 (20:53 +0000)
committerRob Church <robchurch@users.mediawiki.org>
Fri, 17 Aug 2007 20:53:17 +0000 (20:53 +0000)
* Users can now enter comments when deleting old versions of files

RELEASE-NOTES
includes/AutoLoader.php
includes/FileDeleteForm.php [new file with mode: 0644]
includes/ImagePage.php
languages/messages/MessagesEn.php
maintenance/language/messages.inc

index 3ffb7f8..f752842 100644 (file)
@@ -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 ==
 
index 632903f..25c728c 100644 (file)
@@ -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 (file)
index 0000000..2d4d22a
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * File deletion user interface
+ *
+ * @addtogroup Media
+ * @author Rob Church <robchur@gmail.com>
+ */
+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 .= '<fieldset><legend>' . wfMsgHtml( 'filedelete-legend' ) . '</legend>';
+               $form .= $this->prepareMessage( 'filedelete-intro' );
+               
+               $form .= '<p>' . Xml::inputLabel( wfMsg( 'filedelete-comment' ), 'wpComment', 'wpComment', 60 ) . '</p>';
+               $form .= '<p>' . Xml::submitButton( wfMsg( 'filedelete-submit' ) ) . '</p>';
+               $form .= '</fieldset>';
+               $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
index d64a418..6443a16 100644 (file)
@@ -480,141 +480,23 @@ EOT
                $wgOut->addHTML( "</ul>\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();
        }
index fa215ff..7abfe69 100644 (file)
@@ -1502,6 +1502,19 @@ If you have this image in full resolution upload this one, otherwise change the
 'filerevert-success'        => "<span class=\"plainlinks\">'''[[Media:$1|$1]]''' has been reverted to the [$4 version as of $2, $3].</span>",
 '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' => "<span class=\"plainlinks\">You are deleting the version of '''[[Media:$1|$1]]''' as of [$4 $2, $3].</span>",
+'filedelete-comment' => 'Comment:',
+'filedelete-submit' => 'Delete',
+'filedelete-success' => "'''$1''' has been deleted.",
+'filedelete-success-old' => "<span class=\"plainlinks\">The version of '''[[Media:$1|$1]]''' as of $2, $3 has been deleted.</span>",
+'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. <tt>image/jpeg</tt>.',
index c6d55ae..9475513 100644 (file)
@@ -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